Projet

Général

Profil

0001-agendas-add-time-period-exception-groups-62801.patch

Valentin Deniaud, 29 mars 2022 17:20

Télécharger (10,5 ko)

Voir les différences:

Subject: [PATCH 1/4] agendas: add time period exception groups (#62801)

 .../migrations/0113_auto_20220323_1708.py     | 45 +++++++++
 chrono/agendas/models.py                      | 39 +++++++-
 tests/data/holidays.ics                       | 92 +++++++++++++++++++
 tests/test_agendas.py                         | 36 ++++++++
 4 files changed, 210 insertions(+), 2 deletions(-)
 create mode 100644 chrono/agendas/migrations/0113_auto_20220323_1708.py
 create mode 100644 tests/data/holidays.ics
chrono/agendas/migrations/0113_auto_20220323_1708.py
1
# Generated by Django 2.2.19 on 2022-03-23 16:08
2

  
3
import django.db.models.deletion
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('agendas', '0112_auto_20220323_1320'),
11
    ]
12

  
13
    operations = [
14
        migrations.CreateModel(
15
            name='TimePeriodExceptionGroup',
16
            fields=[
17
                (
18
                    'id',
19
                    models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
20
                ),
21
                ('slug', models.SlugField(max_length=160, verbose_name='Identifier')),
22
                ('label', models.CharField(max_length=150, verbose_name='Label')),
23
                (
24
                    'unavailability_calendar',
25
                    models.ForeignKey(
26
                        on_delete=django.db.models.deletion.CASCADE, to='agendas.UnavailabilityCalendar'
27
                    ),
28
                ),
29
            ],
30
            options={
31
                'ordering': ['label'],
32
                'unique_together': {('unavailability_calendar', 'slug')},
33
            },
34
        ),
35
        migrations.AddField(
36
            model_name='timeperiodexception',
37
            name='group',
38
            field=models.ForeignKey(
39
                null=True,
40
                on_delete=django.db.models.deletion.CASCADE,
41
                related_name='exceptions',
42
                to='agendas.TimePeriodExceptionGroup',
43
            ),
44
        ),
45
    ]
chrono/agendas/models.py
2403 2403
        else:
2404 2404
            parsed = data
2405 2405

  
2406
        categories = collections.defaultdict(list)
2406 2407
        with transaction.atomic():
2407 2408
            # delete old exceptions related to this source
2408 2409
            self.timeperiodexception_set.all().delete()
......
2444 2445
                    'recurrence_id': 0,
2445 2446
                }
2446 2447

  
2448
                if 'categories' in vevent.contents and len(vevent.categories.value) > 0:
2449
                    category = vevent.categories.value[0]
2450
                else:
2451
                    category = None
2452

  
2447 2453
                if not vevent.rruleset:
2448 2454
                    # classical event
2449
                    TimePeriodException.objects.create(**event)
2455
                    exception = TimePeriodException.objects.create(**event)
2456
                    if category:
2457
                        categories[category].append(exception)
2450 2458
                elif vevent.rruleset.count():
2451 2459
                    # recurring event until recurring_days in the future
2452 2460
                    from_dt = start_dt
......
2464 2472
                        event['start_datetime'] = start_dt
2465 2473
                        event['end_datetime'] = end_dt
2466 2474
                        if end_dt >= update_datetime:
2467
                            TimePeriodException.objects.create(**event)
2475
                            exception = TimePeriodException.objects.create(**event)
2476
                            if category:
2477
                                categories[category].append(exception)
2478

  
2479
            if self.unavailability_calendar_id:
2480
                for category, exceptions in categories.items():
2481
                    exception_group, dummy = TimePeriodExceptionGroup.objects.get_or_create(
2482
                        unavailability_calendar_id=self.unavailability_calendar_id,
2483
                        slug=category,
2484
                        defaults={'label': exceptions[0].label},
2485
                    )
2486
                    exception_group.exceptions.add(*exceptions)
2468 2487

  
2469 2488
    @classmethod
