Projet

Général

Profil

0010-manager-simple-desk-management-sources-48924.patch

Lauréline Guérin, 05 février 2021 10:19

Télécharger (46,5 ko)

Voir les différences:

Subject: [PATCH 10/11] manager: simple desk management & sources (#48924)

 chrono/manager/forms.py |  22 +-
 chrono/manager/views.py | 137 ++++++++---
 tests/test_manager.py   | 499 +++++++++++++++++++++++++++++-----------
 3 files changed, 492 insertions(+), 166 deletions(-)
chrono/manager/forms.py
636 636
        fields = []
637 637

  
638 638
    def save(self, *args, **kwargs):
639
        if bool(self.instance.ics_file):
640
            self.instance.ics_file.delete()
641
        self.instance.ics_file = self.cleaned_data['ics_newfile']
642
        self.instance.ics_filename = self.cleaned_data['ics_newfile'].name
643
        self.instance.save()
639
        def store_source(source):
640
            if bool(source.ics_file):
641
                source.ics_file.delete()
642
            source.ics_file = self.cleaned_data['ics_newfile']
643
            source.ics_filename = self.cleaned_data['ics_newfile'].name
644
            source.save()
645
            self.cleaned_data['ics_newfile'].seek(0)
646

  
647
        old_filename = self.instance.ics_filename
648
        store_source(self.instance)
649
        if self.instance.desk.agenda.desk_simple_management:
650
            for desk in self.instance.desk.agenda.desk_set.exclude(pk=self.instance.desk_id):
651
                source = desk.timeperiodexceptionsource_set.filter(ics_filename=old_filename).first()
652
                if source is not None:
653
                    store_source(source)
654

  
655
        return self.instance
644 656

  
645 657

  
646 658
class AgendasImportForm(forms.Form):
chrono/manager/views.py
1765 1765

  
1766 1766
    def get(self, request, *args, **kwargs):
1767 1767
        unavailability_calendar = self.get_object()
1768
        activate = False
1768
        enabled = False
1769 1769
        try:
1770 1770
            self.desk.unavailability_calendars.get(pk=unavailability_calendar.pk)
1771 1771
            self.desk.unavailability_calendars.remove(unavailability_calendar)
1772 1772
            message = _(
1773 1773
                'Unavailability calendar %(unavailability_calendar)s has been disabled on desk %(desk)s.'
1774 1774
            )
1775

  
1776 1775
        except UnavailabilityCalendar.DoesNotExist:
1777
            activate = True
1776
            enabled = True
1778 1777
            self.desk.unavailability_calendars.add(unavailability_calendar)
1779 1778
            message = _(
1780 1779
                'Unavailability calendar %(unavailability_calendar)s has been enabled on desk %(desk)s.'
1781 1780
            )
1781

  
1782
        if self.desk.agenda.desk_simple_management:
1783
            for desk in self.desk.agenda.desk_set.exclude(pk=self.desk.pk):
1784
                if enabled:
1785
                    desk.unavailability_calendars.add(unavailability_calendar)
1786
                    message = _('Unavailability calendar %(unavailability_calendar)s has been enabled.')
1787
                else:
1788
                    desk.unavailability_calendars.remove(unavailability_calendar)
1789
                    message = _('Unavailability calendar %(unavailability_calendar)s has been disabled.')
1790

  
1782 1791
        messages.info(
1783 1792
            self.request, message % {'unavailability_calendar': unavailability_calendar, 'desk': self.desk}
1784 1793
        )
1785
        if activate:
1794
        if enabled:
1786 1795
            for exception in unavailability_calendar.timeperiodexception_set.all():
1787
                if exception.has_booking_within_time_slot(target_desk=self.desk):
1788
                    message = _('One or several bookings overlap with exceptions.')
1789
                    messages.warning(self.request, message)
1790
                    break
1796
                desks = [self.desk]
1797
                if self.desk.agenda.desk_simple_management:
1798
                    desks = self.desk.agenda.desk_set.all()
1799
                for desk in desks:
1800
                    if exception.has_booking_within_time_slot(target_desk=desk):
1801
                        message = _('One or several bookings overlap with exceptions.')
1802
                        messages.warning(self.request, message)
1803
                        break
1791 1804

  
1792 1805
        return HttpResponseRedirect(
1793 1806
            reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda_id})
......
2197 2210
        )
2198 2211
        return context
2199 2212

  
2213
    def import_file(self, desk, form):
2214
        if form.cleaned_data['ics_file']:
2215
            exceptions = desk.import_timeperiod_exceptions_from_ics_file(form.cleaned_data['ics_file'])
2216
            form.cleaned_data['ics_file'].seek(0)
2217
            return exceptions
2218
        elif form.cleaned_data['ics_url']:
2219
            return desk.import_timeperiod_exceptions_from_remote_ics(form.cleaned_data['ics_url'])
2220

  
2200 2221
    def form_valid(self, form):
2201 2222
        exceptions = None
2223
        desk = self.get_object()
2202 2224
        try:
2203
            if form.cleaned_data['ics_file']:
2204
                exceptions = form.instance.import_timeperiod_exceptions_from_ics_file(
2205
                    form.cleaned_data['ics_file']
2206
                )
2207
            elif form.cleaned_data['ics_url']:
2208
                exceptions = form.instance.import_timeperiod_exceptions_from_remote_ics(
2209
                    form.cleaned_data['ics_url']
2210
                )
2225
            if desk.agenda.desk_simple_management:
2226
                for _desk in desk.agenda.desk_set.all():
2227
                    result = self.import_file(_desk, form)
2228
                    exceptions = result if exceptions is None else exceptions
2229
            else:
2230
                exceptions = self.import_file(desk, form)
2211 2231
        except ICSError as e:
2212 2232
            form.add_error(None, force_text(e))
