From a1302e3389849ff8c095634f6ed7c58536158f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Fri, 17 Mar 2023 10:16:49 +0100 Subject: [PATCH 1/2] api: lock check on event (#75416) --- .../migrations/0150_event_check_locked.py | 15 + chrono/agendas/models.py | 1 + chrono/api/serializers.py | 16 +- chrono/api/urls.py | 5 + chrono/api/views.py | 30 ++ tests/api/test_event.py | 292 ++++++++++++++++++ 6 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 chrono/agendas/migrations/0150_event_check_locked.py diff --git a/chrono/agendas/migrations/0150_event_check_locked.py b/chrono/agendas/migrations/0150_event_check_locked.py new file mode 100644 index 00000000..39a76876 --- /dev/null +++ b/chrono/agendas/migrations/0150_event_check_locked.py @@ -0,0 +1,15 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('agendas', '0149_booking_extra_user_block'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='check_locked', + field=models.BooleanField(default=False), + ), + ] diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index fe7fdcfc..4c3d1cb1 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -1521,6 +1521,7 @@ class Event(models.Model): cancelled = models.BooleanField(default=False) cancellation_scheduled = models.BooleanField(default=False) checked = models.BooleanField(default=False) + check_locked = models.BooleanField(default=False) meeting_type = models.ForeignKey(MeetingType, null=True, on_delete=models.CASCADE) desk = models.ForeignKey('Desk', null=True, on_delete=models.CASCADE) resources = models.ManyToManyField('Resource') diff --git a/chrono/api/serializers.py b/chrono/api/serializers.py index 031abd40..13888567 100644 --- a/chrono/api/serializers.py +++ b/chrono/api/serializers.py @@ -157,6 +157,18 @@ class MultipleAgendasEventsCheckStatusSerializer(AgendaSlugsMixin, DateRangeMixi return get_objects_from_slugs(value, qs=self.get_agenda_qs()) +class MultipleAgendasEventsCheckLockSerializer(AgendaSlugsMixin, DateRangeMixin, serializers.Serializer): + check_locked = serializers.BooleanField() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for field in ['agendas', 'date_start', 'date_end', 'check_locked']: + self.fields[field].required = True + + def validate_agendas(self, value): + return get_objects_from_slugs(value, qs=self.get_agenda_qs()) + + class RecurringFillslotsSerializer(MultipleAgendasEventsFillSlotsSerializer): include_booked_events_detail = serializers.BooleanField(default=False) check_overlaps = CommaSeparatedStringField( @@ -437,8 +449,10 @@ class EventSerializer(serializers.ModelSerializer): 'url', 'primary_event', 'agenda', + 'checked', + 'check_locked', ] - read_only_fields = ['slug'] + read_only_fields = ['slug', 'checked', 'check_locked'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/chrono/api/urls.py b/chrono/api/urls.py index a09e1a6a..e46c7f5e 100644 --- a/chrono/api/urls.py +++ b/chrono/api/urls.py @@ -38,6 +38,11 @@ urlpatterns = [ views.agendas_events_check_status, name='api-agendas-events-check-status', ), + path( + 'agendas/events/check-lock/', + views.agendas_events_check_lock, + name='api-agendas-events-check-lock', + ), re_path(r'^agenda/(?P[\w-]+)/$', views.agenda), re_path( r'^agenda/(?P[\w-]+)/datetimes/$', views.datetimes, name='api-agenda-datetimes' diff --git a/chrono/api/views.py b/chrono/api/views.py index 38847df4..2ea2c747 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -502,6 +502,7 @@ def get_event_detail( 'url': event.url, 'duration': event.duration, 'checked': event.checked, + 'check_locked': event.check_locked, } for key, value in event.get_custom_fields().items(): details['custom_field_%s' % key] = value @@ -2296,6 +2297,35 @@ class MultipleAgendasEventsCheckStatus(APIView): agendas_events_check_status = MultipleAgendasEventsCheckStatus.as_view() +class MultipleAgendasEventsCheckLock(APIView): + permission_classes = (permissions.IsAuthenticated,) + serializer_class = serializers.MultipleAgendasEventsCheckLockSerializer + + def post(self, request): + serializer = self.serializer_class(data=request.data) + + if not serializer.is_valid(): + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors, err=1) + agendas = serializer.validated_data['agendas'] + date_start = serializer.validated_data['date_start'] + date_end = serializer.validated_data['date_end'] + check_locked = serializer.validated_data['check_locked'] + + events = Event.objects.filter( + agenda__in=agendas, + recurrence_days__isnull=True, + cancelled=False, + start_datetime__gte=date_start, + start_datetime__lt=date_end, + ) + events.update(check_locked=check_locked) + + return Response({'err': 0}) + + +agendas_events_check_lock = MultipleAgendasEventsCheckLock.as_view() + + class SubscriptionFilter(filters.FilterSet): date_start = filters.DateFilter(method='do_nothing') date_end = filters.DateFilter(method='do_nothing') diff --git a/tests/api/test_event.py b/tests/api/test_event.py index 3c3aad3e..d30c0864 100644 --- a/tests/api/test_event.py +++ b/tests/api/test_event.py @@ -54,6 +54,7 @@ def test_status(app, user): 'disabled': False, 'duration': None, 'checked': False, + 'check_locked': False, 'api': { 'bookings_url': 'http://testserver/api/agenda/foo-bar/bookings/event-slug/', 'fillslot_url': 'http://testserver/api/agenda/foo-bar/fillslot/event-slug/', @@ -92,6 +93,7 @@ def test_status(app, user): 'disabled': False, 'duration': None, 'checked': False, + 'check_locked': False, 'custom_field_text': 'foo', 'custom_field_textarea': 'foo bar', 'custom_field_bool': True, @@ -880,6 +882,7 @@ def test_events(app, user): places=10, agenda=agenda2, checked=True, + check_locked=True, ) # cancelled event, not returned Event.objects.create( @@ -939,6 +942,8 @@ def test_events(app, user): assert resp.json['data'] == [ { 'agenda': 'bar', + 'check_locked': True, + 'checked': True, 'description': None, 'duration': None, 'label': 'Event Label', @@ -956,6 +961,8 @@ def test_events(app, user): }, { 'agenda': 'foo', + 'check_locked': False, + 'checked': False, 'description': None, 'duration': None, 'label': 'Recurring Event Label', @@ -973,6 +980,8 @@ def test_events(app, user): }, { 'agenda': 'foo', + 'check_locked': False, + 'checked': False, 'description': None, 'duration': None, 'label': 'Recurring Event Label', @@ -1301,6 +1310,7 @@ def test_events_check_status_events(app, user): places=10, agenda=agenda, checked=True, + check_locked=True, ) # not checked event notchecked_event = Event.objects.create( @@ -1366,6 +1376,8 @@ def test_events_check_status_events(app, user): 'waiting_list_places': 0, 'agenda': agenda.slug, 'primary_event': None, + 'check_locked': False, + 'checked': False, 'custom_field_bool': None, 'custom_field_text': '', 'custom_field_textarea': '', @@ -1390,6 +1402,8 @@ def test_events_check_status_events(app, user): 'waiting_list_places': 0, 'agenda': agenda.slug, 'primary_event': None, + 'check_locked': True, + 'checked': True, 'custom_field_bool': None, 'custom_field_text': '', 'custom_field_textarea': '', @@ -1429,6 +1443,8 @@ def test_events_check_status_events(app, user): 'waiting_list_places': 0, 'agenda': agenda.slug, 'primary_event': recurring_event.slug, + 'check_locked': False, + 'checked': True, 'custom_field_text': 'foo', 'custom_field_textarea': 'foo bar', 'custom_field_bool': True, @@ -1595,3 +1611,279 @@ def test_events_check_status_subscription_filter(app, user, freezer, event_date, } resp = app.get(url, params=params) assert len(resp.json['data']) == int(expected) + + +def test_events_check_lock_params(app, user): + app.authorization = ('Basic', ('john.doe', 'password')) + + # missing check_locked + resp = app.post_json( + '/api/agendas/events/check-lock/', + params={'agendas': 'foo', 'date_start': '2022-05-01', 'date_end': '2022-06-01'}, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'invalid payload' + assert resp.json['errors']['check_locked'] == ['This field is required.'] + + # missing agendas + resp = app.post_json( + '/api/agendas/events/check-lock/', + params={'check_locked': True, 'date_start': '2022-05-01', 'date_end': '2022-06-01'}, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'invalid payload' + assert resp.json['errors']['agendas'] == ['This field is required.'] + + # unknown agenda + resp = app.post_json( + '/api/agendas/events/check-lock/', + params={ + 'check_locked': True, + 'agendas': 'foo, bar', + 'date_start': '2022-05-01', + 'date_end': '2022-06-01', + }, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'invalid payload' + assert resp.json['errors']['agendas'] == ['invalid slugs: bar, foo'] + Agenda.objects.create(label='Foo') + resp = app.post_json( + '/api/agendas/events/check-lock/', + params={ + 'check_locked': True, + 'agendas': 'foo, bar', + 'date_start': '2022-05-01', + 'date_end': '2022-06-01', + }, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'invalid payload' + assert resp.json['errors']['agendas'] == ['invalid slugs: bar'] + + # wrong kind + wrong_agenda = Agenda.objects.create(label='Bar') + for kind in ['meetings', 'virtual']: + wrong_agenda.kind = kind + wrong_agenda.save() + resp = app.post_json( + '/api/agendas/events/check-lock/', + params={ + 'check_locked': True, + 'agendas': 'foo, bar', + 'date_start': '2022-05-01', + 'date_end': '2022-06-01', + }, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'invalid payload' + assert resp.json['errors']['agendas'] == ['invalid slugs: bar'] + + # missing date_start + resp = app.post_json( + '/api/agendas/events/check-lock/', + params={'check_locked': True, 'agendas': 'foo', 'date_end': '2022-06-01'}, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'invalid payload' + assert resp.json['errors']['date_start'] == ['This field is required.'] + + # missing date_end + resp = app.post_json( + '/api/agendas/events/check-lock/', + params={'check_locked': True, 'agendas': 'foo', 'date_start': '2022-05-01'}, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'invalid payload' + assert resp.json['errors']['date_end'] == ['This field is required.'] + + # bad date format + resp = app.post_json( + '/api/agendas/events/check-lock/', + params={'check_locked': True, 'agendas': 'foo', 'date_start': 'wrong', 'date_end': 'wrong'}, + status=400, + ) + assert resp.json['err'] == 1 + assert resp.json['err_desc'] == 'invalid payload' + assert 'wrong format' in resp.json['errors']['date_start'][0] + assert 'wrong format' in resp.json['errors']['date_end'][0] + + +@pytest.mark.freeze_time('2022-05-30 14:00') +def test_events_check_lock(app, user): + agenda = Agenda.objects.create(label='Foo') + event = Event.objects.create( + slug='event-slug', + label='Event Label', + start_datetime=now(), + places=10, + agenda=agenda, + checked=True, + ) + + app.authorization = ('Basic', ('john.doe', 'password')) + url = '/api/agendas/events/check-lock/' + params = { + 'check_locked': True, + 'agendas': 'foo', + 'date_start': '2022-05-01', + 'date_end': '2022-06-01', + } + resp = app.post_json(url, params=params) + assert resp.json['err'] == 0 + event.refresh_from_db() + assert event.check_locked is True + + params['check_locked'] = False + resp = app.post_json(url, params=params) + assert resp.json['err'] == 0 + event.refresh_from_db() + assert event.check_locked is False + + +@pytest.mark.freeze_time('2022-05-30 14:00') +def test_events_check_lock_events(app, user): + events_type = EventsType.objects.create( + label='Foo', + ) + agenda = Agenda.objects.create(label='Foo', events_type=events_type) + start_datetime = now() + # recurring event + recurring_event = Event.objects.create( + slug='recurring-event-slug', + label='Recurring Event Label', + start_datetime=start_datetime, + recurrence_days=[start_datetime.weekday()], + recurrence_end_date=start_datetime + datetime.timedelta(days=7), + places=10, + agenda=agenda, + ) + recurring_event.create_all_recurrences() + first_event = recurring_event.recurrences.get() + event = Event.objects.create( + slug='event-slug', + label='Event Label', + start_datetime=start_datetime - datetime.timedelta(days=1), + places=10, + agenda=agenda, + ) + # cancelled event, not updated + cancelled_event = Event.objects.create( + slug='cancelled', + label='Cancelled', + start_datetime=start_datetime, + places=10, + agenda=agenda, + cancelled=True, + ) + + app.authorization = ('Basic', ('john.doe', 'password')) + url = '/api/agendas/events/check-lock/' + params = { + 'check_locked': True, + 'agendas': 'foo', + 'date_start': '2022-05-01', + 'date_end': '2022-06-01', + } + resp = app.post_json(url, params=params) + assert resp.json['err'] == 0 + recurring_event.refresh_from_db() + assert recurring_event.check_locked is False + first_event.refresh_from_db() + assert first_event.check_locked is True + event.refresh_from_db() + assert event.check_locked is True + cancelled_event.refresh_from_db() + assert cancelled_event.check_locked is False + + +@pytest.mark.freeze_time('2022-05-30 14:00') +def test_events_check_lock_agendas_filter(app, user): + agenda1 = Agenda.objects.create(label='Foo') + agenda2 = Agenda.objects.create(label='Foo 2') + event1 = Event.objects.create( + slug='event-1', + label='Event 1', + start_datetime=now(), + places=10, + agenda=agenda1, + ) + event2 = Event.objects.create( + slug='event-2', + label='Event 2', + start_datetime=now(), + places=10, + agenda=agenda2, + ) + + app.authorization = ('Basic', ('john.doe', 'password')) + url = '/api/agendas/events/check-lock/' + params = { + 'check_locked': True, + 'agendas': 'foo, foo-2', + 'date_start': '2022-05-01', + 'date_end': '2022-06-01', + } + app.post_json(url, params=params) + event1.refresh_from_db() + assert event1.check_locked is True + event2.refresh_from_db() + assert event2.check_locked is True + + params['agendas'] = 'foo' + params['check_locked'] = False + app.post_json(url, params=params) + event1.refresh_from_db() + assert event1.check_locked is False + event2.refresh_from_db() + assert event2.check_locked is True + + params['agendas'] = 'foo-2' + app.post_json(url, params=params) + event1.refresh_from_db() + assert event1.check_locked is False + event2.refresh_from_db() + assert event2.check_locked is False + + +@pytest.mark.parametrize( + 'event_date, expected', + [ + # just before first day + ((2022, 4, 30, 12, 0), False), + # first day + ((2022, 5, 1, 12, 0), True), + # last day + ((2022, 5, 31, 12, 0), True), + # just after last day + ((2022, 6, 1, 12, 0), False), + ], +) +def test_events_check_lock_date_filter(app, user, event_date, expected): + agenda = Agenda.objects.create(label='Foo') + event = Event.objects.create( + slug='event', + label='Event', + start_datetime=make_aware(datetime.datetime(*event_date)), + places=10, + agenda=agenda, + ) + + app.authorization = ('Basic', ('john.doe', 'password')) + url = '/api/agendas/events/check-lock/' + params = { + 'check_locked': True, + 'agendas': 'foo', + 'date_start': '2022-05-01', + 'date_end': '2022-06-01', + } + app.post_json(url, params=params) + event.refresh_from_db() + assert event.check_locked == expected -- 2.39.2 From 8892bd7bcb83f73ff4d816936b665df79dc9cb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Fri, 17 Mar 2023 10:40:37 +0100 Subject: [PATCH 2/2] manager: lock check if flag check_locked is enabled (#75416) --- .../chrono/manager_agenda_event_fragment.html | 3 + .../manager_event_check_booking_fragment.html | 62 +++++++++--------- .../chrono/manager_event_detail.html | 1 + chrono/manager/views.py | 3 + tests/manager/test_event.py | 65 +++++++++++++++++++ 5 files changed, 104 insertions(+), 30 deletions(-) diff --git a/chrono/manager/templates/chrono/manager_agenda_event_fragment.html b/chrono/manager/templates/chrono/manager_agenda_event_fragment.html index a963cbe6..536f91b1 100644 --- a/chrono/manager/templates/chrono/manager_agenda_event_fragment.html +++ b/chrono/manager/templates/chrono/manager_agenda_event_fragment.html @@ -49,6 +49,9 @@ {% if event.main_list_full %} {% trans "Full" %} {% endif %} + {% if event.check_locked %} + {% trans "Check locked" %} + {% endif %} {% if event.checked %} {% trans "Checked" %} {% endif %} diff --git a/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html b/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html index 1460e30a..582c29cf 100644 --- a/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html +++ b/chrono/manager/templates/chrono/manager_event_check_booking_fragment.html @@ -16,7 +16,7 @@ ({% trans "Cancelled" %}) {% endif %} {% if not event.checked or not agenda.disable_check_update %} - {% if booking.user_was_present is not None %} + {% if booking.user_was_present is not None and not event.check_locked %}
{% csrf_token %} {% trans "Reset" context "check" %} @@ -33,45 +33,47 @@ {% endif %} {% endif %} -{% if not event.checked or not agenda.disable_check_update %} - +{% if not event.check_locked %} + {% if not event.checked or not agenda.disable_check_update %} + + {% if booking.kind == "subscription" %} + + {% else %} + + {% endif %} + {% csrf_token %} + + {% if booking.presence_form.check_type.field.choices.1 %}{{ booking.presence_form.check_type }}{% endif %} + +
{% if booking.kind == "subscription" %} -
+ {% else %} - + {% endif %} {% csrf_token %} - {% if booking.presence_form.check_type.field.choices.1 %}{{ booking.presence_form.check_type }}{% endif %} + {% if booking.user_was_present is False %}disabled{% endif %} + >{% trans "Absence" %} + {% if booking.absence_form.check_type.field.choices.1 %}{{ booking.absence_form.check_type }}{% endif %} -
- {% if booking.kind == "subscription" %} -
- {% else %} - +
+ {% endif %} - {% csrf_token %} - - {% if booking.absence_form.check_type.field.choices.1 %}{{ booking.absence_form.check_type }}{% endif %} - - - {% endif %} diff --git a/chrono/manager/templates/chrono/manager_event_detail.html b/chrono/manager/templates/chrono/manager_event_detail.html index ac2650df..0feef473 100644 --- a/chrono/manager/templates/chrono/manager_event_detail.html +++ b/chrono/manager/templates/chrono/manager_event_detail.html @@ -27,6 +27,7 @@
{% if event.cancellation_status %}{{ event.cancellation_status }}{% endif %} {% if event.main_list_full %}{% trans "Full" %}{% endif %} + {% if event.check_locked %}{% trans "Check locked" %}{% endif %} {% if event.checked %}{% trans "Checked" %}{% endif %} {% if event.is_day_past and not event.cancelled %} {% if event.present_count %}{% blocktrans with count=event.present_count %}Presents {{ count }}{% endblocktrans %}{% endif %} diff --git a/chrono/manager/views.py b/chrono/manager/views.py index 9651db64..6b8e10bc 100644 --- a/chrono/manager/views.py +++ b/chrono/manager/views.py @@ -2671,6 +2671,7 @@ class EventCheckMixin: pk=kwargs.get('event_pk'), agenda=self.agenda, cancelled=False, + check_locked=False, ) def get_bookings(self): @@ -3495,6 +3496,7 @@ class BookingCheckMixin: pk=kwargs['booking_pk'], event__agenda=self.agenda, event__cancelled=False, + event__check_locked=False, in_waiting_list=False, primary_booking__isnull=True, ) @@ -3596,6 +3598,7 @@ class SubscriptionCheckMixin(BookingCheckMixin): Q(start_datetime__date__lte=now().date()) | Q(agenda__enable_check_for_future_events=True), agenda=self.agenda, cancelled=False, + check_locked=False, pk=kwargs['event_pk'], ) subscription = get_object_or_404( diff --git a/tests/manager/test_event.py b/tests/manager/test_event.py index 30068802..998d1e6a 100644 --- a/tests/manager/test_event.py +++ b/tests/manager/test_event.py @@ -1716,6 +1716,7 @@ def test_event_checked(app, admin_user): for url in urls: resp = app.get(url) assert 'Checked' not in resp + assert 'check-locked' not in resp assert 'Presents 3' in resp assert 'Absents 4' in resp assert 'Not checked 1' in resp @@ -1736,6 +1737,7 @@ def test_event_checked(app, admin_user): for url in urls: resp = app.get(url) assert 'Checked' in resp + assert 'check-locked' not in resp assert 'Presents 4' in resp assert 'Absents 4' in resp assert 'meta meta-disabled' not in resp @@ -1761,6 +1763,29 @@ def test_event_checked(app, admin_user): status=302, ) + # event check is locked + event.checked = False + event.check_locked = True + event.save() + urls = [ + '/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk), + '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), + '/manage/agendas/%s/month/%d/%d/%d/' + % (agenda.pk, event.start_datetime.year, event.start_datetime.month, event.start_datetime.day), + '/manage/agendas/%s/week/%d/%d/%d/' + % (agenda.pk, event.start_datetime.year, event.start_datetime.month, event.start_datetime.day), + '/manage/agendas/%s/day/%d/%d/%d/' + % (agenda.pk, event.start_datetime.year, event.start_datetime.month, event.start_datetime.day), + ] + for url in urls: + resp = app.get(url) + assert 'Check locked' in resp + app.post( + '/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk), + params={'csrfmiddlewaretoken': token}, + status=404, + ) + @mock.patch('chrono.manager.forms.get_agenda_check_types') def test_event_check_filters(check_types, app, admin_user): @@ -2416,7 +2441,32 @@ def test_event_check_booking(check_types, app, admin_user): status=302, ) + # event check is locked + event.check_locked = True + event.save() + resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) + assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) not in resp + assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) not in resp + assert '/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk) not in resp + app.post( + '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk), + params={'csrfmiddlewaretoken': token}, + status=404, + ) + app.post( + '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk), + params={'csrfmiddlewaretoken': token}, + status=404, + ) + app.post( + '/manage/agendas/%s/bookings/%s/reset' % (agenda.pk, booking.pk), + params={'csrfmiddlewaretoken': token}, + status=404, + ) + # now disable check update + event.check_locked = False + event.save() agenda.disable_check_update = True agenda.save() resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) @@ -2790,6 +2840,21 @@ def test_event_check_subscription(check_types, app, admin_user): params={'csrfmiddlewaretoken': token}, status=302, ) + Booking.objects.all().delete() + + # event check is locked + event.check_locked = True + event.save() + app.post( + '/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk), + params={'csrfmiddlewaretoken': token}, + status=404, + ) + app.post( + '/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk), + params={'csrfmiddlewaretoken': token}, + status=404, + ) @mock.patch('chrono.manager.forms.get_agenda_check_types') -- 2.39.2