2470 2489
    def import_json(cls, data):
......
2568 2587
        return created, unavailability_calendar
2569 2588

  
2570 2589

  
2590
class TimePeriodExceptionGroup(models.Model):
2591
    unavailability_calendar = models.ForeignKey(UnavailabilityCalendar, on_delete=models.CASCADE)
2592
    slug = models.SlugField(_('Identifier'), max_length=160)
2593
    label = models.CharField(_('Label'), max_length=150)
2594

  
2595
    class Meta:
2596
        ordering = ['label']
2597
        unique_together = ['unavailability_calendar', 'slug']
2598

  
2599
    def __str__(self):
2600
        return self.label
2601

  
2602

  
2571 2603
class TimePeriodException(models.Model):
2572 2604
    desk = models.ForeignKey(Desk, on_delete=models.CASCADE, null=True)
2573 2605
    unavailability_calendar = models.ForeignKey(UnavailabilityCalendar, on_delete=models.CASCADE, null=True)
......
2577 2609
    end_datetime = models.DateTimeField(_('Exception end time'))
2578 2610
    update_datetime = models.DateTimeField(auto_now=True)
2579 2611
    recurrence_id = models.PositiveIntegerField(_('Recurrence ID'), default=0)
2612
    group = models.ForeignKey(
2613
        TimePeriodExceptionGroup, on_delete=models.CASCADE, null=True, related_name='exceptions'
2614
    )
2580 2615

  
2581 2616
    @property
2582 2617
    def read_only(self):
tests/data/holidays.ics
1
BEGIN:VCALENDAR
2
VERSION:2.0
3
PRODID:-//PYVOBJECT//NONSGML Version 1//EN
4
BEGIN:VEVENT
5
UID:christmas_holidays-2017
6
DTSTART;VALUE=DATE:20171223
7
DTEND;VALUE=DATE:20180108
8
CATEGORIES:christmas_holidays
9
DTSTAMP:20220328T140507Z
10
SUMMARY:Vacances de Noël
11
END:VEVENT
12
BEGIN:VEVENT
13
UID:summer_holidays-2018
14
DTSTART;VALUE=DATE:20180707
15
DTEND;VALUE=DATE:20180903
16
CATEGORIES:summer_holidays
17
DTSTAMP:20220328T140507Z
18
SUMMARY:Vacances d’Été
19
END:VEVENT
20
BEGIN:VEVENT
21
UID:christmas_holidays-2018
22
DTSTART;VALUE=DATE:20181222
23
DTEND;VALUE=DATE:20190107
24
CATEGORIES:christmas_holidays
25
DTSTAMP:20220328T140507Z
26
SUMMARY:Vacances de Noël
27
END:VEVENT
28
BEGIN:VEVENT
29
UID:summer_holidays-2019
30
DTSTART;VALUE=DATE:20190706
31
DTEND;VALUE=DATE:20190902
32
CATEGORIES:summer_holidays
33
DTSTAMP:20220328T140507Z
34
SUMMARY:Vacances d’Été
35
END:VEVENT
36
BEGIN:VEVENT
37
UID:christmas_holidays-2019
38
DTSTART;VALUE=DATE:20191221
39
DTEND;VALUE=DATE:20200106
40
CATEGORIES:christmas_holidays
41
DTSTAMP:20220328T140507Z
42
SUMMARY:Vacances de Noël
43
END:VEVENT
44
BEGIN:VEVENT
45
UID:summer_holidays-2020
46
DTSTART;VALUE=DATE:20200704
47
DTEND;VALUE=DATE:20200901
48
CATEGORIES:summer_holidays
49
DTSTAMP:20220328T140507Z
50
SUMMARY:Vacances d’Été
51
END:VEVENT
52
BEGIN:VEVENT
53
UID:christmas_holidays-2020
54
DTSTART;VALUE=DATE:20201219
55
DTEND;VALUE=DATE:20210104
56
CATEGORIES:christmas_holidays
57
DTSTAMP:20220328T140507Z
58
SUMMARY:Vacances de Noël
59
END:VEVENT
60
BEGIN:VEVENT
61
UID:summer_holidays-2021
62
DTSTART;VALUE=DATE:20210706
63
DTEND;VALUE=DATE:20210902
64
CATEGORIES:summer_holidays
65
DTSTAMP:20220328T140507Z
66
SUMMARY:Vacances d’Été
67
END:VEVENT
68
BEGIN:VEVENT
69
UID:christmas_holidays-2021
70
DTSTART;VALUE=DATE:20211218
71
DTEND;VALUE=DATE:20220103
72
CATEGORIES:christmas_holidays
73
DTSTAMP:20220328T140507Z
74
SUMMARY:Vacances de Noël
75
END:VEVENT
76
BEGIN:VEVENT
77
UID:summer_holidays-2022
78
DTSTART;VALUE=DATE:20220707
79
DTEND;VALUE=DATE:20220901
80
CATEGORIES:summer_holidays
81
DTSTAMP:20220328T140507Z
82
SUMMARY:Vacances d’Été
83
END:VEVENT
84
BEGIN:VEVENT
85
UID:christmas_holidays-2022
86
DTSTART;VALUE=DATE:20221217
87
DTEND;VALUE=DATE:20230103
88
CATEGORIES:christmas_holidays
89
DTSTAMP:20220328T140507Z
90
SUMMARY:Vacances de Noël
91
END:VEVENT
92
END:VCALENDAR
tests/test_agendas.py
31 31
    SharedCustodyRule,