2213 2233
            return self.form_invalid(form)
......
2231 2251
    def get_success_url(self):
2232 2252
        return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda_id})
2233 2253

  
2254
    def delete(self, request, *args, **kwargs):
2255
        source = self.get_object()
2256
        response = super().delete(request, *args, **kwargs)
2257

  
2258
        if not source.desk.agenda.desk_simple_management:
2259
            return response
2260

  
2261
        for desk in source.desk.agenda.desk_set.exclude(pk=source.desk_id):
2262
            if source.ics_filename:
2263
                queryset = desk.timeperiodexceptionsource_set.filter(ics_filename=source.ics_filename)
2264
            else:
2265
                queryset = desk.timeperiodexceptionsource_set.filter(ics_url=source.ics_url)
2266
            _source = queryset.first()
2267
            if _source is not None:
2268
                _source.delete()
2269

  
2270
        return response
2271

  
2234 2272

  
2235 2273
time_period_exception_source_delete = TimePeriodExceptionSourceDeleteView.as_view()
2236 2274

  
......
2244 2282
        queryset = super(TimePeriodExceptionSourceReplaceView, self).get_queryset()
2245 2283
        return queryset.filter(ics_filename__isnull=False)
2246 2284

  
2285
    def import_file(self, desk, form):
2286
        exceptions = None
2287
        source = desk.timeperiodexceptionsource_set.filter(
2288
            ics_filename=self.get_object().ics_filename
2289
        ).first()
2290
        if source is not None:
2291
            exceptions = desk.import_timeperiod_exceptions_from_ics_file(
2292
                form.cleaned_data['ics_newfile'], source=source
2293
            )
2294
            form.cleaned_data['ics_newfile'].seek(0)
2295
        return exceptions
2296

  
2247 2297
    def form_valid(self, form):
2248 2298
        exceptions = None
2299
        desk = self.get_object().desk
2249 2300
        try:
2250
            exceptions = form.instance.desk.import_timeperiod_exceptions_from_ics_file(
2251
                form.cleaned_data['ics_newfile'], source=form.instance
2252
            )
2301
            if desk.agenda.desk_simple_management:
2302
                for _desk in desk.agenda.desk_set.all():
2303
                    result = self.import_file(_desk, form)
2304
                    exceptions = result if exceptions is None else exceptions
2305
            else:
2306
                exceptions = self.import_file(desk, form)
2253 2307
        except ICSError as e:
2254 2308
            form.add_error(None, force_text(e))
2255 2309
            return self.form_invalid(form)
......
2273 2327
        queryset = super(TimePeriodExceptionSourceRefreshView, self).get_queryset()
2274 2328
        return queryset.filter(ics_url__isnull=False)
2275 2329

  
2330
    def import_file(self, desk):
2331
        exceptions = None
2332
        source = desk.timeperiodexceptionsource_set.filter(ics_url=self.get_object().ics_url).first()
2333
        if source is not None:
2334
            exceptions = desk.import_timeperiod_exceptions_from_remote_ics(source.ics_url, source=source)
2335
        return exceptions
2336

  
2276 2337
    def get(self, request, *args, **kwargs):
2338
        exceptions = None
2339
        desk = self.get_object().desk
2277 2340
        try:
2278
            source = self.get_object()
2279
            exceptions = source.desk.import_timeperiod_exceptions_from_remote_ics(
2280
                source.ics_url, source=source
2281
            )
2341
            if desk.agenda.desk_simple_management:
2342
                for _desk in desk.agenda.desk_set.all():
2343
                    result = self.import_file(_desk)
2344
                    exceptions = result if exceptions is None else exceptions
2345
            else:
2346
                exceptions = self.import_file(desk)
2282 2347
        except ICSError as e:
2283 2348
            messages.error(self.request, force_text(e))
2284
        else:
2349

  
2350
        if exceptions is not None:
2285 2351
            message = ungettext(
2286 2352
                'An exception has been imported.', '%(count)d exceptions have been imported.', exceptions
2287 2353
            )
2288 2354
            message = message % {'count': exceptions}
2289 2355
            messages.info(self.request, message)
2356

  
2290 2357
        # redirect to settings
2291
        return HttpResponseRedirect(
2292
            reverse('chrono-manager-agenda-settings', kwargs={'pk': source.desk.agenda_id})
2293
        )
2358
        return HttpResponseRedirect(reverse('chrono-manager-agenda-settings', kwargs={'pk': desk.agenda_id}))
2294 2359

  
2295 2360

  
2296 2361
time_period_exception_source_refresh = TimePeriodExceptionSourceRefreshView.as_view()
......
2424 2489

  
2425 2490
    def get(self, request, *args, **kwargs):
2426 2491
        source = self.get_object()
2492

  
2427 2493
        if source.enabled:
2428 2494
            source.disable()
2495
            was_enabled = False
2429 2496
            message = _('Exception source %(source)s has been disabled on desk %(desk)s.')
2430 2497
        else:
2431 2498
            source.enable()
2499
            was_enabled = True
2432 2500
            message = _('Exception source %(source)s has been enabled on desk %(desk)s.')
2501

  
2502
        if self.desk.agenda.desk_simple_management:
2503
            for desk in self.desk.agenda.desk_set.exclude(pk=self.desk.pk):
2504
                _source = desk.timeperiodexceptionsource_set.filter(
2505
                    settings_slug=source.settings_slug
2506
                ).first()
2507
                if _source is None:
2508
                    continue
2509
                if was_enabled:
2510
                    _source.enable()
2511
                    message = _('Exception source %(source)s has been enabled.')
2512
                else:
2513
                    _source.disable()
2514
                    message = _('Exception source %(source)s has been disabled.')
2515

  
2433 2516
        messages.info(self.request, message % {'source': source, 'desk': source.desk})
