From d8fdee635a0e9d5d62669894278194e37737e05e Mon Sep 17 00:00:00 2001
From: Valentin Deniaud <vdeniaud@entrouvert.com>
Date: Thu, 13 Oct 2022 15:49:11 +0200
Subject: [PATCH 3/7] agendas: add date field to time period (#70185)

---
 .../0140_add_timeperiod_date_field.py         | 50 +++++++++++++++++++
 chrono/agendas/models.py                      | 13 ++++-
 tests/test_import_export.py                   |  6 ++-
 tests/test_time_periods.py                    | 29 +++++++++++
 4 files changed, 95 insertions(+), 3 deletions(-)
 create mode 100644 chrono/agendas/migrations/0140_add_timeperiod_date_field.py

diff --git a/chrono/agendas/migrations/0140_add_timeperiod_date_field.py b/chrono/agendas/migrations/0140_add_timeperiod_date_field.py
new file mode 100644
index 00000000..a4c42862
--- /dev/null
+++ b/chrono/agendas/migrations/0140_add_timeperiod_date_field.py
@@ -0,0 +1,50 @@
+# Generated by Django 2.2.26 on 2022-10-13 13:46
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('agendas', '0139_auto_20220726_1017'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='timeperiod',
+            options={'ordering': ['weekday', 'date', 'start_time']},
+        ),
+        migrations.AddField(
+            model_name='timeperiod',
+            name='date',
+            field=models.DateField(null=True, verbose_name='Date'),
+        ),
+        migrations.AlterField(
+            model_name='timeperiod',
+            name='weekday',
+            field=models.IntegerField(
+                choices=[
+                    (0, 'Monday'),
+                    (1, 'Tuesday'),
+                    (2, 'Wednesday'),
+                    (3, 'Thursday'),
+                    (4, 'Friday'),
+                    (5, 'Saturday'),
+                    (6, 'Sunday'),
+                ],
+                null=True,
+                verbose_name='Week day',
+            ),
+        ),
+        migrations.AddConstraint(
+            model_name='timeperiod',
+            constraint=models.CheckConstraint(
+                check=models.Q(
+                    models.Q(('date__isnull', True), ('weekday__isnull', False)),
+                    models.Q(('date__isnull', False), ('weekday__isnull', True)),
+                    _connector='OR',
+                ),
+                name='date_xor_weekday',
+            ),
+        ),
+    ]
diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 2ece19a3..c56930f7 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -1103,13 +1103,14 @@ WEEK_CHOICES = [
 
 
 class TimePeriod(models.Model):
-    weekday = models.IntegerField(_('Week day'), choices=WEEKDAYS_LIST)
+    weekday = models.IntegerField(_('Week day'), choices=WEEKDAYS_LIST, null=True)
     weekday_indexes = ArrayField(
         models.IntegerField(choices=WEEK_CHOICES),
         verbose_name=_('Repeat'),
         blank=True,
         null=True,
     )
+    date = models.DateField(_('Date'), null=True)
     start_time = models.TimeField(_('Start'))
     end_time = models.TimeField(_('End'))
     desk = models.ForeignKey('Desk', on_delete=models.CASCADE, null=True)
@@ -1118,7 +1119,14 @@ class TimePeriod(models.Model):
     )
 
     class Meta:
-        ordering = ['weekday', 'start_time']
+        ordering = ['weekday', 'date', 'start_time']
+        constraints = [
+            models.CheckConstraint(
+                check=Q(date__isnull=True, weekday__isnull=False)
+                | Q(date__isnull=False, weekday__isnull=True),
+                name='date_xor_weekday',
+            )
+        ]
 
     def __str__(self):
         label = force_str(WEEKDAYS[self.weekday])
@@ -1153,6 +1161,7 @@ class TimePeriod(models.Model):
         return {
             'weekday': self.weekday,
             'weekday_indexes': self.weekday_indexes,
+            'date': self.date.strftime('%Y-%m-%d') if self.date else None,
             'start_time': self.start_time.strftime('%H:%M'),
             'end_time': self.end_time.strftime('%H:%M'),
         }
diff --git a/tests/test_import_export.py b/tests/test_import_export.py
index 92e50314..9f86d258 100644
--- a/tests/test_import_export.py
+++ b/tests/test_import_export.py
@@ -63,6 +63,9 @@ def test_import_export(app):
     TimePeriod.objects.create(
         desk=desk, weekday=2, start_time=tpx_start.time(), end_time=tpx_end.time(), weekday_indexes=[1, 3]
     )
+    TimePeriod.objects.create(
+        desk=desk, date=datetime.date(2022, 10, 24), start_time=tpx_start.time(), end_time=tpx_end.time()
+    )
 
     output = get_output_of_command('export_site')
     assert len(json.loads(output)['agendas']) == 2
@@ -92,7 +95,8 @@ def test_import_export(app):
     assert TimePeriodException.objects.get(desk__agenda__kind='meetings').end_datetime == tpx_end
     assert TimePeriodException.objects.get(desk__agenda__kind='events').start_datetime == tpx_start
     assert TimePeriodException.objects.get(desk__agenda__kind='events').end_datetime == tpx_end
-    assert TimePeriod.objects.get().weekday_indexes == [1, 3]
+    assert TimePeriod.objects.filter(weekday_indexes=[1, 3]).count() == 1
+    assert TimePeriod.objects.filter(date=datetime.date(2022, 10, 24)).count() == 1
 
     agenda1 = Agenda.objects.get(label='Events Agenda')
     agenda2 = Agenda.objects.get(label='Meetings Agenda')
diff --git a/tests/test_time_periods.py b/tests/test_time_periods.py
index 50748e8c..0cf542d0 100644
--- a/tests/test_time_periods.py
+++ b/tests/test_time_periods.py
@@ -1,6 +1,7 @@
 import datetime
 
 import pytest
+from django.db import IntegrityError, transaction
 from django.db.models import Q
 from django.test import override_settings
 from django.utils.encoding import force_str
@@ -391,3 +392,31 @@ def test_timeperiod_weekday_indexes():
     events = get_events(datetime.datetime(2022, 5, 1), datetime.datetime(2022, 6, 1))
     assert events[0].timetuple()[:5] == (2022, 5, 30, 22, 0)
     assert len(events) == 1
+
+
+def test_time_period_check_constraint():
+    TimePeriod.objects.create(
+        weekday=0, start_time=datetime.time(hour=1, minute=0), end_time=datetime.time(hour=2, minute=0)
+    )
+    TimePeriod.objects.create(
+        date=datetime.date.today(),
+        start_time=datetime.time(hour=1, minute=0),
+        end_time=datetime.time(hour=2, minute=0),
+    )
+
+    # missing weekday or date
+    with pytest.raises(IntegrityError):
+        with transaction.atomic():
+            TimePeriod.objects.create(
+                start_time=datetime.time(hour=1, minute=0), end_time=datetime.time(hour=2, minute=0)
+            )
+
+    # both weekday and date
+    with pytest.raises(IntegrityError):
+        with transaction.atomic():
+            TimePeriod.objects.create(
+                weekday=0,
+                date=datetime.date.today(),
+                start_time=datetime.time(hour=1, minute=0),
+                end_time=datetime.time(hour=2, minute=0),
+            )
-- 
2.35.1

