From 78153e7e8c100b992b109c7eb32a6b3a7b9e5428 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Tue, 5 Oct 2021 14:36:29 +0200
Subject: [PATCH 1/2] agendas: factorize recurrent event update code (#57305)

---
 chrono/agendas/models.py | 25 +++++++++++++++++++++++++
 chrono/manager/forms.py  | 25 ++++++-------------------
 2 files changed, 31 insertions(+), 19 deletions(-)

diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 3a0016e..c989003 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -17,16 +17,17 @@
 import collections
 import copy
 import datetime
 import functools
 import itertools
 import math
 import sys
 import uuid
+from contextlib import contextmanager
 
 import requests
 import vobject
 from dateutil.rrule import DAILY, WEEKLY, rrule, rruleset
 from django.conf import settings
 from django.contrib.auth.models import Group
 from django.contrib.postgres.fields import ArrayField, JSONField
 from django.core.exceptions import FieldDoesNotExist, ValidationError
@@ -1404,16 +1405,40 @@ class Event(models.Model):
     def save(self, seen_slugs=None, *args, **kwargs):
         assert self.agenda.kind != 'virtual', "an event can't reference a virtual agenda"
         assert not (self.slug and self.slug.isdigit()), 'slug cannot be a number'
         self.start_datetime = self.start_datetime.replace(second=0, microsecond=0)
         if not self.slug:
             self.slug = generate_slug(self, seen_slugs=seen_slugs, agenda=self.agenda)
         return super().save(*args, **kwargs)
 
+    @contextmanager
+    def update_recurrences(
+        self, changed_data=None, cleaned_data=None, protected_fields=None, exclude_fields=None
+    ):
+        changed_data = changed_data or []
+        cleaned_data = cleaned_data or {}
+        protected_fields = protected_fields or []
+        exclude_fields = exclude_fields or []
+
+        with transaction.atomic():
+            if any(field for field in changed_data if field in protected_fields):
+                self.recurrences.all().delete()
+            elif self.recurrence_days:
+                update_fields = {
+                    field: value for field, value in cleaned_data.items() if field not in exclude_fields
+                }
+                self.recurrences.update(**update_fields)
+            yield
+
+            if self.recurrence_end_date:
+                self.recurrences.filter(start_datetime__gt=self.recurrence_end_date).delete()
+                excluded_datetimes = [evt.datetime_slug for evt in self.recurrences.all()]
+                self.create_all_recurrences(excluded_datetimes)
+
     @property
     def base_slug(self):
         # label can be empty
         return slugify(self.label or ('%s-event' % self.agenda.label))
 
     def main_list_full(self):
         return bool(self.booked_places >= self.places)
 
diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index f5b7afc..abdbb98 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -19,17 +19,16 @@ import collections
 import csv
 import datetime
 
 import django_filters
 from django import forms
 from django.conf import settings
 from django.contrib.auth.models import Group
 from django.core.exceptions import FieldDoesNotExist
-from django.db import transaction
 from django.forms import ValidationError
 from django.utils.encoding import force_text
 from django.utils.six import StringIO
 from django.utils.timezone import make_aware, now
 from django.utils.translation import ugettext_lazy as _
 
 from chrono.agendas.models import (
     WEEKDAYS_LIST,
@@ -275,35 +274,23 @@ class EventForm(NewEventForm):
         ):
             raise ValidationError(_('Bookings exist after this date.'))
 
         if self.cleaned_data.get('frequency') == 'unique':
             self.cleaned_data['recurrence_days'] = None
             self.cleaned_data['recurrence_end_date'] = None
 
     def save(self, *args, **kwargs):
-        with transaction.atomic():
-            if any(field for field in self.changed_data if field in self.protected_fields):
-                self.instance.recurrences.all().delete()
-            elif self.instance.recurrence_days:
-                protected_fields = list(self.protected_fields) + ['recurrence_end_date', 'frequency']
-                update_fields = {
-                    field: value
-                    for field, value in self.cleaned_data.items()
-                    if field not in protected_fields
-                }
-                self.instance.recurrences.update(**update_fields)
-
+        with self.instance.update_recurrences(
+            self.changed_data,
+            self.cleaned_data,
+            self.protected_fields,
+            list(self.protected_fields) + ['recurrence_end_date', 'frequency'],
+        ):
             super().save(*args, **kwargs)
-            if self.instance.recurrence_end_date:
-                self.instance.recurrences.filter(
-                    start_datetime__gt=self.instance.recurrence_end_date
-                ).delete()
-                excluded_datetimes = [event.datetime_slug for event in self.instance.recurrences.all()]
-                self.instance.create_all_recurrences(excluded_datetimes)
         return self.instance
 
 
 class BookingCheckFilterSet(django_filters.FilterSet):
     class Meta:
         model = Booking
         fields = []
 
-- 
2.30.2