2434 2517
        return HttpResponseRedirect(
2435 2518
            reverse('chrono-manager-agenda-settings', kwargs={'pk': source.desk.agenda_id})
tests/test_manager.py
9 9
import os
10 10

  
11 11
from django.contrib.auth.models import User, Group
12
from django.core.files.base import ContentFile
12 13
from django.core.management import call_command
13 14
from django.db import connection
14 15
from django.test import override_settings
......
2879 2880
def test_agenda_import_time_period_exception_from_ics(app, admin_user):
2880 2881
    agenda = Agenda.objects.create(label='Example', kind='meetings')
2881 2882
    desk = Desk.objects.create(agenda=agenda, label='Test Desk')
2882
    MeetingType.objects.create(agenda=agenda, label='Foo')
2883
    desk.duplicate()
2883 2884
    login(app)
2884 2885
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
2885 2886
    assert 'Manage exception sources' in resp.text
2886
    resp = resp.click('manage exceptions')
2887
    resp = resp.click('manage exceptions', index=0)
2887 2888
    assert "To add new exceptions, you can upload a file or specify an address to a remote calendar." in resp
2888 2889
    resp = resp.form.submit(status=200)
2889 2890
    assert 'Please provide an ICS File or an URL.' in resp.text
2890
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
2891
    resp = resp.click('manage exceptions')
2892 2891
    resp.form['ics_file'] = Upload('exceptions.ics', b'invalid content', 'text/calendar')
2893 2892
    resp = resp.form.submit(status=200)
2894 2893
    assert 'File format is invalid' in resp.text
......
2920 2919
SUMMARY:New Year's Eve
2921 2920
END:VEVENT
2922 2921
END:VCALENDAR"""
2923
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
2924
    resp = resp.click('manage exceptions')
2922
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
2925 2923
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_exceptions, 'text/calendar')
2926 2924
    resp = resp.form.submit(status=302)
2927 2925
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
2928 2926
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1
2929
    source = TimePeriodExceptionSource.objects.latest('pk')
2930
    exception = TimePeriodException.objects.latest('pk')
2927
    source = desk.timeperiodexceptionsource_set.get()
2928
    exception = desk.timeperiodexception_set.get()
2931 2929
    assert exception.source == source
2932 2930
    assert source.ics_filename == 'exceptions.ics'
2933 2931
    assert 'exceptions.ics' in source.ics_file.name
......
2966 2964
def test_agenda_import_time_period_exception_with_remote_ics(mocked_get, app, admin_user):
2967 2965
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
2968 2966
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
2969
    MeetingType.objects.create(agenda=agenda, label='Bar')
2967
    desk.duplicate()
2970 2968
    login(app)
2971
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
2972
    resp = resp.click('manage exceptions')
2969
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
2973 2970

  
2974 2971
    assert 'ics_file' in resp.form.fields
2975 2972
    assert 'ics_url' in resp.form.fields
......
2987 2984
    mocked_get.return_value = mocked_response
2988 2985
    resp = resp.form.submit(status=302)
2989 2986
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
2990
    exception = TimePeriodException.objects.get(desk=desk)
2991 2987
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1
2992
    source = TimePeriodExceptionSource.objects.latest('pk')
2993
    exception = TimePeriodException.objects.latest('pk')
2988
    source = desk.timeperiodexceptionsource_set.get()
2989
    exception = desk.timeperiodexception_set.get()
2994 2990
    assert exception.source == source
2995 2991
    assert source.ics_filename is None
2996 2992
    assert source.ics_file.name == ''
......
3026 3022
    mocked_get, app, admin_user
3027 3023
):
3028 3024
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
3029
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
3025
    Desk.objects.create(agenda=agenda, label='New Desk')
3030 3026
    MeetingType.objects.create(agenda=agenda, label='Bar')
3031 3027
    login(app)
3032 3028
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
......
3049 3045
@mock.patch('chrono.agendas.models.requests.get')
3050 3046
def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_get, app, admin_user):
3051 3047
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
3052
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
3048
    Desk.objects.create(agenda=agenda, label='New Desk')
3053 3049
    MeetingType.objects.create(agenda=agenda, label='Bar')
3054 3050
    login(app)
3055 3051
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
......
3070 3066
@mock.patch('chrono.agendas.models.requests.get')
3071 3067
def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mocked_get, app, admin_user):
3072 3068
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
3073
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
3069
    Desk.objects.create(agenda=agenda, label='New Desk')
3074 3070
    MeetingType.objects.create(agenda=agenda, label='Bar')
3075 3071
    login(app)
3076 3072
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
......
3087 3083
    assert 'Failed to retrieve remote calendar (https://example.com/foo.ics, SSL error).' in resp.text
3088 3084

  
3089 3085

  
3090
def test_meetings_agenda_delete_time_period_exception_source(app, admin_user, freezer):
3091
    freezer.move_to('2019-12-01')
3092
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
3093
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
3094
    MeetingType(agenda=agenda, label='Blah').save()
3095
    TimePeriod.objects.create(
3096
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
3097
    )
3086
@mock.patch('chrono.agendas.models.requests.get')
3087
def test_agenda_import_time_period_exception_url_desk_simple_management(mocked_get, app, admin_user):
3088
    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
3089
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
3090
    desk.duplicate()
3091
    assert agenda.is_available_for_simple_management() is True
3092

  
3098 3093
    login(app)
3099
    # import a source
3100
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
3101
    resp = resp.click('manage exceptions')
3102
    ics_with_recurrent_exceptions = b"""BEGIN:VCALENDAR
3094
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
3095
    resp.form['ics_url'] = 'http://example.com/foo.ics'
3096
    mocked_response = mock.Mock()
3097
    mocked_response.text = """BEGIN:VCALENDAR
3103 3098
VERSION:2.0
3104 3099
PRODID:-//foo.bar//EN
3105 3100
BEGIN:VEVENT
3106 3101
DTSTART:20180101
3107 3102
DTEND:20180101
3108 3103
SUMMARY:New Year's Eve
3109
RRULE:FREQ=YEARLY
3110 3104
END:VEVENT
3111 3105
END:VCALENDAR"""
3112
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_recurrent_exceptions, 'text/calendar')
3113
    resp = resp.form.submit(status=302).follow()
