agendas: add time period exception groups (#62801)
This commit is contained in:
parent
89289ad665
commit
dcec0f2d3f
|
@ -0,0 +1,45 @@
|
|||
# Generated by Django 2.2.19 on 2022-03-23 16:08
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agendas', '0112_auto_20220323_1320'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TimePeriodExceptionGroup',
|
||||
fields=[
|
||||
(
|
||||
'id',
|
||||
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
('slug', models.SlugField(max_length=160, verbose_name='Identifier')),
|
||||
('label', models.CharField(max_length=150, verbose_name='Label')),
|
||||
(
|
||||
'unavailability_calendar',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to='agendas.UnavailabilityCalendar'
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ['label'],
|
||||
'unique_together': {('unavailability_calendar', 'slug')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='timeperiodexception',
|
||||
name='group',
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='exceptions',
|
||||
to='agendas.TimePeriodExceptionGroup',
|
||||
),
|
||||
),
|
||||
]
|
|
@ -2399,6 +2399,7 @@ class TimePeriodExceptionSource(models.Model):
|
|||
else:
|
||||
parsed = data
|
||||
|
||||
categories = collections.defaultdict(list)
|
||||
with transaction.atomic():
|
||||
# delete old exceptions related to this source
|
||||
self.timeperiodexception_set.all().delete()
|
||||
|
@ -2440,9 +2441,16 @@ class TimePeriodExceptionSource(models.Model):
|
|||
'recurrence_id': 0,
|
||||
}
|
||||
|
||||
if 'categories' in vevent.contents and len(vevent.categories.value) > 0:
|
||||
category = vevent.categories.value[0]
|
||||
else:
|
||||
category = None
|
||||
|
||||
if not vevent.rruleset:
|
||||
# classical event
|
||||
TimePeriodException.objects.create(**event)
|
||||
exception = TimePeriodException.objects.create(**event)
|
||||
if category:
|
||||
categories[category].append(exception)
|
||||
elif vevent.rruleset.count():
|
||||
# recurring event until recurring_days in the future
|
||||
from_dt = start_dt
|
||||
|
@ -2460,7 +2468,18 @@ class TimePeriodExceptionSource(models.Model):
|
|||
event['start_datetime'] = start_dt
|
||||
event['end_datetime'] = end_dt
|
||||
if end_dt >= update_datetime:
|
||||
TimePeriodException.objects.create(**event)
|
||||
exception = TimePeriodException.objects.create(**event)
|
||||
if category:
|
||||
categories[category].append(exception)
|
||||
|
||||
if self.unavailability_calendar_id:
|
||||
for category, exceptions in categories.items():
|
||||
exception_group, dummy = TimePeriodExceptionGroup.objects.get_or_create(
|
||||
unavailability_calendar_id=self.unavailability_calendar_id,
|
||||
slug=category,
|
||||
defaults={'label': exceptions[0].label},
|
||||
)
|
||||
exception_group.exceptions.add(*exceptions)
|
||||
|
||||
@classmethod
|
||||
def import_json(cls, data):
|
||||
|
@ -2564,6 +2583,19 @@ class UnavailabilityCalendar(models.Model):
|
|||
return created, unavailability_calendar
|
||||
|
||||
|
||||
class TimePeriodExceptionGroup(models.Model):
|
||||
unavailability_calendar = models.ForeignKey(UnavailabilityCalendar, on_delete=models.CASCADE)
|
||||
slug = models.SlugField(_('Identifier'), max_length=160)
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
|
||||
class Meta:
|
||||
ordering = ['label']
|
||||
unique_together = ['unavailability_calendar', 'slug']
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
|
||||
class TimePeriodException(models.Model):
|
||||
desk = models.ForeignKey(Desk, on_delete=models.CASCADE, null=True)
|
||||
unavailability_calendar = models.ForeignKey(UnavailabilityCalendar, on_delete=models.CASCADE, null=True)
|
||||
|
@ -2573,6 +2605,9 @@ class TimePeriodException(models.Model):
|
|||
end_datetime = models.DateTimeField(_('Exception end time'))
|
||||
update_datetime = models.DateTimeField(auto_now=True)
|
||||
recurrence_id = models.PositiveIntegerField(_('Recurrence ID'), default=0)
|
||||
group = models.ForeignKey(
|
||||
TimePeriodExceptionGroup, on_delete=models.CASCADE, null=True, related_name='exceptions'
|
||||
)
|
||||
|
||||
@property
|
||||
def read_only(self):
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//PYVOBJECT//NONSGML Version 1//EN
|
||||
BEGIN:VEVENT
|
||||
UID:christmas_holidays-2017
|
||||
DTSTART;VALUE=DATE:20171223
|
||||
DTEND;VALUE=DATE:20180108
|
||||
CATEGORIES:christmas_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances de Noël
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:summer_holidays-2018
|
||||
DTSTART;VALUE=DATE:20180707
|
||||
DTEND;VALUE=DATE:20180903
|
||||
CATEGORIES:summer_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances d’Été
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:christmas_holidays-2018
|
||||
DTSTART;VALUE=DATE:20181222
|
||||
DTEND;VALUE=DATE:20190107
|
||||
CATEGORIES:christmas_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances de Noël
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:summer_holidays-2019
|
||||
DTSTART;VALUE=DATE:20190706
|
||||
DTEND;VALUE=DATE:20190902
|
||||
CATEGORIES:summer_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances d’Été
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:christmas_holidays-2019
|
||||
DTSTART;VALUE=DATE:20191221
|
||||
DTEND;VALUE=DATE:20200106
|
||||
CATEGORIES:christmas_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances de Noël
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:summer_holidays-2020
|
||||
DTSTART;VALUE=DATE:20200704
|
||||
DTEND;VALUE=DATE:20200901
|
||||
CATEGORIES:summer_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances d’Été
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:christmas_holidays-2020
|
||||
DTSTART;VALUE=DATE:20201219
|
||||
DTEND;VALUE=DATE:20210104
|
||||
CATEGORIES:christmas_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances de Noël
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:summer_holidays-2021
|
||||
DTSTART;VALUE=DATE:20210706
|
||||
DTEND;VALUE=DATE:20210902
|
||||
CATEGORIES:summer_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances d’Été
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:christmas_holidays-2021
|
||||
DTSTART;VALUE=DATE:20211218
|
||||
DTEND;VALUE=DATE:20220103
|
||||
CATEGORIES:christmas_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances de Noël
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:summer_holidays-2022
|
||||
DTSTART;VALUE=DATE:20220707
|
||||
DTEND;VALUE=DATE:20220901
|
||||
CATEGORIES:summer_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances d’Été
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:christmas_holidays-2022
|
||||
DTSTART;VALUE=DATE:20221217
|
||||
DTEND;VALUE=DATE:20230103
|
||||
CATEGORIES:christmas_holidays
|
||||
DTSTAMP:20220328T140507Z
|
||||
SUMMARY:Vacances de Noël
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
|
@ -31,6 +31,7 @@ from chrono.agendas.models import (
|
|||
SharedCustodyRule,
|
||||
TimePeriod,
|
||||
TimePeriodException,
|
||||
TimePeriodExceptionGroup,
|
||||
TimePeriodExceptionSource,
|
||||
UnavailabilityCalendar,
|
||||
VirtualMember,
|
||||
|
@ -119,6 +120,10 @@ with open('tests/data/atreal.ics') as f:
|
|||
ICS_ATREAL = f.read()
|
||||
|
||||
|
||||
with open('tests/data/holidays.ics') as f:
|
||||
ICS_HOLIDAYS = f.read()
|
||||
|
||||
|
||||
def test_slug():
|
||||
agenda = Agenda(label='Foo bar')
|
||||
agenda.save()
|
||||
|
@ -854,6 +859,37 @@ def test_timeperiodexception_from_settings_command():
|
|||
assert not TimePeriodExceptionSource.objects.exists()
|
||||
|
||||
|
||||
def test_timeperiodexception_groups():
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
||||
source = unavailability_calendar.timeperiodexceptionsource_set.create(
|
||||
ics_filename='holidays.ics', ics_file=ContentFile(ICS_HOLIDAYS, name='holidays.ics')
|
||||
)
|
||||
source.refresh_timeperiod_exceptions_from_ics()
|
||||
assert TimePeriodException.objects.count() == 11
|
||||
group1, group2 = TimePeriodExceptionGroup.objects.all()
|
||||
assert group1.label == 'Vacances de Noël'
|
||||
assert group1.slug == 'christmas_holidays'
|
||||
assert group2.label == 'Vacances d’Été'
|
||||
assert group2.slug == 'summer_holidays'
|
||||
|
||||
assert group1.exceptions.count() == 6
|
||||
assert group2.exceptions.count() == 5
|
||||
|
||||
unavailability_calendar.delete()
|
||||
assert not TimePeriodException.objects.exists()
|
||||
assert not TimePeriodExceptionGroup.objects.exists()
|
||||
|
||||
# check no groups are created for desks
|
||||
agenda = Agenda.objects.create(label='Test 1 agenda')
|
||||
desk = Desk.objects.create(label='Test 1 desk', agenda=agenda)
|
||||
source = desk.timeperiodexceptionsource_set.create(
|
||||
ics_filename='holidays.ics', ics_file=ContentFile(ICS_HOLIDAYS, name='holidays.ics')
|
||||
)
|
||||
source.refresh_timeperiod_exceptions_from_ics()
|
||||
assert TimePeriodException.objects.count() == 11
|
||||
assert not TimePeriodExceptionGroup.objects.exists()
|
||||
|
||||
|
||||
def test_base_meeting_duration():
|
||||
agenda = Agenda(label='Meeting', kind='meetings')
|
||||
agenda.save()
|
||||
|
|
Loading…
Reference in New Issue