From 3eeeb42d3229e0a13a07307e6acf3a079470cae2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 10 Aug 2021 14:58:31 +0200
Subject: [PATCH 2/2] manager: option to apply exceptions to all desks (#55826)

---
 chrono/manager/forms.py         |  8 ++++
 chrono/manager/views.py         |  3 +-
 tests/manager/test_exception.py | 73 +++++++++++++++++++++++++++++++++
 3 files changed, 83 insertions(+), 1 deletion(-)

diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index cf10611..4a419b0 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -762,11 +762,19 @@ class ExceptionsImportForm(forms.ModelForm):
         required=False,
         help_text=_('URL to remote calendar which will be synchronised hourly.'),
     )
+    all_desks = forms.BooleanField(label=_('Apply exceptions on all desks of the agenda'), required=False)
 
     class Meta:
         model = Desk
         fields = []
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        if self.instance.agenda.desk_set.count() == 1:
+            del self.fields['all_desks']
+        elif self.instance.agenda.desk_simple_management:
+            del self.fields['all_desks']
+
     def clean(self, *args, **kwargs):
         cleaned_data = super().clean(*args, **kwargs)
         if not cleaned_data.get('ics_file') and not cleaned_data.get('ics_url'):
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index 8fcf3bc..9c104e5 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -2634,9 +2634,10 @@ class DeskImportTimePeriodExceptionsView(ManagedAgendaSubobjectMixin, UpdateView
     def form_valid(self, form):
         desk = self.get_object()
         sources = []
+        all_desks = form.cleaned_data.get('all_desks')
         try:
             with transaction.atomic():
-                if desk.agenda.desk_simple_management:
+                if all_desks or desk.agenda.desk_simple_management:
                     for _desk in desk.agenda.desk_set.all():
                         sources.append(self.import_file(_desk, form))
                 else:
diff --git a/tests/manager/test_exception.py b/tests/manager/test_exception.py
index dfed674..7432aff 100644
--- a/tests/manager/test_exception.py
+++ b/tests/manager/test_exception.py
@@ -781,6 +781,77 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mock
     assert 'Failed to retrieve remote calendar (https://example.com/foo.ics, SSL error).' in resp.text
 
 
+@mock.patch('chrono.agendas.models.requests.get')
+def test_agenda_import_time_period_exception_url_desk_all_desks(mocked_get, app, admin_user):
+    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
+    desk = Desk.objects.create(agenda=agenda, label='Desk A')
+
+    # only one desk: no option to apply to all desks
+    app = login(app)
+    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
+    assert 'all_desks' not in resp.form.fields
+    # more than one desk
+    Desk.objects.create(agenda=agenda, label='Desk B')
+    agenda2 = Agenda.objects.create(label='Foo bar', kind='meetings')
+    Desk.objects.create(agenda=agenda2, label='Other Desk')  # check exceptions are not created for this one
+    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
+    resp.form['all_desks'] = True
+    resp.form['ics_url'] = 'http://example.com/foo.ics'
+    mocked_response = mock.Mock()
+    mocked_response.text = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//foo.bar//EN
+BEGIN:VEVENT
+DTSTART:20180101
+DTEND:20180101
+SUMMARY:New Year's Eve
+END:VEVENT
+END:VCALENDAR"""
+    mocked_get.return_value = mocked_response
+    resp = resp.form.submit(status=302)
+    assert TimePeriodException.objects.count() == 2
+    assert TimePeriodException.objects.filter(desk__slug='desk-a').exists()
+    assert TimePeriodException.objects.filter(desk__slug='desk-b').exists()
+    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
+    desk = Desk.objects.create(agenda=agenda, label='New Desk')
+    desk.duplicate()
+    assert agenda.is_available_for_simple_management() is True
+
+
+def test_agenda_import_time_period_exception_file_desk_all_desks(app, admin_user):
+    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
+    desk = Desk.objects.create(agenda=agenda, label='Desk A')
+
+    # only one desk: no option to apply to all desks
+    app = login(app)
+    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
+    assert 'all_desks' not in resp.form.fields
+    # more than one desk
+    Desk.objects.create(agenda=agenda, label='Desk B')
+    agenda2 = Agenda.objects.create(label='Foo bar', kind='meetings')
+    Desk.objects.create(agenda=agenda2, label='Other Desk')  # check exceptions are not created for this one
+    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
+    resp.form['all_desks'] = True
+    ics_exceptions = b"""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//foo.bar//EN
+BEGIN:VEVENT
+DTSTART:20180101
+DTEND:20180101
+SUMMARY:New Year's Eve
+END:VEVENT
+END:VCALENDAR"""
+    resp.form['ics_file'] = Upload('exceptions.ics', ics_exceptions, 'text/calendar')
+    resp = resp.form.submit(status=302)
+    assert TimePeriodException.objects.count() == 2
+    assert TimePeriodException.objects.filter(desk__slug='desk-a').exists()
+    assert TimePeriodException.objects.filter(desk__slug='desk-b').exists()
+    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
+    desk = Desk.objects.create(agenda=agenda, label='New Desk')
+    desk.duplicate()
+    assert agenda.is_available_for_simple_management() is True
+
+
 @mock.patch('chrono.agendas.models.requests.get')
 def test_agenda_import_time_period_exception_url_desk_simple_management(mocked_get, app, admin_user):
     agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
@@ -790,6 +861,7 @@ def test_agenda_import_time_period_exception_url_desk_simple_management(mocked_g
 
     login(app)
     resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
+    assert 'all_desks' not in resp.form.fields
     resp.form['ics_url'] = 'http://example.com/foo.ics'
     mocked_response = mock.Mock()
     mocked_response.text = """BEGIN:VCALENDAR
@@ -815,6 +887,7 @@ def test_agenda_import_time_period_exception_file_desk_simple_management(app, ad
 
     login(app)
     resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
+    assert 'all_desks' not in resp.form.fields
     ics_exceptions = b"""BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//foo.bar//EN
-- 
2.30.2