3114
    assert TimePeriodException.objects.filter(desk=desk).count() == 2
3115
    source1 = TimePeriodExceptionSource.objects.latest('pk')
3116
    assert source1.timeperiodexception_set.count() == 2
3106
    mocked_get.return_value = mocked_response
3107
    resp = resp.form.submit(status=302)
3108
    assert TimePeriodException.objects.count() == 2
3109
    assert agenda.is_available_for_simple_management() is True
3117 3110

  
3118
    # import another one
3119
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
3120
    resp = resp.click('manage exceptions')
3121
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_recurrent_exceptions, 'text/calendar')
3122
    resp = resp.form.submit(status=302).follow()
3123
    assert TimePeriodException.objects.filter(desk=desk).count() == 4
3124
    source2 = TimePeriodExceptionSource.objects.latest('pk')
3125
    assert source2.timeperiodexception_set.count() == 2
3126 3111

  
3127
    # delete the second one
3128
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
3129
    resp = resp.click('manage exceptions')
3130
    resp = resp.click(href='/manage/time-period-exceptions-source/%d/delete' % source2.pk)
3131
    resp = resp.form.submit().follow()
3112
def test_agenda_import_time_period_exception_file_desk_simple_management(app, admin_user):
3113
    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
3114
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
3115
    desk.duplicate()
3116
    assert agenda.is_available_for_simple_management() is True
3117

  
3118
    login(app)
3119
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
3120
    ics_exceptions = b"""BEGIN:VCALENDAR
3121
VERSION:2.0
3122
PRODID:-//foo.bar//EN
3123
BEGIN:VEVENT
3124
DTSTART:20180101
3125
DTEND:20180101
3126
SUMMARY:New Year's Eve
3127
END:VEVENT
3128
END:VCALENDAR"""
3129
    resp.form['ics_file'] = Upload('exceptions.ics', ics_exceptions, 'text/calendar')
3130
    resp = resp.form.submit(status=302)
3132 3131
    assert TimePeriodException.objects.count() == 2
3133
    assert source1.timeperiodexception_set.count() == 2
3132
    assert agenda.is_available_for_simple_management() is True
3133

  
3134

  
3135
def test_meetings_agenda_delete_time_period_exception_source(app, admin_user):
3136
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
3137
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
3138
    source1 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
3139
    TimePeriodException.objects.create(
3140
        desk=desk,
3141
        source=source1,
3142
        start_datetime=now() - datetime.timedelta(days=1),
3143
        end_datetime=now() + datetime.timedelta(days=1),
3144
    )
3145
    source2 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
3146
    TimePeriodException.objects.create(
3147
        desk=desk,
3148
        source=source2,
3149
        start_datetime=now() - datetime.timedelta(days=1),
3150
        end_datetime=now() + datetime.timedelta(days=1),
3151
    )
3152
    desk.duplicate()
3153
    assert TimePeriodException.objects.count() == 4
3154
    assert TimePeriodExceptionSource.objects.count() == 4
3155

  
3156
    login(app)
3157
    resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source2.pk)
3158
    resp = resp.form.submit()
3159
    assert TimePeriodException.objects.count() == 3
3160
    assert TimePeriodExceptionSource.objects.count() == 3
3161
    assert source1.timeperiodexception_set.count() == 1
3134 3162
    assert TimePeriodExceptionSource.objects.filter(pk=source2.pk).exists() is False
3135 3163

  
3136
    # delete the first one
3137
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
3138
    resp = resp.click('manage exceptions')
3139
    resp = resp.click(href='/manage/time-period-exceptions-source/%d/delete' % source1.pk)
3140
    resp = resp.form.submit().follow()
3141
    assert TimePeriodException.objects.count() == 0
3142
    assert TimePeriodExceptionSource.objects.filter(pk=source1.pk).exists() is False
3164

  
3165
def test_meetings_agenda_delete_time_period_exception_source_desk_simple_management(app, admin_user):
3166
    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
3167
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
3168
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
3169
    desk.duplicate()
3170
    assert TimePeriodExceptionSource.objects.count() == 2
3171
    assert agenda.is_available_for_simple_management() is True
3172

  
3173
    login(app)
3174
    resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source.pk)
3175
    resp.form.submit()
3176
    assert TimePeriodExceptionSource.objects.count() == 0
3177
    assert agenda.is_available_for_simple_management() is True
3178

  
3179
    # should not happen: corresponding source does not exist
3180
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
3181
    assert TimePeriodExceptionSource.objects.count() == 1
3182
    resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source.pk)
3183
    resp.form.submit()
3184
    assert TimePeriodExceptionSource.objects.count() == 0
3143 3185

  
3144 3186

  
3145 3187
def test_meetings_agenda_replace_time_period_exception_source(app, admin_user, freezer):
3146 3188
    freezer.move_to('2019-12-01')
3147 3189
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
3148 3190
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
3149
    MeetingType(agenda=agenda, label='Blah').save()
3150
    TimePeriod.objects.create(
3151
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
3152
    )