32 32
    TimePeriod,
33 33
    TimePeriodException,
34
    TimePeriodExceptionGroup,
34 35
    TimePeriodExceptionSource,
35 36
    UnavailabilityCalendar,
36 37
    VirtualMember,
......
119 120
    ICS_ATREAL = f.read()
120 121

  
121 122

  
123
with open('tests/data/holidays.ics') as f:
124
    ICS_HOLIDAYS = f.read()
125

  
126

  
122 127
def test_slug():
123 128
    agenda = Agenda(label='Foo bar')
124 129
    agenda.save()
......
854 859
    assert not TimePeriodExceptionSource.objects.exists()
855 860

  
856 861

  
862
def test_timeperiodexception_groups():
863
    unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
864
    source = unavailability_calendar.timeperiodexceptionsource_set.create(
865
        ics_filename='holidays.ics', ics_file=ContentFile(ICS_HOLIDAYS, name='holidays.ics')
866
    )
867
    source.refresh_timeperiod_exceptions_from_ics()
868
    assert TimePeriodException.objects.count() == 11
869
    group1, group2 = TimePeriodExceptionGroup.objects.all()
870
    assert group1.label == 'Vacances de Noël'
871
    assert group1.slug == 'christmas_holidays'
872
    assert group2.label == 'Vacances d’Été'
873
    assert group2.slug == 'summer_holidays'
874

  
875
    assert group1.exceptions.count() == 6
876
    assert group2.exceptions.count() == 5
877

  
878
    unavailability_calendar.delete()
879
    assert not TimePeriodException.objects.exists()
880
    assert not TimePeriodExceptionGroup.objects.exists()
881

  
882
    # check no groups are created for desks
883
    agenda = Agenda.objects.create(label='Test 1 agenda')
884
    desk = Desk.objects.create(label='Test 1 desk', agenda=agenda)
885
    source = desk.timeperiodexceptionsource_set.create(
886
        ics_filename='holidays.ics', ics_file=ContentFile(ICS_HOLIDAYS, name='holidays.ics')
887
    )
888
    source.refresh_timeperiod_exceptions_from_ics()
889
    assert TimePeriodException.objects.count() == 11
890
    assert not TimePeriodExceptionGroup.objects.exists()
891

  
892

  
857 893
def test_base_meeting_duration():
858 894
    agenda = Agenda(label='Meeting', kind='meetings')
859 895
    agenda.save()
860
-