From 26a112e11433673f13953e553db43957e2033042 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 24 Nov 2020 11:46:03 +0100
Subject: [PATCH] agendas: fix import with duplicated exceptins (#48615)

---
 chrono/agendas/models.py    | 28 ++++++++++------------------
 tests/test_import_export.py | 22 +++++++++++++++++++++-
 2 files changed, 31 insertions(+), 19 deletions(-)

diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 911892d..3b49a19 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -614,8 +614,7 @@ class TimePeriod(models.Model):
     @classmethod
     def import_json(cls, data):
         data = clean_import_data(cls, data)
-        timeperiod, _ = cls.objects.update_or_create(defaults=data, **data)
-        return timeperiod
+        cls.objects.update_or_create(defaults=data, **data)
 
     def export_json(self):
         return {
@@ -786,10 +785,7 @@ class MeetingType(models.Model):
     @classmethod
     def import_json(cls, data):
         data = clean_import_data(cls, data)
-        meeting_type, created = cls.objects.update_or_create(
-            slug=data['slug'], agenda=data['agenda'], defaults=data
-        )
-        return meeting_type
+        cls.objects.update_or_create(slug=data['slug'], agenda=data['agenda'], defaults=data)
 
     def export_json(self):
         return {
@@ -977,11 +973,10 @@ class Event(models.Model):
         )
         data = clean_import_data(cls, data)
         if data.get('slug'):
-            event, created = cls.objects.update_or_create(slug=data['slug'], defaults=data)
-            return event
+            cls.objects.update_or_create(slug=data['slug'], defaults=data)
+            return
         event = cls(**data)
         event.save()
-        return event
 
     def export_json(self):
         return {
@@ -1171,8 +1166,6 @@ class Desk(models.Model):
                 raise AgendaImportError(_('The unavailability calendar "%s" does not exist.') % slug)
             desk.unavailability_calendars.add(target_calendar)
 
-        return desk
-
     def export_json(self):
         time_period_exceptions = self.timeperiodexception_set.filter(source__settings_slug__isnull=True)
         time_period_exception_sources = self.timeperiodexceptionsource_set.filter(settings_slug__isnull=False)
@@ -1514,7 +1507,6 @@ class TimePeriodExceptionSource(models.Model):
         source, _ = cls.objects.update_or_create(**data)
         if source.enabled:
             source.enable()
-        return source
 
     def export_json(self):
         '''Export only sources from settings.'''
@@ -1695,8 +1687,10 @@ class TimePeriodException(models.Model):
         data = clean_import_data(cls, data)
         query_data = data.copy()
         query_data.pop('update_datetime')
-        exception, _ = cls.objects.update_or_create(defaults=data, **query_data)
-        return exception
+        try:
+            cls.objects.update_or_create(defaults=data, **query_data)
+        except cls.MultipleObjectsReturned:
+            cls.objects.filter(**query_data).update(update_datetime=data['update_datetime'])
 
     def export_json(self):
         def export_datetime(dt):
@@ -1847,8 +1841,7 @@ class AgendaNotificationsSettings(models.Model):
     def import_json(cls, data):
         data = clean_import_data(cls, data)
         agenda = data.pop('agenda')
-        notification_settings, created = cls.objects.update_or_create(agenda=agenda, defaults=data)
-        return notification_settings
+        cls.objects.update_or_create(agenda=agenda, defaults=data)
 
     def export_json(self):
         return {
@@ -1908,8 +1901,7 @@ class AgendaReminderSettings(models.Model):
     def import_json(cls, data):
         data = clean_import_data(cls, data)
         agenda = data.pop('agenda')
-        reminder_settings, created = cls.objects.update_or_create(agenda=agenda, defaults=data)
-        return reminder_settings
+        cls.objects.update_or_create(agenda=agenda, defaults=data)
 
     def export_json(self):
         return {
diff --git a/tests/test_import_export.py b/tests/test_import_export.py
index c07b3c6..585fbd4 100644
--- a/tests/test_import_export.py
+++ b/tests/test_import_export.py
@@ -578,7 +578,7 @@ def test_import_export_do_not_duplicate_timeperiod_and_exceptions():
     TimePeriod.objects.create(
         desk=desk, weekday=3, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0)
     )
-    TimePeriodException.objects.create(
+    tpe1 = TimePeriodException.objects.create(
         desk=desk,
         start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)),
         end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)),
@@ -597,6 +597,26 @@ def test_import_export_do_not_duplicate_timeperiod_and_exceptions():
     assert TimePeriod.objects.count() == 2
     assert TimePeriodException.objects.count() == 2
 
+    # duplicated exception
+    output = get_output_of_command('export_site')
+    payload = json.loads(output)
+    assert len(payload['agendas'][0]['desks'][0]['exceptions']) == 2
+    tpe2 = TimePeriodException.objects.create(
+        desk=desk,
+        start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)),
+        end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)),
+    )
+    assert TimePeriod.objects.count() == 2
+    assert TimePeriodException.objects.count() == 3
+    assert tpe1.update_datetime < tpe2.update_datetime
+
+    import_site(payload)
+    assert TimePeriod.objects.count() == 2
+    assert TimePeriodException.objects.count() == 3
+    tpe1.refresh_from_db()
+    tpe2.refresh_from_db()
+    assert tpe1.update_datetime == tpe2.update_datetime
+
 
 def test_import_export_unavailability_calendar(app):
     output = get_output_of_command('export_site')
-- 
2.28.0