3153 3191
    ics_file_content = b"""BEGIN:VCALENDAR
3154 3192
VERSION:2.0
3155 3193
PRODID:-//foo.bar//EN
......
3163 3201

  
3164 3202
    login(app)
3165 3203
    # import a source from a file
3166
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
3167
    resp = resp.click('manage exceptions')
3204
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
3168 3205
    resp.form['ics_file'] = Upload('exceptions.ics', ics_file_content, 'text/calendar')
3169 3206
    resp = resp.form.submit(status=302).follow()
3170 3207
    assert TimePeriodException.objects.filter(desk=desk).count() == 2
......
3173 3210
    exceptions = list(source.timeperiodexception_set.order_by('pk'))
3174 3211
    old_ics_file_path = source.ics_file.path
3175 3212

  
3213
    desk2 = desk.duplicate()
3214
    source2 = desk2.timeperiodexceptionsource_set.get()
3215
    exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
3216
    assert TimePeriodException.objects.count() == 4
3217
    old_ics_file_path2 = source2.ics_file.path
3218

  
3176 3219
    # replace the source
3177
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
3178
    resp = resp.click('manage exceptions')
3179
    resp = resp.click(href='/manage/time-period-exceptions-source/%d/replace' % source.pk)
3220
    resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
3180 3221
    resp.form['ics_newfile'] = Upload('exceptions-bis.ics', ics_file_content, 'text/calendar')
3181 3222
    resp = resp.form.submit().follow()
3182 3223
    source.refresh_from_db()
3183 3224
    assert source.ics_file.path != old_ics_file_path
3184 3225
    assert source.ics_filename == 'exceptions-bis.ics'
3185 3226
    assert os.path.exists(old_ics_file_path) is False
3186
    assert TimePeriodException.objects.count() == 2
3227
    assert TimePeriodException.objects.count() == 4
3187 3228
    assert source.timeperiodexception_set.count() == 2
3188 3229
    new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
3189 3230
    assert exceptions[0].pk != new_exceptions[0].pk
3190 3231
    assert exceptions[1].pk != new_exceptions[1].pk
3232
    source2.refresh_from_db()
3233
    assert source2.ics_file.path == old_ics_file_path2
3234
    assert source2.ics_filename == 'exceptions.ics'
3235
    new_exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
3236
    assert exceptions2[0].pk == new_exceptions2[0].pk
3237
    assert exceptions2[1].pk == new_exceptions2[1].pk
3191 3238

  
3192 3239

  
3193
@mock.patch('chrono.agendas.models.requests.get')
3194
def test_meetings_agenda_refresh_time_period_exception_source(mocked_get, app, admin_user):
3195
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
3240
def test_meetings_agenda_replace_time_period_exception_source_desk_simple_management(app, admin_user):
3241
    ics_file_content = b"""BEGIN:VCALENDAR
3242
VERSION:2.0
3243
PRODID:-//foo.bar//EN
3244
BEGIN:VEVENT
3245
DTSTART:20180101
3246
DTEND:20180101
3247
SUMMARY:New Year's Eve
3248
END:VEVENT
3249
END:VCALENDAR"""
3250

  
3251
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
3196 3252
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
3197
    MeetingType(agenda=agenda, label='Blah').save()
3198
    TimePeriod.objects.create(
3199
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
3253
    source = TimePeriodExceptionSource.objects.create(
3254
        desk=desk,
3255
        ics_filename='sample.ics',
3256
        ics_file=ContentFile(ics_file_content, name='sample.ics'),
3200 3257
    )
3201
    ics_url_content = """BEGIN:VCALENDAR
3258
    desk2 = desk.duplicate()
3259
    source2 = desk2.timeperiodexceptionsource_set.get()
3260
    assert TimePeriodExceptionSource.objects.count() == 2
3261
    assert TimePeriodException.objects.count() == 0  # not imported yet
3262
    assert agenda.is_available_for_simple_management() is True
3263
    old_ics_file_path = source.ics_file.path
3264
    old_ics_file_path2 = source2.ics_file.path
3265

  
3266
    login(app)
3267
    resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
3268
    resp.form['ics_newfile'] = Upload('exceptions-bis.ics', ics_file_content, 'text/calendar')
3269
    resp.form.submit()
3270
    assert TimePeriodExceptionSource.objects.count() == 2
3271
    assert TimePeriodException.objects.count() == 2
3272
    assert desk.timeperiodexception_set.count() == 1
3273
    assert desk2.timeperiodexception_set.count() == 1
3274
    source.refresh_from_db()
3275
    assert source.ics_file.path != old_ics_file_path
3276
    assert source.ics_filename == 'exceptions-bis.ics'
3277
    assert os.path.exists(old_ics_file_path) is False
3278
    source2.refresh_from_db()
3279
    assert source2.ics_file.path != old_ics_file_path2
3280
    assert source2.ics_filename == 'exceptions-bis.ics'
3281
    assert os.path.exists(old_ics_file_path2) is False
3282
    assert agenda.is_available_for_simple_management() is True
3283

  
3284
    # should not happen: corresponding source does not exist
3285
    source2.delete()
3286
    resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
3287
    resp.form['ics_newfile'] = Upload('exceptions-ter.ics', ics_file_content, 'text/calendar')
3288
    resp.form.submit()
3289
    assert TimePeriodExceptionSource.objects.count() == 1
3290
    assert TimePeriodException.objects.count() == 1
3291
    assert desk.timeperiodexception_set.count() == 1
3292
    assert desk2.timeperiodexception_set.count() == 0
3293
    assert desk.timeperiodexceptionsource_set.get().ics_filename == 'exceptions-ter.ics'
3294

  
3295

  
3296
@mock.patch('chrono.agendas.models.requests.get')
3297
def test_meetings_agenda_refresh_time_period_exception_source(mocked_get, app, admin_user):
3298
    mocked_response = mock.Mock()
3299
    mocked_response.text = """BEGIN:VCALENDAR
3202 3300
VERSION:2.0
3203 3301
PRODID:-//foo.bar//EN
3204 3302
BEGIN:VEVENT
......
3207 3305
SUMMARY:New Year's Eve
3208 3306
END:VEVENT
3209 3307
END:VCALENDAR"""
3308
    mocked_get.return_value = mocked_response
3309

  
3310
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
3311
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
3210 3312

  
3211 3313
    login(app)
3212 3314
    # import a source from an url
3213 3315
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
3214 3316
    resp = resp.click('manage exceptions')
3215 3317
    resp.form['ics_url'] = 'http://example.com/foo.ics'
3216
    mocked_response = mock.Mock()
3217
    mocked_response.text = ics_url_content
3218
    mocked_get.return_value = mocked_response
3219 3318
    resp = resp.form.submit(status=302).follow()
3220 3319
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
3221 3320
    source = TimePeriodExceptionSource.objects.latest('pk')
3222 3321
    assert source.timeperiodexception_set.count() == 1
3223 3322
    exceptions = list(source.timeperiodexception_set.order_by('pk'))
3224 3323

  
3324
    desk2 = desk.duplicate()
3325
    source2 = desk2.timeperiodexceptionsource_set.get()
3326
    exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
3327
    assert TimePeriodException.objects.count() == 2
3328

  
3225 3329
    # refresh the source
3226 3330
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
3227
    resp = resp.click('manage exceptions')
3331
    resp = resp.click('manage exceptions', index=0)
3332
    resp = resp.click(href='/manage/time-period-exceptions-source/%d/refresh' % source.pk)
3333
    assert TimePeriodException.objects.count() == 2
3334
    new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
3335
    assert exceptions[0].pk != new_exceptions[0].pk
3336
    new_exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
3337
    assert exceptions2[0].pk == new_exceptions2[0].pk
3338

  
3339

  
3340
@mock.patch('chrono.agendas.models.requests.get')
3341
def test_meetings_agenda_refresh_time_period_exception_source_desk_simple_management(
3342
    mocked_get, app, admin_user
3343
):
3228 3344
    mocked_response = mock.Mock()
3229
    mocked_response.text = ics_url_content
3345
    mocked_response.text = """BEGIN:VCALENDAR
3346
VERSION:2.0
3347
PRODID:-//foo.bar//EN
3348
BEGIN:VEVENT
3349
DTSTART:20180101
3350
DTEND:20180101
3351
SUMMARY:New Year's Eve
3352
END:VEVENT
3353
END:VCALENDAR"""
3230 3354
    mocked_get.return_value = mocked_response
3231
    resp = resp.click(href='/manage/time-period-exceptions-source/%d/refresh' % source.pk)
3355

  
3356
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
3357
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
3358
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
3359
    desk2 = desk.duplicate()
3360
    source2 = desk2.timeperiodexceptionsource_set.get()
3361
    assert TimePeriodExceptionSource.objects.count() == 2
3362
    assert TimePeriodException.objects.count() == 0  # not imported yet
3363
    assert agenda.is_available_for_simple_management() is True
3364

  
3365
    login(app)
3366
    app.get('/manage/time-period-exceptions-source/%d/refresh' % source.pk)
3367
    assert TimePeriodExceptionSource.objects.count() == 2
3368
    assert TimePeriodException.objects.count() == 2
3369
    assert source.timeperiodexception_set.count() == 1
3370
    assert source2.timeperiodexception_set.count() == 1
3371
    assert agenda.is_available_for_simple_management() is True
3372

  
3373
    # should not happen: corresponding source does not exist
3374
    source2.delete()
3375
    app.get('/manage/time-period-exceptions-source/%d/refresh' % source.pk)
3376
    assert TimePeriodExceptionSource.objects.count() == 1
3232 3377
    assert TimePeriodException.objects.count() == 1
3233 3378
    assert source.timeperiodexception_set.count() == 1
3234
    new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
3235
    assert exceptions[0].pk != new_exceptions[0].pk
3236 3379

  
3237 3380

  
3238 3381
@override_settings(
......
3240 3383
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
3241 3384
    }
3242 3385
)
3243
def test_meetings_agenda_time_period_exception_source_from_settings(app, admin_user):
3386
def test_meetings_agenda_time_period_exception_source_from_settings_toggle(app, admin_user):
3244 3387
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
3245 3388
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
3246 3389
    desk.import_timeperiod_exceptions_from_settings(enable=True)
3247
    MeetingType(agenda=agenda, label='Blah').save()
3248
    TimePeriod.objects.create(
3249
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
3250
    )
3251
    assert TimePeriodException.objects.exists()
3390
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
3391
    desk2.import_timeperiod_exceptions_from_settings(enable=True)
3392
    assert desk.timeperiodexception_set.exists()
3393
    assert desk2.timeperiodexception_set.exists()
3252 3394

  
3253 3395
    login(app)
3254
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
3255
    resp = resp.click('Settings')
3256
    resp = resp.click('manage exceptions')
3396
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
3257 3397
    assert 'Holidays' in resp.text
3258 3398
    assert 'disabled' not in resp.text
3259 3399
    assert 'refresh' not in resp.text
3260 3400

  
3261 3401
    resp = resp.click('disable').follow()
3262
    assert not TimePeriodException.objects.exists()
3402
    assert not desk.timeperiodexception_set.exists()
3403
    assert desk2.timeperiodexception_set.exists()
3263 3404

  
3264
    resp = resp.click('manage exceptions')
3405
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
3265 3406
    assert 'Holidays' in resp.text
3266 3407
    assert 'disabled' in resp.text
3267 3408

  
3268 3409
    resp = resp.click('enable').follow()
3269
    assert TimePeriodException.objects.exists()
3410
    assert desk.timeperiodexception_set.exists()
3411
    assert desk2.timeperiodexception_set.exists()
3270 3412

  
3271
    resp = resp.click('manage exceptions')
3413
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
3272 3414
    assert 'disabled' not in resp.text
3273 3415

  
3274 3416

  
3417
@override_settings(
3418
    EXCEPTIONS_SOURCES={
3419
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
3420
    }
3421
)
3422
def test_meetings_agenda_time_period_exception_source_from_settings_toggle_desk_simple_management(
3423
    app, admin_user
3424
):
3425
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
3426
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
3427
    desk.import_timeperiod_exceptions_from_settings(enable=True)
3428
    source = desk.timeperiodexceptionsource_set.get()
3429
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
3430
    desk2.import_timeperiod_exceptions_from_settings(enable=True)
3431
    source2 = desk2.timeperiodexceptionsource_set.get()
3432
    assert desk.timeperiodexception_set.exists()
3433
    assert desk2.timeperiodexception_set.exists()
3434
    assert agenda.is_available_for_simple_management() is True
3435

  
3436
    login(app)
3437

  
3438
    app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
3439
    source.refresh_from_db()
3440
    source2.refresh_from_db()
3441
    assert not source.enabled
3442
    assert not source2.enabled
3443
    assert not desk.timeperiodexception_set.exists()
3444
    assert not desk2.timeperiodexception_set.exists()
3445
    assert agenda.is_available_for_simple_management() is True
3446

  
3447
    app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
3448
    source.refresh_from_db()
3449
    source2.refresh_from_db()
3450
    assert source.enabled
3451
    assert source2.enabled
3452
    assert desk.timeperiodexception_set.exists()
3453
    assert desk2.timeperiodexception_set.exists()
3454
    assert agenda.is_available_for_simple_management() is True
3455

  
3456
    # should not happen: corresponding source does not exist
3457
    source2.delete()
3458
    app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
3459
    source.refresh_from_db()
3460
    assert not source.enabled
3461

  
3462

  
3275 3463
def test_meetings_agenda_time_period_exception_source_try_disable_ics(app, admin_user):
3276 3464
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
3277 3465
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
......
3300 3488
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
3301 3489
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
3302 3490
    desk.import_timeperiod_exceptions_from_settings(enable=True)
3303
    MeetingType(agenda=agenda, label='Blah').save()
3304
    TimePeriod.objects.create(
3305
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
3306
    )
3307 3491
    new_year = desk.timeperiodexception_set.filter(label='New year').first()
3308 3492
    remove_url = reverse('chrono-manager-time-period-exception-delete', kwargs={'pk': new_year.pk})
3309 3493
    edit_url = reverse('chrono-manager-time-period-exception-edit', kwargs={'pk': new_year.pk})
......
3312 3496
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
3313 3497
    resp = resp.click('Settings')
3314 3498
    assert 'New year' in resp.text
3315
    assert not remove_url in resp.text and not edit_url in resp.text
3499
    assert remove_url not in resp.text and edit_url not in resp.text
3316 3500

  
3317 3501
    resp = resp.click('see all')
3318 3502
    assert 'New year' in resp.text
3319
    assert not remove_url in resp.text and not edit_url in resp.text
3503
    assert remove_url not in resp.text and edit_url not in resp.text
3320 3504

  
3321 3505
    app.get(remove_url, status=404)
3322 3506

  
......
5470 5654
    assert unavailability_calendar.timeperiodexception_set.count() == 0
5471 5655

  
5472 5656

  
5473
def test_activate_unavailability_calendar_in_desk(app, admin_user):
5657
def test_unavailability_calendar_in_desk(app, admin_user):
5474 5658
    app = login(app)
5475 5659
    unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
5476
    start_datetime = localtime(now()) + datetime.timedelta(days=1)
5477
    time_period_exception = TimePeriodException.objects.create(
5660
    today = localtime(now().replace(hour=0, minute=0, second=0, microsecond=0))
5661
    tomorrow = today + datetime.timedelta(days=1)
5662
    TimePeriodException.objects.create(
5478 5663
        unavailability_calendar=unavailability_calendar,
5479
        start_datetime=start_datetime,
5480
        end_datetime=localtime(now()) + datetime.timedelta(days=2),
5664
        start_datetime=tomorrow,
5665
        end_datetime=tomorrow + datetime.timedelta(days=2),
5481 5666
        label='what',
5482 5667
    )
5483 5668
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
5484 5669
    desk = Desk.objects.create(agenda=agenda, label='desk')
5670
    desk2 = desk.duplicate()
5671
    assert not desk.unavailability_calendars.exists()
5672
    assert not desk2.unavailability_calendars.exists()
5485 5673
    exceptions_url = '/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk
5486 5674
    resp = app.get(exceptions_url)
5487 5675
    assert 'calendar' in resp.text
......
5491 5679
    )
5492 5680
    settings_url = '/manage/agendas/%s/settings' % agenda.pk
5493 5681
    assert resp.location.endswith(settings_url)
5494
    # exception from calendar displayed on the settigns page
5682
    # exception from calendar displayed on the settings page
5495 5683
    resp = app.get(settings_url)
5496 5684
    assert 'what' in resp.text
5497 5685
    assert 'One or several bookings overlap with exceptions.' not in resp.text
......
5500 5688
    assert 'calendar' in resp.text
5501 5689
    assert 'disable' in resp.text
5502 5690
    assert desk.unavailability_calendars.get(pk=unavailability_calendar.pk)
5691
    assert not desk2.unavailability_calendars.exists()
5503 5692

  
5504 5693
    # reset
5505
    unavailability_calendar.desks.remove(desk)
5694
    resp = resp.click(
5695
        href='/manage/desk/%s/unavailability-calendar/%s/toggle/' % (desk.pk, unavailability_calendar.pk)
5696
    )
5697
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
5698
    resp = app.get(exceptions_url)
5699
    assert 'calendar' in resp.text
5700
    assert 'enable' in resp.text
5701
    assert not desk.unavailability_calendars.exists()
5506 5702

  
5507 5703
    # info message if some exceptions overlaps with a booking
5508
    today = datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
5509
    tomorrow = make_aware(today + datetime.timedelta(days=1))
5510
    TimePeriodException.objects.create(
5511
        start_datetime=tomorrow,
5512
        end_datetime=tomorrow.replace(hour=15, minute=30, second=0),
5513
        unavailability_calendar=unavailability_calendar,
5514
    )
5515 5704
    meeting_type = MeetingType.objects.create(label='Meeting Type', agenda=agenda)
5516 5705
    event = Event.objects.create(
5517 5706
        agenda=agenda,
......
5532 5721
    assert 'One or several bookings overlap with exceptions.' in resp.text
5533 5722

  
5534 5723

  
5724
def test_unavailability_calendar_in_desk_desk_simple_management(app, admin_user):
5725
    unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
5726
    today = localtime(now().replace(hour=0, minute=0, second=0, microsecond=0))
5727
    tomorrow = today + datetime.timedelta(days=1)
5728
    TimePeriodException.objects.create(
5729
        unavailability_calendar=unavailability_calendar,
5730
        start_datetime=tomorrow,
5731
        end_datetime=tomorrow + datetime.timedelta(days=2),
5732
        label='what',
5733
    )
5734
    agenda = Agenda.objects.create(label='Agenda', kind='meetings', desk_simple_management=True)
5735
    desk = Desk.objects.create(agenda=agenda, label='desk')
5736
    desk2 = desk.duplicate()
5737
    assert not desk.unavailability_calendars.exists()
5738
    assert not desk2.unavailability_calendars.exists()
5739
    assert agenda.is_available_for_simple_management() is True
5740

  
5741
    meeting_type = MeetingType.objects.create(label='Meeting Type', agenda=agenda)
5742
    event = Event.objects.create(
5743
        agenda=agenda,
5744
        places=1,
5745
        desk=desk2,
5746
        meeting_type=meeting_type,
5747
        start_datetime=tomorrow.replace(hour=10, minute=30, second=0),
5748
    )
5749
    Booking.objects.create(event=event)
5750

  
5751
    login(app)
5752

  
5753
    resp = app.get(
5754
        '/manage/desk/%s/unavailability-calendar/%s/toggle/' % (desk.pk, unavailability_calendar.pk)
5755
    )
5756
    assert desk.unavailability_calendars.exists()
5757
    assert desk2.unavailability_calendars.exists()
5758
    assert agenda.is_available_for_simple_management() is True
5759
    resp = resp.follow()
5760
    assert 'One or several bookings overlap with exceptions.' in resp.text
5761

  
5762
    app.get('/manage/desk/%s/unavailability-calendar/%s/toggle/' % (desk.pk, unavailability_calendar.pk))
5763
    assert not desk.unavailability_calendars.exists()
5764
    assert not desk2.unavailability_calendars.exists()
5765
    assert agenda.is_available_for_simple_management() is True
5766

  
5767
    # should not happen: unavailability_calendar is not in the correct state
5768
    desk2.unavailability_calendars.add(unavailability_calendar)
5769
    assert not desk.unavailability_calendars.exists()
5770
    assert desk2.unavailability_calendars.exists()
5771
    assert agenda.is_available_for_simple_management() is False
5772
    app.get('/manage/desk/%s/unavailability-calendar/%s/toggle/' % (desk.pk, unavailability_calendar.pk))
5773
    assert desk.unavailability_calendars.exists()
5774
    assert desk2.unavailability_calendars.exists()
5775
    assert agenda.is_available_for_simple_management() is True
5776
    desk2.unavailability_calendars.remove(unavailability_calendar)
5777
    assert desk.unavailability_calendars.exists()
5778
    assert not desk2.unavailability_calendars.exists()
5779
    assert agenda.is_available_for_simple_management() is False
5780
    app.get('/manage/desk/%s/unavailability-calendar/%s/toggle/' % (desk.pk, unavailability_calendar.pk))
5781
    assert not desk.unavailability_calendars.exists()
5782
    assert not desk2.unavailability_calendars.exists()
5783
    assert agenda.is_available_for_simple_management() is True
5784

  
5785

  
5535 5786
def test_unavailability_calendar_exception_in_desk(app, admin_user):
5536 5787
    unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
5537 5788
    start_datetime = localtime(now()) + datetime.timedelta(days=1)
......
5554 5805
    assert resp.text.count('exception foo bar') == 3  # displayed just once per desk
5555 5806

  
5556 5807

  
5557
def test_deactivate_unavailability_calendar_in_desk(app, admin_user):
5558
    app = login(app)
5559
    unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
5560
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
5561
    desk = Desk.objects.create(agenda=agenda, label='desk')
5562
    desk.unavailability_calendars.add(unavailability_calendar)
5563
    exceptions_url = '/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk
5564
    resp = app.get(exceptions_url)
5565
    assert 'calendar' in resp.text
5566
    assert 'disable' in resp.text
5567
    resp = resp.click(
5568
        href='/manage/desk/%s/unavailability-calendar/%s/toggle/' % (desk.pk, unavailability_calendar.pk)
5569
    )
5570
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
5571
    resp = app.get(exceptions_url)
5572
    assert 'calendar' in resp.text
5573
    assert 'enable' in resp.text
5574
    assert desk.unavailability_calendars.count() == 0
5575

  
5576

  
5577 5808
def test_unavailability_calendar_homepage_permission(app, manager_user):
5578 5809
    unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
5579 5810
    app = login(app, username='manager', password='manager')
5580
-