import codecs import datetime from unittest import mock import pytest import requests from django.core.management import call_command from django.db import connection from django.test.utils import CaptureQueriesContext from django.utils.timezone import localtime, make_aware, now from webtest import Upload from chrono.agendas.models import Agenda, Booking, Desk, Event, EventsType, Subscription from chrono.utils.lingo import CheckType from tests.utils import login pytestmark = pytest.mark.django_db def test_add_event(app, admin_user): events_type = EventsType.objects.create( label='Foo', custom_fields=[{'varname': 'foo', 'label': 'Foo', 'field_type': 'text'}] ) agenda = Agenda.objects.create(label='Foo bar', maximal_booking_delay=0, events_type=events_type) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert "This agenda doesn't have any event yet." in resp.text year = now().year + 1 resp = resp.click('New Event') assert 'custom_field_foo' not in resp.context['form'].fields resp.form['start_datetime_0'] = '%s-02-15' % year resp.form['start_datetime_1'] = '17:00' resp.form['places'] = 10 resp = resp.form.submit() resp = resp.follow() event = Event.objects.get(places=10) assert event.publication_datetime is None assert "This agenda doesn't have any event yet." not in resp.text assert '/manage/agendas/%s/events/%s/' % (agenda.id, event.id) in resp.text assert ('Feb. 15, %s, 5 p.m.' % year) in resp.text resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id) assert resp_datetimes.json['data'][0]['text'] == 'Feb. 15, %s, 5 p.m.' % year assert resp_datetimes.json['data'][0]['datetime'] == '%s-02-15 17:00:00' % year # add with errors in datetime parts for parts in ( ('', ''), ('invalid', ''), ('', 'invalid'), ('2019-02-24', 'invalid'), ('invalid', '17:00'), ): resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('New Event') resp.form['start_datetime_0'] = parts[0] resp.form['start_datetime_1'] = parts[1] resp.form['places'] = 10 resp = resp.form.submit() assert ( resp.text.count('Enter a valid date') or resp.text.count('Enter a valid time') == 1 or resp.text.count('This field is required.') >= 1 ) @pytest.mark.freeze_time('2021-05-06 14:00') def test_add_recurring_event(app, admin_user): agenda = Agenda.objects.create(label='Foo bar', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('New Event') resp.form['start_datetime_0'] = '2021-06-01' resp.form['start_datetime_1'] = '17:00' resp.form['places'] = 10 resp.form['frequency'] = 'unique' # not a recurring event resp.form['recurrence_days'] = [1] resp.form.submit().follow() event = Event.objects.get() assert event.recurrence_days is None event.delete() # add recurring event resp.form['frequency'] = 'recurring' resp.form.submit().follow() event = Event.objects.get(primary_event__isnull=True) assert event.recurrence_days == [1] assert Event.objects.filter(primary_event=event).count() == 49 event.delete() # add recurring event with end date resp.form['recurrence_end_date'] = '2021-07-01' resp.form.submit().follow() event = Event.objects.get(primary_event__isnull=True) assert event.recurrence_days == [1] assert Event.objects.filter(primary_event=event).count() == 5 # add recurring event with end date in a very long time resp.form['recurrence_end_date'] = '2030-01-01' resp = resp.form.submit() assert 'Recurrence end date cannot be more than 3 years from now' in resp.text def test_add_event_on_missing_agenda(app, admin_user): app = login(app) app.get('/manage/agendas/%s/add-event' % '0', status=404) def test_add_event_as_manager(app, manager_user): agenda = Agenda(label='Foo bar') agenda.view_role = manager_user.groups.all()[0] agenda.save() Desk.objects.create(agenda=agenda, slug='_exceptions_holder') app = login(app, username='manager', password='manager') resp = app.get('/manage/agendas/%s/' % agenda.id, status=302) app.get('/manage/agendas/%s/add-event' % agenda.id, status=403) agenda.edit_role = manager_user.groups.all()[0] agenda.save() resp = app.get('/manage/agendas/%s/settings' % agenda.pk) assert '

Settings' in resp.text resp = resp.click('New Event') resp.form['start_datetime_0'] = '2016-02-15' resp.form['start_datetime_1'] = '17:00' resp.form['places'] = 10 resp = resp.form.submit() resp = resp.follow() event = Event.objects.get(places=10) assert "This agenda doesn't have any event yet." not in resp.text assert '/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id) in resp.text assert 'Feb. 15, 2016, 5 p.m.' in resp.text assert event.duration is None assert event.end_datetime is None resp = resp.click('New Event') resp.form['start_datetime_0'] = '2016-02-15' resp.form['start_datetime_1'] = '17:00' resp.form['duration'] = 45 resp.form['places'] = 12 resp = resp.form.submit() resp = resp.follow() event = Event.objects.get(places=12) assert event.duration == 45 assert event.end_datetime == event.start_datetime + datetime.timedelta(minutes=45) def test_edit_event(settings, app, admin_user): settings.LANGUAGE_CODE = 'fr-fr' # check date initial value format agenda = Agenda.objects.create(label='Foo bar') event = Event.objects.create( label='Foo', start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=20, agenda=agenda, ) event2 = Event.objects.create( label='Other', start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=20, agenda=agenda, ) assert event.duration is None assert event.end_datetime is None other_agenda = Agenda.objects.create(label='Foo bar') other_event = Event.objects.create( label='Foo', start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=20, agenda=other_agenda, ) assert event.slug == other_event.slug app = login(app) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk)) assert resp.form['start_datetime_0'].value == '2016-02-15' assert resp.form['start_datetime_1'].value == '17:00' assert resp.form['publication_datetime_0'].value == '' assert resp.form['publication_datetime_1'].value == '' assert resp.form['duration'].value == '' assert resp.form['description'].value == '' resp.form['start_datetime_0'] = '2016-02-16' resp.form['start_datetime_1'] = '17:00' resp.form['publication_datetime_0'] = '2020-05-11' resp.form['publication_datetime_1'] = '12:00' resp.form['duration'].value = 45 resp.form['places'] = 20 resp.form['description'] = 'A description' resp = resp.form.submit() settings.LANGUAGE_CODE = 'en' resp = resp.follow() assert '/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id) in resp.text assert 'Feb. 16, 2016, 5 p.m.' in resp.text event.refresh_from_db() assert event.places == 20 assert str(event.publication_datetime) == '2020-05-11 10:00:00+00:00' assert str(event.publication_datetime.tzinfo) == 'UTC' assert event.duration == 45 assert event.end_datetime == event.start_datetime + datetime.timedelta(minutes=45) assert event.description == 'A description' assert event.slug == other_event.slug # check slug edition resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk)) resp.form['slug'] = event2.slug resp = resp.form.submit() assert resp.context['form'].errors['slug'] == ['Another event exists with the same identifier.'] def test_event_digit_slug(app, admin_user): agenda = Agenda(label='Foo bar') agenda.maximal_booking_delay = 0 agenda.save() event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10) app = login(app) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['slug'] = 42 resp = resp.form.submit() assert 'value cannot be a number' in resp.text def test_edit_missing_event(app, admin_user): app = login(app) app.get('/manage/agendas/999/', status=404) def test_edit_event_as_manager(app, manager_user): agenda = Agenda(label='Foo bar') agenda.view_role = manager_user.groups.all()[0] agenda.save() Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=20, agenda=agenda, publication_datetime=make_aware(datetime.datetime(2020, 5, 11)), ) app = login(app, username='manager', password='manager') resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id), status=403) agenda.edit_role = manager_user.groups.all()[0] agenda.save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('Feb. 15, 2016, 5 p.m.') assert resp.form['start_datetime_0'].value == '2016-02-15' assert resp.form['start_datetime_1'].value == '17:00' assert resp.form['publication_datetime_0'].value == '2020-05-11' assert resp.form['publication_datetime_1'].value == '00:00' resp.form['start_datetime_0'] = '2016-02-16' resp.form['start_datetime_1'] = '17:00' resp.form['publication_datetime_0'] = '' resp.form['publication_datetime_1'] = '' resp.form['places'] = 20 resp = resp.form.submit() resp = resp.follow() assert '/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id) in resp.text assert 'Feb. 16, 2016, 5 p.m.' in resp.text event.refresh_from_db() assert event.publication_datetime is None def test_edit_event_with_custom_fields(app, admin_user): events_type = EventsType.objects.create( label='Foo', custom_fields=[ {'varname': 'text', 'label': 'Text', 'field_type': 'text'}, {'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'}, {'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'}, ], ) agenda = Agenda.objects.create(label='Foo', kind='events') event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10) app = login(app) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk)) assert 'custom_field_text' not in resp.context['form'].fields assert 'custom_field_textarea' not in resp.context['form'].fields assert 'custom_field_bool' not in resp.context['form'].fields agenda.events_type = events_type agenda.save() resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk)) assert 'custom_field_text' in resp.context['form'].fields assert 'custom_field_textarea' in resp.context['form'].fields assert 'custom_field_bool' in resp.context['form'].fields resp.form.submit().follow() event.refresh_from_db() assert event.custom_fields == { 'text': '', 'textarea': '', 'bool': None, } resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk)) resp.form['custom_field_text'] = 'foo' resp.form['custom_field_textarea'] = 'foo bar' resp.form['custom_field_bool'] = 'true' resp.form.submit().follow() event.refresh_from_db() assert event.custom_fields == { 'text': 'foo', 'textarea': 'foo bar', 'bool': True, } resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk)) assert resp.form['custom_field_text'].value == 'foo' assert resp.form['custom_field_textarea'].value == 'foo bar' assert resp.form['custom_field_bool'].value == 'true' resp.form['custom_field_text'] = '' resp.form['custom_field_textarea'] = '' resp.form['custom_field_bool'] = 'false' resp.form.submit().follow() event.refresh_from_db() assert event.custom_fields == { 'text': '', 'textarea': '', 'bool': False, } resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk)) resp.form['custom_field_bool'] = 'unknown' resp.form.submit().follow() event.refresh_from_db() assert event.custom_fields == { 'text': '', 'textarea': '', 'bool': None, } def test_edit_recurring_event(settings, app, admin_user, freezer): freezer.move_to('2021-01-12 12:10') events_type = EventsType.objects.create( label='Foo', custom_fields=[{'varname': 'foo', 'label': 'Foo', 'field_type': 'text'}] ) agenda = Agenda.objects.create( label='Foo bar', kind='events', minimal_booking_delay=15, maximal_booking_delay=30, events_type=events_type, ) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create(start_datetime=now(), places=10, agenda=agenda) app = login(app) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['frequency'] = 'recurring' resp.form['recurrence_days'] = [localtime().weekday()] resp = resp.form.submit() # no end date, events are created for the year to come assert Event.objects.count() == 54 assert Event.objects.last().start_datetime.strftime('%Y-%m-%d') == '2022-01-11' # specifying end date removes events resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['recurrence_end_date'] = '2021-06-01' resp = resp.form.submit() assert Event.objects.count() == 21 assert Event.objects.last().start_datetime.strftime('%Y-%m-%d') == '2021-05-25' # detail page doesn't exist resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.id, event.id), status=404) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert 'On Tuesdays at 1:10 p.m.' in resp.text # event is bookable regardless of minimal_booking_delay, since it has bookable recurrences assert len(resp.pyquery.find('.bookable')) == 1 # maximal_booking_delay is accounted for, because no recurrences are bookable freezer.move_to('2020-11-12') resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert len(resp.pyquery.find('.not-bookable')) == 1 # editing recurring event updates event recurrences event.refresh_from_db() event_recurrence = Event.objects.get(primary_event=event, start_datetime=event.start_datetime) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['places'] = 20 resp.form['custom_field_foo'] = 'bar' resp = resp.form.submit().follow() event_recurrence.refresh_from_db() assert event_recurrence.places == 20 assert event_recurrence.custom_fields == {'foo': 'bar'} event.refresh_from_db() assert event.custom_fields == {'foo': 'bar'} # but some fields should not be updated assert event_recurrence.slug != event.slug assert not event_recurrence.recurrence_days # changing recurrence attribute removes event recurrences resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['frequency'] = 'unique' resp = resp.form.submit().follow() assert not Event.objects.filter(primary_event=event).exists() # same goes with changing slug event.recurrence_days = [1] event.save() event.create_all_recurrences() event_recurrence = Event.objects.get(primary_event=event, start_datetime=event.start_datetime) assert Event.objects.filter(primary_event=event, slug__startswith='foo-bar-event').exists() resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['slug'] = 'hop' resp = resp.form.submit().follow() assert not Event.objects.filter(primary_event=event, slug__startswith='foo-bar-event').exists() # changing recurring attribute or slug is forbidden if there are bookings for future recurrences event_recurrence = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=7) ) Booking.objects.create(event=event_recurrence) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) assert 'disabled' in resp.form['frequency'].attrs assert all('disabled' in resp.form.get('recurrence_days', index=i).attrs for i in range(7)) assert 'disabled' in resp.form['recurrence_week_interval'].attrs assert 'disabled' in resp.form['slug'].attrs assert 'disabled' in resp.form['start_datetime_0'].attrs assert 'disabled' in resp.form['start_datetime_1'].attrs # changing it anyway doesn't work resp.form['slug'] = 'changed' resp = resp.form.submit() assert not Event.objects.filter(slug='changed').exists() # deletion of event recurrence is not allowed resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.id, event_recurrence.id)) assert 'Delete' not in resp.text resp = resp.click('Options') assert 'Delete' not in resp.text assert { 'slug', 'frequency', 'recurrence_days', 'recurence_weekly_interval', 'recurrence_end_date', 'publication_datetime_0', 'publication_datetime_1', 'custom_field_foo', }.isdisjoint(resp.form.fields) resp.form.submit().follow() # custom fields not changed event_recurrence.refresh_from_db() assert event_recurrence.custom_fields == {'foo': 'bar'} def test_edit_recurring_event_with_end_date(settings, app, admin_user, freezer): freezer.move_to('2021-01-12 12:10') agenda = Agenda.objects.create(label='Foo bar', kind='events') event = Event.objects.create( start_datetime=now(), places=10, recurrence_days=list(range(7)), agenda=agenda ) app = login(app) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['recurrence_end_date'] = (localtime() + datetime.timedelta(days=5)).strftime('%Y-%m-%d') resp = resp.form.submit() # recurrences are created automatically event = Event.objects.get(recurrence_days__isnull=False) assert Event.objects.filter(primary_event=event).count() == 5 assert Event.objects.filter(primary_event=event, start_datetime=now()).exists() resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['start_datetime_1'] = (localtime() + datetime.timedelta(hours=1)).strftime('%H:%M') resp = resp.form.submit() assert Event.objects.filter(primary_event=event).count() == 5 assert Event.objects.filter( primary_event=event, start_datetime=now() + datetime.timedelta(hours=1) ).exists() # old recurrences were deleted assert not Event.objects.filter(primary_event=event, start_datetime=now()).exists() # if start datetime of a recurrence is edited, it stays that way recurrence = event.recurrences.first() recurrence.start_datetime += datetime.timedelta(hours=1) recurrence.save() resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.form.submit() assert Event.objects.filter(primary_event=event).count() == 5 assert Event.objects.filter(primary_event=event, start_datetime=recurrence.start_datetime).count() == 1 # ensure recurrence_end_date has not been propagated assert not Event.objects.filter(primary_event=event, recurrence_end_date__isnull=False).exists() # editing recurrence_end_date is permitted as long as bookings are not impacted event_recurrence = Event.objects.get(primary_event=event, start_datetime__day=15) Booking.objects.create(event=event_recurrence) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['recurrence_end_date'] = (localtime() + datetime.timedelta(days=6)).strftime('%Y-%m-%d') resp = resp.form.submit() assert Event.objects.filter(primary_event=event).count() == 6 resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['recurrence_end_date'] = (localtime() + datetime.timedelta(days=4)).strftime('%Y-%m-%d') resp = resp.form.submit() assert Event.objects.filter(primary_event=event).count() == 4 resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp.form['recurrence_end_date'] = (localtime() + datetime.timedelta(days=2)).strftime('%Y-%m-%d') resp = resp.form.submit() assert Event.objects.filter(primary_event=event).count() == 4 assert 'Bookings exist after this date' in resp.text def test_edit_booked_event_disable_frequency_choice(settings, app, admin_user, freezer): freezer.move_to('2021-01-12 12:10') agenda = Agenda.objects.create(label='Foo bar', kind='events') event = Event.objects.create(start_datetime=now(), places=10, agenda=agenda) app = login(app) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) assert 'disabled' not in resp.form['frequency'].attrs assert 'This field will not be editable once event has bookings.' in resp.text Booking.objects.create(event=event) resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) assert 'disabled' in resp.form['frequency'].attrs assert 'cannot be modified' in resp.form['frequency'].attrs['title'] assert 'This field will not be editable once event has bookings.' not in resp.text def test_booked_places(app, admin_user): agenda = Agenda(label='Foo bar') agenda.save() event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda) event.save() Booking(event=event).save() Booking(event=event).save() app = login(app) day = event.start_datetime resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month), status=200) assert '8 remaining places' in resp.text assert '(2/10 bookings)' in resp.text def test_event_classes(app, admin_user): agenda = Agenda(label='Foo bar') agenda.save() Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda) event.save() for _ in range(2): Booking(event=event).save() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert 'full' not in resp.text assert 'overbooking' not in resp.text resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk)) assert 'Full' not in resp resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Full' not in resp for _ in range(8): Booking(event=event).save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert 'full' in resp.text assert 'overbooking' not in resp.text resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk)) assert 'Full' in resp resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Full' in resp Booking(event=event).save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert 'full' in resp.text assert 'overbooking' in resp.text def test_event_detail_backoffice_url_translation(app, admin_user): agenda = Agenda(label='Foo bar') agenda.save() Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda) event.save() Booking.objects.create(event=event, backoffice_url='publik://default/foo/') app = login(app) resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk)) assert 'http://example.org/foo/' in resp.text def test_delete_event(app, admin_user): agenda = Agenda(label='Foo bar') agenda.save() Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda) event.save() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id) assert Event.objects.count() == 0 def test_delete_busy_event(app, admin_user): agenda = Agenda(label='Foo bar') agenda.save() Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event(start_datetime=now() + datetime.timedelta(days=10), places=10, agenda=agenda) event.save() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'Are you sure you want to delete this event?' in resp.text booking = Booking(event=event) booking.save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'This cannot be removed' in resp.text booking.cancellation_datetime = now() booking.save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'Are you sure you want to delete this event?' in resp.text # suddenly the booking is no longer cancelled, but the admin clicks on the # delete button. booking.cancellation_datetime = None booking.save() resp = resp.form.submit(status=403) def test_delete_recurring_event(app, admin_user, freezer): agenda = Agenda.objects.create(label='Foo bar', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') start_datetime = now() + datetime.timedelta(days=10) event = Event.objects.create( start_datetime=start_datetime, places=10, agenda=agenda, recurrence_days=[start_datetime.weekday()], recurrence_end_date=start_datetime + datetime.timedelta(days=15), ) event.create_all_recurrences() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'Are you sure you want to delete this event?' in resp.text event_recurrence = Event.objects.get(primary_event=event, start_datetime=event.start_datetime) booking = Booking.objects.create(event=event_recurrence) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'This cannot be removed' in resp.text booking.cancellation_datetime = now() booking.save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'Are you sure you want to delete this event?' in resp.text booking.cancellation_datetime = None booking.save() freezer.move_to(now() + datetime.timedelta(days=11)) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'Are you sure you want to delete this event?' in resp.text def test_delete_event_as_manager(app, manager_user): agenda = Agenda(label='Foo bar') agenda.edit_role = manager_user.groups.all()[0] agenda.save() Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda) event.save() app = login(app, username='manager', password='manager') resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id) assert Event.objects.count() == 0 def test_export_events(app, admin_user): agenda = Agenda.objects.create(label='Foo bar') app = login(app) resp = app.get('/manage/agendas/%s/export-events' % agenda.id) csv_export = resp.text assert ( csv_export == 'date,time,number of places,number of places in waiting list,label,identifier,description,pricing,URL,publication date/time,duration\r\n' ) resp = app.get('/manage/agendas/%s/import-events' % agenda.id) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,00:30,10', 'text/csv') resp.form.submit(status=302) resp = app.get('/manage/agendas/%s/export-events' % agenda.id) csv_export = resp.text assert ( csv_export == 'date,time,number of places,number of places in waiting list,label,identifier,description,pricing,URL,publication date/time,duration\r\n' '2016-09-16,00:30,10,0,,foo-bar-event,,,,,\r\n' ) resp = app.get('/manage/agendas/%s/import-events' % agenda.id) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,23:30,10,5,label,slug,"description\nfoobar",pricing,url,2016-10-16 00:00,90', 'text/csv', ) resp.form.submit(status=302) resp = app.get('/manage/agendas/%s/export-events' % agenda.id) csv_export = resp.text assert ( csv_export == 'date,time,number of places,number of places in waiting list,label,identifier,description,pricing,URL,publication date/time,duration\r\n' '2016-09-16,00:30,10,0,,foo-bar-event,,,,,\r\n' '2016-09-16,23:30,10,5,label,slug,"description\nfoobar",pricing,url,2016-10-16 00:00,90\r\n' ) def test_export_events_wrong_kind(app, admin_user): agenda = Agenda.objects.create(label='Foo bar', kind='meetings') app = login(app) app.get('/manage/agendas/%s/export-events' % agenda.id, status=404) agenda.kind = 'virtual' agenda.save() app.get('/manage/agendas/%s/export-events' % agenda.id, status=404) def test_import_events(app, admin_user): agenda = Agenda(label='Foo bar') agenda.save() Desk.objects.create(agenda=agenda, slug='_exceptions_holder') app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('Import Events') sample_csv_resp = resp.click('Download sample file') assert sample_csv_resp.content_type == 'text/csv' assert sample_csv_resp.text.startswith('date,time') resp.form['events_csv_file'] = Upload('t.csv', sample_csv_resp.content, 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'xx', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format.' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'xxxx\0\0xxxx', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format.' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-14-16,18:00', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format.' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-14-16,18:00,10', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format. (date/time format' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,blah', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format. (number of places,' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,blah', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format. (number of places in waiting list,' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,' + b'x' * 151, 'text/csv') resp = resp.form.submit(status=200) assert 'Ensure this value has at most 150' in resp.text resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10', 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5', 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', '2016-09-16,18:00,10,5,éléphant'.encode(), 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 assert Event.objects.all()[0].label == 'éléphant' Event.objects.all().delete() # BOM resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', codecs.BOM_UTF8 + '2016-09-16,18:00,10,5,éléphant'.encode(), 'text/csv' ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 assert Event.objects.all()[0].label == 'éléphant' Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', '2016-09-16,18:00,10,5,éléphant'.encode('iso-8859-15'), 'text/csv' ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 assert Event.objects.all()[0].label == 'éléphant' Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', '2016-09-16,18:00,10,5,éléphant'.encode('eucjp'), 'text/csv' ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 assert Event.objects.all()[0].label == '\x8f«±l\x8f«±phant' # eucjp interpreted as iso-8859-15 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'date,time,etc.\n' b'2016-09-16,18:00,10,5,bla bla bla\n' b'\n' b'2016-09-19,18:00,10', 'text/csv', ) resp = resp.form.submit(status=302) assert Event.objects.count() == 2 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', '"date"\t"time"\t"etc."\n' '"2016-09-16"\t"18:00"\t"10"\t"5"\t"éléphant"\n' '"2016-09-19"\t"18:00"\t"10"'.encode('iso-8859-15'), 'text/csv', ) resp = resp.form.submit(status=302) assert Event.objects.count() == 2 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,slug', 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 event = Event.objects.latest('pk') assert event.start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert event.places == 10 assert event.waiting_list_places == 5 assert event.label == 'label' assert event.slug == 'slug' resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,slug', 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 event = Event.objects.latest('pk') assert event.slug == 'slug' # additional optional attributes Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,slug,,,,,', 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 event = Event.objects.get() assert event.description == '' assert event.pricing == '' assert event.url == '' assert event.publication_datetime is None assert event.duration is None Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,slug,"description\nfoobar",pricing,url,2016-10-16,90', 'text/csv', ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 event = Event.objects.get() assert event.description == 'description\nfoobar' assert event.pricing == 'pricing' assert event.url == 'url' assert str(event.publication_datetime) == '2016-10-15 22:00:00+00:00' assert str(event.publication_datetime.tzinfo) == 'UTC' assert event.duration == 90 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,slug,"description\nfoobar",pricing,url,2016-10-16 10:00,90', 'text/csv', ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 event = Event.objects.get() assert event.description == 'description\nfoobar' assert event.pricing == 'pricing' assert event.url == 'url' assert str(event.publication_datetime) == '2016-10-16 08:00:00+00:00' assert str(event.publication_datetime.tzinfo) == 'UTC' assert event.duration == 90 # publication date/time bad format resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,slug,description,pricing,url,foobar', 'text/csv' ) resp = resp.form.submit(status=200) assert 'Invalid file format. (date/time format' in resp.text # duration bad format resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,slug,description,pricing,url,2016-09-16,foobar', 'text/csv' ) resp = resp.form.submit(status=200) assert 'Invalid file format. (duration' in resp.text # import events with empty slugs Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,labela,labelb,,pricing,\n' b'2016-09-17,18:00,10,5,labela,labelb-1,,pricing,\n' b'2016-09-18,18:00,10,5,labela,labelb-2,,pricing,\n' b'2016-09-18,18:00,10,5,labelb,,,pricing,\n' b'2016-09-18,18:00,10,5,labelb,,,pricing,\n', 'text/csv', ) with CaptureQueriesContext(connection) as ctx: resp = resp.form.submit(status=302) assert len(ctx.captured_queries) == 22 assert Event.objects.count() == 5 assert set(Event.objects.values_list('slug', flat=True)) == { 'labelb', 'labelb-1', 'labelb-2', 'labelb-3', 'labelb-4', } # forbidden numerical slug Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,1234', 'text/csv') resp = resp.form.submit(status=200) assert 'value cannot be a number' in resp.text assert 'Identifier:' in resp.text # verbose_name is shown, not field name ('slug:') def test_import_events_existing_event(app, admin_user, freezer): agenda = Agenda.objects.create(label='Foo bar') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') app = login(app) resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,slug\n2016-09-16,18:00,10,5,label,slug\n', 'text/csv', ) resp.form.submit(status=302) assert agenda.event_set.count() == 1 event = Event.objects.latest('pk') def check_import(date, time, with_alert): resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'%s,%s,10,5,label,slug\n' % (date.encode(), time.encode()), 'text/csv', ) resp = resp.form.submit(status=302).follow() assert agenda.event_set.count() == 1 event.refresh_from_db() if with_alert: assert ( '
  • Event "label" start date has changed. Do not forget to notify the registrants.
  • ' in resp.text ) else: assert ( '
  • Event "label" start date has changed. Do not forget to notify the registrants.
  • ' not in resp.text ) assert event.start_datetime == make_aware( datetime.datetime(*(int(v) for v in date.split('-')), *(int(v) for v in time.split(':'))) ) # change date or time # event in the past, no alert, with or without booking Booking.objects.create( event=event, cancellation_datetime=make_aware(datetime.datetime(2017, 5, 20, 10, 30)) ) check_import('2016-09-15', '18:00', False) # change date check_import('2016-09-15', '17:00', False) # change time # available booking Booking.objects.create(event=event) check_import('2016-09-14', '17:00', False) # change date check_import('2016-09-14', '16:00', False) # change time # date in the future freezer.move_to('2016-09-01') # warn if available booking only check_import('2016-09-13', '16:00', True) # change date check_import('2016-09-13', '15:00', True) # change time # no available booking Booking.objects.all().delete() Booking.objects.create( event=event, cancellation_datetime=make_aware(datetime.datetime(2017, 5, 20, 10, 30)) ) check_import('2016-09-12', '15:00', False) # change date check_import('2016-09-12', '14:00', False) # change time # check there is a message per changed event resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,slug\n2016-09-16,19:00,10,5,label,other_slug\n', 'text/csv', ) resp.form.submit(status=302) assert agenda.event_set.count() == 2 event2 = Event.objects.latest('pk') Booking.objects.create(event=event) Booking.objects.create(event=event2) resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-17,18:00,10,5,label,slug\n2016-09-17,19:00,10,5,,other_slug\n2016-09-17,20:00,10,5,,other_slug\n', 'text/csv', ) resp = resp.form.submit(status=302).follow() assert agenda.event_set.count() == 2 assert ( resp.text.count( 'Event "label" start date has changed. Do not forget to notify the registrants.' ) == 1 ) assert ( resp.text.count( 'Event "other_slug" start date has changed. Do not forget to notify the registrants.' ) == 1 ) def test_import_events_wrong_kind(app, admin_user): agenda = Agenda.objects.create(label='Foo bar', kind='meetings') app = login(app) app.get('/manage/agendas/%s/import-events' % agenda.id, status=404) agenda.kind = 'virtual' agenda.save() app.get('/manage/agendas/%s/import-events' % agenda.id, status=404) @pytest.mark.freeze_time('2022-05-24') def test_event_detail(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, waiting_list_places=2, agenda=agenda, ) Booking.objects.create(event=event, user_last_name="User's 1") Booking.objects.create(event=event, user_last_name='User 2', in_waiting_list=True) login(app) resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) assert 'Bookings (1/10)' in resp.text assert 'User's 1, May 24, 2022, 2 a.m.' in resp.text assert 'Waiting List (1/2): 1 remaining place' in resp.text assert 'User 2, May 24, 2022, 2 a.m.' in resp.text agenda.booking_user_block_template = '{{ booking.user_name }} Foo Bar' agenda.save() resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) assert 'Bookings (1/10)' in resp.text assert '<b>User's 1</b> Foo Bar, May 24, 2022, 2 a.m.' in resp.text assert 'Waiting List (1/2): 1 remaining place' in resp.text assert '<b>User 2</b> Foo Bar, May 24, 2022, 2 a.m.' in resp.text def test_event_cancellation(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda ) login(app) resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) assert 'Bookings (0/10)' in resp.text resp = resp.click('Cancel', href='/cancel') assert 'related bookings' not in resp.text Booking.objects.create(event=event, user_last_name='User 1') Booking.objects.create(event=event, user_last_name='User 2') resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) assert 'Bookings (2/10)' in resp.text resp = resp.click('Cancel', href='manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk)) assert '2 related bookings will also be cancelled.' in resp.text resp = resp.form.submit().follow() assert 'Cancelled' in resp.text assert 'Bookings (0/10)' in resp.text assert Booking.objects.filter(event=event, cancellation_datetime__isnull=False).count() == 2 resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) assert '/manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk) not in resp.text def test_event_cancellation_error_report(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda ) day = event.start_datetime def mocked_requests_connection_error(*args, **kwargs): raise requests.exceptions.ConnectionError('unreachable') for _ in range(5): Booking.objects.create(event=event, cancel_callback_url='http://example.org/jump/trigger/') login(app) resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month)) resp = resp.click('Cancellation error reports') assert 'No error report' in resp.text resp = app.get('/manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk)) resp = resp.form.submit().follow() assert 'Cancellation in progress' in resp.text with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send: mock_response = mock.Mock(status_code=200) mock_send.return_value = mock_response mock_send.side_effect = mocked_requests_connection_error call_command('cancel_events') event.refresh_from_db() assert not event.cancelled and not event.cancellation_scheduled assert not Booking.objects.filter(cancellation_datetime__isnull=False).exists() resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month)) assert 'Errors occured during cancellation of event "xyz".' in resp.text # warning doesn't go away resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month)) assert 'Errors occured during cancellation of event "xyz".' in resp.text resp = resp.click('Details') assert resp.text.count('unreachable') == 5 resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month)) assert 'Errors occured during cancellation of event "xyz".' not in resp.text resp = resp.click('Cancellation error reports') assert '(5 failures)' in resp.text resp = resp.click(str(event)) resp = resp.click('Force cancellation') resp = resp.form.submit().follow() event.refresh_from_db() assert event.cancelled and not event.cancellation_scheduled assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 5 def test_event_cancellation_error_report_backofice_url_translation(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda ) day = event.start_datetime def mocked_requests_connection_error(*args, **kwargs): raise requests.exceptions.ConnectionError('unreachable') for _ in range(5): Booking.objects.create( event=event, cancel_callback_url='http://example.org/jump/trigger/', backoffice_url='publik://default/', ) login(app) resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month)) resp = resp.click('Cancellation error reports') assert 'No error report' in resp.text resp = app.get('/manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk)) resp = resp.form.submit().follow() assert 'Cancellation in progress' in resp.text with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send: mock_response = mock.Mock(status_code=200) mock_send.return_value = mock_response mock_send.side_effect = mocked_requests_connection_error call_command('cancel_events') event.refresh_from_db() assert not event.cancelled and not event.cancellation_scheduled assert not Booking.objects.filter(cancellation_datetime__isnull=False).exists() resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month)) assert 'Errors occured during cancellation of event "xyz".' in resp.text resp = resp.click('Cancellation error reports') assert '(5 failures)' in resp.text resp = resp.click(str(event)) assert 'http://example.org/' in resp.text def test_event_cancellation_forbidden(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda ) Booking.objects.create(event=event) booking = Booking.objects.create(event=event, backoffice_url='http://example.org/backoffice/xx/') login(app) resp = app.get('/manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk)) assert 'event has bookings with no callback url configured' in resp.text assert 'Proceed with cancellation' not in resp.text booking.cancel() resp = app.get('/manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk)) assert 'event has bookings with no callback url configured' not in resp.text assert 'Proceed with cancellation' in resp.text def test_event_booking_form_url(settings, app, admin_user): settings.TEMPLATE_VARS = {'eservices_url': 'http://demarches/'} agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda ) day = event.start_datetime login(app) assert event.get_booking_form_url() is None resp = app.get('/manage/agendas/%d/%d/%d/' % (agenda.pk, day.year, day.month)) assert 'Booking form' not in resp.text resp = app.get('/manage/agendas/%d/events/open/' % agenda.pk) assert 'Booking form' not in resp.text resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) assert 'Booking form' not in resp.text agenda.booking_form_url = '{{ eservices_url }}backoffice/submission/inscription-aux-activites/' agenda.save() assert ( event.get_booking_form_url() == 'http://demarches/backoffice/submission/inscription-aux-activites/?agenda=%s&event=%s' % (agenda.slug, event.slug) ) resp = app.get('/manage/agendas/%d/%d/%d/' % (agenda.pk, day.year, day.month)) assert ( 'Booking form' % ( agenda.slug, event.slug, 'http://testserver/manage/agendas/%d/%d/%d/' % (agenda.pk, day.year, day.month), ) in resp.text ) resp = app.get('/manage/agendas/%d/events/open/' % agenda.pk) assert ( 'Booking form' % (agenda.slug, event.slug, 'http://testserver/manage/agendas/%d/events/open/' % agenda.pk) in resp.text ) resp = app.get('/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) assert ( 'Booking form' % (agenda.slug, event.slug, 'http://testserver/manage/agendas/%d/events/%d/' % (agenda.pk, event.pk)) in resp.text ) def test_booking_cancellation_events_agenda(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event(label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda) event.save() booking = Booking.objects.create(event=event) login(app) resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.id, event.id)) assert 'Bookings (1/10)' in resp.text resp = resp.click('Cancel', href='bookings/') resp = resp.form.submit() assert resp.location.endswith('/manage/agendas/%s/events/%s/' % (agenda.id, event.id)) booking.refresh_from_db() assert booking.cancellation_datetime resp = resp.follow() assert 'Bookings (0/10)' in resp.text # again app.get('/manage/agendas/%s/bookings/%s/cancel' % (agenda.pk, booking.pk), status=404) # test secondary booking primary = Booking.objects.create(event=event) secondary = Booking.objects.create(event=event, primary_booking=primary) resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk)) assert 'Bookings (2/10)' in resp.text assert '/manage/agendas/%s/bookings/%s/cancel' % (agenda.pk, primary.pk) in resp.text assert '/manage/agendas/%s/bookings/%s/cancel' % (agenda.pk, secondary.pk) not in resp.text app.get('/manage/agendas/%s/bookings/%s/cancel' % (agenda.pk, secondary.pk), status=404) app.get('/manage/agendas/%s/bookings/%s/cancel' % (agenda.pk, primary.pk)).form.submit() primary.refresh_from_db() secondary.refresh_from_db() assert primary.cancellation_datetime assert secondary.cancellation_datetime def test_event_check(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') agenda2 = Agenda.objects.create(label='Events', kind='events') Desk.objects.create(agenda=agenda2, slug='_exceptions_holder') event = Event.objects.create( label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, waiting_list_places=5, agenda=agenda, ) booking1 = Booking.objects.create( event=event, user_external_id='user:1', user_first_name='User', user_last_name='42' ) Booking.objects.create( event=event, user_external_id='user:2', user_first_name="User's", user_last_name='01' ) Booking.objects.create( event=event, user_external_id='user:3', user_first_name='User', user_last_name='17' ) Booking.objects.create( event=event, user_external_id='user:4', user_first_name='User', user_last_name='35' ) Booking.objects.create( event=event, user_external_id='user:5', user_first_name='User', user_last_name='05' ) booking6 = Booking.objects.create( event=event, user_external_id='user:6', user_first_name='User', user_last_name='12 Cancelled' ) booking6.cancel() booking7 = Booking.objects.create( event=event, user_external_id='user:7', user_first_name='User', user_last_name='Waiting', in_waiting_list=True, ) booking8 = Booking.objects.create( event=event, user_external_id='user:8', user_first_name='User', user_last_name='Waiting and Cancelled', in_waiting_list=True, ) booking8.cancel() booking9 = Booking.objects.create( event=event, user_external_id='user:1', user_first_name='User', user_last_name='Secondary', primary_booking=booking1, ) login(app) # event not in past resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk)) assert '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk) not in resp app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), status=404) event.start_datetime = localtime(now()).replace(hour=22, minute=0) # it's ok all the day event.save() resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk)) assert '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk) in resp # unknown agenda app.get('/manage/agendas/%s/events/%s/check' % (0, event.pk), status=404) # wrong agenda app.get('/manage/agendas/%s/events/%s/check' % (agenda2.pk, event.pk), status=404) resp = resp.click('Check') assert ( resp.text.index('Bookings (6/10)') < resp.text.index("User's 01") < resp.text.index('User 05') < resp.text.index('User 17') < resp.text.index('User 35') < resp.text.index('User 42') < resp.text.index('Waiting List (1/5)') < resp.text.index('User Waiting') ) assert 'User 12 Cancelled' not in resp assert 'User Waiting and Cancelled' not in resp Subscription.objects.create( agenda=agenda, user_external_id='user:1', user_first_name='Subscription', user_last_name='42', date_start=now(), date_end=now() + datetime.timedelta(days=1), ) Subscription.objects.create( agenda=agenda, user_external_id='user:9', user_first_name='Subscription', user_last_name='43', date_start=now(), date_end=now() + datetime.timedelta(days=1), ) Subscription.objects.create( agenda=agenda, user_external_id='user:10', user_first_name='Subscription', user_last_name='14', date_start=now(), date_end=now() + datetime.timedelta(days=1), ) Subscription.objects.create( agenda=agenda, user_external_id='user:7', user_first_name='Subscription', user_last_name='Waiting', date_start=now(), date_end=now() + datetime.timedelta(days=1), ) Subscription.objects.create( agenda=agenda, user_external_id='user:42', user_first_name='Subscription', user_last_name='Too soon', date_start=now() - datetime.timedelta(days=1), date_end=now(), ) Subscription.objects.create( agenda=agenda, user_external_id='user:42', user_first_name='Subscription', user_last_name='Too late', date_start=now() + datetime.timedelta(days=1), date_end=now() + datetime.timedelta(days=2), ) resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert ( resp.text.index('Bookings (6/10)') < resp.text.index("User's 01") < resp.text.index('User 05') < resp.text.index('User 12 Cancelled') < resp.text.index('Subscription 14') < resp.text.index('(Not booked)') < resp.text.index('User 17') < resp.text.index('User 35') < resp.text.index('User 42') < resp.text.index('Subscription 43') < resp.text.index('Waiting List (1/5)') < resp.text.index('User Waiting') < resp.text.index('User Waiting and Cancelled') ) assert 'Subscription Waiting' not in resp assert 'Subscription 42' not in resp assert 'Subscription too soon' not in resp assert 'Subscription too late' not in resp agenda.booking_user_block_template = '{{ booking.user_name }} Foo Bar' agenda.save() resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert '<b>User's 01</b> Foo Bar' in resp assert '<b>Subscription 14</b> Foo Bar' in resp assert '<b>User Waiting</b> Foo Bar' in resp # cancelled booking token = resp.context['csrf_token'] app.post( '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking6.pk), params={'csrfmiddlewaretoken': token}, status=404, ) app.post( '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking6.pk), params={'csrfmiddlewaretoken': token}, status=404, ) # booking in waiting list app.post( '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking7.pk), params={'csrfmiddlewaretoken': token}, status=404, ) app.post( '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking7.pk), params={'csrfmiddlewaretoken': token}, status=404, ) # secondary booking app.post( '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking9.pk), params={'csrfmiddlewaretoken': token}, status=404, ) app.post( '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking9.pk), params={'csrfmiddlewaretoken': token}, status=404, ) # unknown agenda app.post( '/manage/agendas/%s/bookings/%s/presence' % (0, booking1.pk), params={'csrfmiddlewaretoken': token}, status=404, ) app.post( '/manage/agendas/%s/bookings/%s/absence' % (0, booking1.pk), params={'csrfmiddlewaretoken': token}, status=404, ) # wrong agenda app.post( '/manage/agendas/%s/bookings/%s/presence' % (agenda2.pk, booking1.pk), params={'csrfmiddlewaretoken': token}, status=404, ) app.post( '/manage/agendas/%s/bookings/%s/absence' % (agenda2.pk, booking1.pk), params={'csrfmiddlewaretoken': token}, status=404, ) # cancelled event event.cancel() resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk)) assert '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk) not in resp app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), status=404) def test_event_checked(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events', booking_check_filters='foo,bar') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( label='xyz', start_datetime=now() - datetime.timedelta(days=1), places=10, agenda=agenda, ) login(app) resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Mark the event as checked' not in resp Booking.objects.create(event=event, user_first_name='User') resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Checked' not in resp assert 'Mark the event as checked' in resp assert event.checked is False resp = app.get('/manage/agendas/%s/settings' % agenda.id) assert 'checked tag' not in resp resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk)) assert 'Checked' not in resp token = resp.context['csrf_token'] resp = app.post( '/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token}, ) assert resp.location.endswith('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) event.refresh_from_db() assert event.checked is True resp = resp.follow() assert 'Checked' in resp assert 'Mark the event as checked' not in resp resp = app.get('/manage/agendas/%s/settings' % agenda.id) assert 'checked tag' in resp resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk)) assert 'Checked' in resp @mock.patch('chrono.manager.forms.get_agenda_check_types') def test_event_check_filters(check_types, app, admin_user): check_types.return_value = [ CheckType(slug='foo-reason', label='Foo reason', kind='absence'), CheckType(slug='bar-reason', label='Bar reason', kind='presence'), ] agenda = Agenda.objects.create( label='Events', kind='events', booking_check_filters='foo,bar', ) event = Event.objects.create( label='xyz', start_datetime=now() - datetime.timedelta(days=1), places=10, agenda=agenda, ) Booking.objects.create( event=event, user_external_id='user:none', user_first_name='User', user_last_name='none' ) Booking.objects.create( event=event, user_external_id='user:empty', user_first_name='User', user_last_name='empty', extra_data={}, ) Booking.objects.create( event=event, user_external_id='user:1', user_first_name='User', user_last_name='foo-val1 bar-none presence', extra_data={'foo': 'val1'}, user_was_present=True, ) Booking.objects.create( event=event, user_external_id='user:2', user_first_name='User', user_last_name='foo-val2 bar-val1 absence', extra_data={'foo': 'val2', 'bar': 'val1'}, user_was_present=False, ) Booking.objects.create( event=event, user_external_id='user:3', user_first_name='User', user_last_name='foo-val1 bar-val2 not-checked', extra_data={'foo': 'val1', 'bar': 'val2'}, ) Booking.objects.create( event=event, user_external_id='user:4', user_first_name='User', user_last_name='foo-none bar-val2 reason-foo', extra_data={'bar': 'val2'}, user_was_present=False, user_check_type_slug='foo-reason', ) Booking.objects.create( event=event, user_external_id='user:5', user_first_name='User', user_last_name='foo-none bar-val2 reason-bar', extra_data={'bar': 'val2'}, user_was_present=True, user_check_type_slug='bar-reason', ) Booking.objects.create( event=event, user_external_id='user:6', user_first_name='User', user_last_name='foo-none bar-val2 cancelled-absence', extra_data={'bar': 'val2'}, user_was_present=False, user_check_type_slug='foo-reason', cancellation_datetime=now(), ) Booking.objects.create( event=event, user_external_id='user:7', user_first_name='User', user_last_name='foo-none bar-val2 cancelled-presence', extra_data={'bar': 'val2'}, user_was_present=True, user_check_type_slug='bar-reason', cancellation_datetime=now(), ) Subscription.objects.create( agenda=agenda, user_external_id='subscription:none', user_first_name='Subscription', user_last_name='none', date_start=event.start_datetime, date_end=event.start_datetime + datetime.timedelta(days=1), ) Subscription.objects.create( agenda=agenda, user_external_id='subscription:empty', user_first_name='Subscription', user_last_name='empty', extra_data={}, date_start=event.start_datetime, date_end=event.start_datetime + datetime.timedelta(days=1), ) Subscription.objects.create( agenda=agenda, user_external_id='subscription:1', user_first_name='Subscription', user_last_name='foo-val1 bar-none', extra_data={'foo': 'val1'}, date_start=event.start_datetime, date_end=event.start_datetime + datetime.timedelta(days=1), ) Subscription.objects.create( agenda=agenda, user_external_id='subscription:2', user_first_name='Subscription', user_last_name='foo-val2 bar-val1', extra_data={'foo': 'val2', 'bar': 'val1'}, date_start=event.start_datetime, date_end=event.start_datetime + datetime.timedelta(days=1), ) Subscription.objects.create( agenda=agenda, user_external_id='subscription:3', user_first_name='Subscription', user_last_name='foo-val1 bar-val2', extra_data={'foo': 'val1', 'bar': 'val2'}, date_start=event.start_datetime, date_end=event.start_datetime + datetime.timedelta(days=1), ) Subscription.objects.create( agenda=agenda, user_external_id='subscription:4', user_first_name='Subscription', user_last_name='foo-none bar-val2', extra_data={'bar': 'val2'}, date_start=event.start_datetime, date_end=event.start_datetime + datetime.timedelta(days=1), ) login(app) for params in [ {}, {'unknown': 'unknown'}, {'extra-data-unknown': 'unknown'}, {'extra-data-foo': 'unknown'}, ]: resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params=params) assert 'User none' in resp assert 'User empty' in resp assert 'User foo-val1 bar-none presence' in resp assert 'User foo-val2 bar-val1 absence' in resp assert 'User foo-val1 bar-val2 not-checked' in resp assert 'User foo-none bar-val2 reason-foo' in resp assert 'User foo-none bar-val2 reason-bar' in resp assert 'User foo-none bar-val2 cancelled-absence' in resp assert 'User foo-none bar-val2 cancelled-presence' in resp assert 'Subscription none' in resp assert 'Subscription empty' in resp assert 'Subscription foo-val1 bar-none' in resp assert 'Subscription foo-val2 bar-val1' in resp assert 'Subscription foo-val1 bar-val2' in resp assert 'Subscription foo-none bar-val2' in resp with CaptureQueriesContext(connection) as ctx: resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'extra-data-foo': 'val1'} ) assert len(ctx.captured_queries) == 10 assert 'User none' not in resp assert 'User empty' not in resp assert 'User foo-val1 bar-none presence' in resp assert 'User foo-val2 bar-val1 absence' not in resp assert 'User foo-val1 bar-val2 not-checked' in resp assert 'User foo-none bar-val2 reason-foo' not in resp assert 'User foo-none bar-val2 reason-bar' not in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' in resp assert 'Subscription foo-none bar-val2' not in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'extra-data-foo': 'val1', 'extra-data-bar': 'val2'}, ) assert 'User none' not in resp assert 'User empty' not in resp assert 'User foo-val1 bar-none presence' not in resp assert 'User foo-val2 bar-val1 absence' not in resp assert 'User foo-val1 bar-val2 not-checked' in resp assert 'User foo-none bar-val2 reason-foo' not in resp assert 'User foo-none bar-val2 reason-bar' not in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' not in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' in resp assert 'Subscription foo-none bar-val2' not in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'extra-data-foo': 'val2', 'extra-data-bar': 'val2'}, ) assert 'User none' not in resp assert 'User empty' not in resp assert 'User foo-val1 bar-none presence' not in resp assert 'User foo-val2 bar-val1 absence' not in resp assert 'User foo-val1 bar-val2 not-checked' not in resp assert 'User foo-none bar-val2 reason-foo' not in resp assert 'User foo-none bar-val2 reason-bar' not in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' not in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' not in resp assert 'Subscription foo-none bar-val2' not in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'booking-status': 'booked'} ) assert 'User none' in resp assert 'User empty' in resp assert 'User foo-val1 bar-none presence' in resp assert 'User foo-val2 bar-val1 absence' in resp assert 'User foo-val1 bar-val2 not-checked' in resp assert 'User foo-none bar-val2 reason-foo' in resp assert 'User foo-none bar-val2 reason-bar' in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' not in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' not in resp assert 'Subscription foo-none bar-val2' not in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'booking-status': 'not-booked'} ) assert 'User none' not in resp assert 'User empty' not in resp assert 'User foo-val1 bar-none presence' not in resp assert 'User foo-val2 bar-val1 absence' not in resp assert 'User foo-val1 bar-val2 not-checked' not in resp assert 'User foo-none bar-val2 reason-foo' not in resp assert 'User foo-none bar-val2 reason-bar' not in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' in resp assert 'Subscription empty' in resp assert 'Subscription foo-val1 bar-none' in resp assert 'Subscription foo-val2 bar-val1' in resp assert 'Subscription foo-val1 bar-val2' in resp assert 'Subscription foo-none bar-val2' in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'booking-status': 'cancelled'} ) assert 'User none' not in resp assert 'User empty' not in resp assert 'User foo-val1 bar-none presence' not in resp assert 'User foo-val2 bar-val1 absence' not in resp assert 'User foo-val1 bar-val2 not-checked' not in resp assert 'User foo-none bar-val2 reason-foo' not in resp assert 'User foo-none bar-val2 reason-bar' not in resp assert 'User foo-none bar-val2 cancelled-absence' in resp assert 'User foo-none bar-val2 cancelled-presence' in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' not in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' not in resp assert 'Subscription foo-none bar-val2' not in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'booking-status': 'not-checked'} ) assert 'User none' in resp assert 'User empty' in resp assert 'User foo-val1 bar-none presence' not in resp assert 'User foo-val2 bar-val1 absence' not in resp assert 'User foo-val1 bar-val2 not-checked' in resp assert 'User foo-none bar-val2 reason-foo' not in resp assert 'User foo-none bar-val2 reason-bar' not in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' not in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' not in resp assert 'Subscription foo-none bar-val2' not in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'booking-status': 'presence'} ) assert 'User none' not in resp assert 'User empty' not in resp assert 'User foo-val1 bar-none presence' in resp assert 'User foo-val2 bar-val1 absence' not in resp assert 'User foo-val1 bar-val2 not-checked' not in resp assert 'User foo-none bar-val2 reason-foo' not in resp assert 'User foo-none bar-val2 reason-bar' in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' not in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' not in resp assert 'Subscription foo-none bar-val2' not in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'booking-status': 'absence'} ) assert 'User none' not in resp assert 'User empty' not in resp assert 'User foo-val1 bar-none presence' not in resp assert 'User foo-val2 bar-val1 absence' in resp assert 'User foo-val1 bar-val2 not-checked' not in resp assert 'User foo-none bar-val2 reason-foo' in resp assert 'User foo-none bar-val2 reason-bar' not in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' not in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' not in resp assert 'Subscription foo-none bar-val2' not in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'booking-status': 'absence::foo-reason'}, ) assert 'User none' not in resp assert 'User empty' not in resp assert 'User foo-val1 bar-none presence' not in resp assert 'User foo-val2 bar-val1 absence' not in resp assert 'User foo-val1 bar-val2 not-checked' not in resp assert 'User foo-none bar-val2 reason-foo' in resp assert 'User foo-none bar-val2 reason-bar' not in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' not in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' not in resp assert 'Subscription foo-none bar-val2' not in resp resp = app.get( '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'booking-status': 'presence::bar-reason'}, ) assert 'User none' not in resp assert 'User empty' not in resp assert 'User foo-val1 bar-none presence' not in resp assert 'User foo-val2 bar-val1 absence' not in resp assert 'User foo-val1 bar-val2 not-checked' not in resp assert 'User foo-none bar-val2 reason-foo' not in resp assert 'User foo-none bar-val2 reason-bar' in resp assert 'User foo-none bar-val2 cancelled-absence' not in resp assert 'User foo-none bar-val2 cancelled-presence' not in resp assert 'Subscription none' not in resp assert 'Subscription empty' not in resp assert 'Subscription foo-val1 bar-none' not in resp assert 'Subscription foo-val2 bar-val1' not in resp assert 'Subscription foo-val1 bar-val2' not in resp assert 'Subscription foo-none bar-val2' not in resp def test_event_check_ordering(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda ) Booking.objects.create( event=event, user_first_name='BB', user_last_name='XX', user_external_id='user:1', ) Booking.objects.create( event=event, user_first_name='AA', user_last_name='YY', user_external_id='user:2', cancellation_datetime=now(), ) Subscription.objects.create( agenda=agenda, user_first_name='CC', user_last_name='WW', user_external_id='user:3', date_start=datetime.date(2022, 2, 1), date_end=datetime.date(2022, 3, 1), ) login(app) resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert resp.text.index('CC WW') < resp.text.index('BB XX') < resp.text.index('AA YY') resp = app.get('/manage/agendas/%s/events/%s/check?sort=lastname,firstname' % (agenda.pk, event.pk)) assert resp.text.index('CC WW') < resp.text.index('BB XX') < resp.text.index('AA YY') resp = app.get('/manage/agendas/%s/events/%s/check?sort=firstname,lastname' % (agenda.pk, event.pk)) assert resp.text.index('AA YY') < resp.text.index('BB XX') < resp.text.index('CC WW') @mock.patch('chrono.manager.forms.get_agenda_check_types') def test_event_check_booking(check_types, app, admin_user): check_types.return_value = [] agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() - datetime.timedelta(days=1), places=10, waiting_list_places=5, agenda=agenda, ) booking = Booking.objects.create(event=event, user_first_name='User', user_last_name='42') secondary_booking = Booking.objects.create( event=event, user_first_name='User', user_last_name='42', primary_booking=booking ) assert agenda.mark_event_checked_auto is False login(app) resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert resp.pyquery.find('td.booking-status')[0].text.strip() == '-' assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 0 assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) in resp assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp # set as present token = resp.context['csrf_token'] resp = app.post( '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk), params={'csrfmiddlewaretoken': token}, ).follow() assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present' assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1 assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence' booking.refresh_from_db() assert booking.user_was_present is True assert booking.user_check_type_slug is None assert booking.user_check_type_label is None secondary_booking.refresh_from_db() assert secondary_booking.user_was_present is True assert secondary_booking.user_check_type_slug is None assert secondary_booking.user_check_type_label is None event.refresh_from_db() assert event.checked is False agenda.mark_event_checked_auto = True agenda.save() # set as absent without check_type resp = app.post( '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk), params={'csrfmiddlewaretoken': token}, ).follow() assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Absent' assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1 assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Absence' booking.refresh_from_db() assert booking.user_was_present is False assert booking.user_check_type_slug is None assert booking.user_check_type_label is None secondary_booking.refresh_from_db() assert secondary_booking.user_was_present is False assert secondary_booking.user_check_type_slug is None assert secondary_booking.user_check_type_label is None event.refresh_from_db() assert event.checked is True check_types.return_value = [ CheckType(slug='foo-reason', label='Foo reason', kind='absence'), ] resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert len(resp.pyquery.find('td.booking-actions form.absence select')) == 1 assert len(resp.pyquery.find('td.booking-actions form.presence select')) == 0 # set as absent with check_type resp = app.post( '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk), params={'csrfmiddlewaretoken': token, 'check_type': 'foo-reason'}, ).follow() assert 'Foo reason' in resp booking.refresh_from_db() assert booking.user_was_present is False assert booking.user_check_type_slug == 'foo-reason' assert booking.user_check_type_label == 'Foo reason' secondary_booking.refresh_from_db() assert secondary_booking.user_was_present is False assert secondary_booking.user_check_type_slug == 'foo-reason' assert secondary_booking.user_check_type_label == 'Foo reason' # set as present without check_type resp = app.post( '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk), params={'csrfmiddlewaretoken': token}, ).follow() assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present' assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1 assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence' booking.refresh_from_db() assert booking.user_was_present is True assert booking.user_check_type_slug is None assert booking.user_check_type_label is None secondary_booking.refresh_from_db() assert secondary_booking.user_was_present is True assert secondary_booking.user_check_type_slug is None assert secondary_booking.user_check_type_label is None event.refresh_from_db() assert event.checked is True resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert len(resp.pyquery.find('td.booking-actions form.absence select')) == 1 assert len(resp.pyquery.find('td.booking-actions form.presence select')) == 0 check_types.return_value = [ CheckType(slug='foo-reason', label='Foo reason', kind='absence'), CheckType(slug='bar-reason', label='Bar reason', kind='presence'), ] resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert len(resp.pyquery.find('td.booking-actions form.absence select')) == 1 assert len(resp.pyquery.find('td.booking-actions form.presence select')) == 1 # set as present with check_type resp = app.post( '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk), params={'csrfmiddlewaretoken': token, 'check_type': 'bar-reason'}, ).follow() assert 'Bar reason' in resp booking.refresh_from_db() assert booking.user_was_present is True assert booking.user_check_type_slug == 'bar-reason' assert booking.user_check_type_label == 'Bar reason' secondary_booking.refresh_from_db() assert secondary_booking.user_was_present is True assert secondary_booking.user_check_type_slug == 'bar-reason' assert secondary_booking.user_check_type_label == 'Bar reason' # mark the event as checked event.checked = 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) in resp assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp resp = app.post( '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk), params={'csrfmiddlewaretoken': token}, status=302, ) app.post( '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk), params={'csrfmiddlewaretoken': token}, status=302, ) # now disable check update agenda.disable_check_update = True agenda.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 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, ) @mock.patch('chrono.manager.forms.get_agenda_check_types') def test_event_check_booking_ajax(check_types, app, admin_user): check_types.return_value = [ CheckType(slug='foo-reason', label='Foo reason', kind='absence'), CheckType(slug='bar-reason', label='Bar reason', kind='presence'), ] agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() - datetime.timedelta(days=1), places=10, waiting_list_places=5, agenda=agenda, ) booking = Booking.objects.create(event=event, user_first_name='User', user_last_name='42') login(app) resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) token = resp.context['csrf_token'] # set as present resp = app.post( '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk), params={'csrfmiddlewaretoken': token, 'check_type': 'bar-reason'}, extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}, ) assert '' not in resp # because this is a fragment assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present\n \n (Bar reason)' assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1 assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence' assert '' in resp # set as absent resp = app.post( '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk), params={'csrfmiddlewaretoken': token, 'check_type': 'foo-reason'}, extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}, ) assert '' not in resp # because this is a fragment assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Absent\n \n (Foo reason)' assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1 assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text.startswith('Absence') assert '' in resp @mock.patch('chrono.manager.forms.get_agenda_check_types') def test_event_check_all_bookings(check_types, app, admin_user): check_types.return_value = [ CheckType(slug='foo-reason', label='Foo reason', kind='absence'), CheckType(slug='bar-reason', label='Bar reason', kind='presence'), ] agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() - datetime.timedelta(days=1), places=10, waiting_list_places=5, agenda=agenda, ) booking1 = Booking.objects.create(event=event, user_first_name='User', user_last_name='42') assert agenda.mark_event_checked_auto is False login(app) resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) token = resp.context['csrf_token'] assert 'Mark all bookings without status' in resp assert '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk) in resp assert '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk) in resp resp = app.post( '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token} ) booking1.refresh_from_db() assert booking1.user_was_present is False assert booking1.user_check_type_slug is None assert booking1.user_check_type_label is None event.refresh_from_db() assert event.checked is False agenda.mark_event_checked_auto = True agenda.save() resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Mark all bookings without status' not in resp assert '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk) not in resp assert '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk) not in resp booking2 = Booking.objects.create(event=event, user_first_name='User', user_last_name='35') secondary_booking = Booking.objects.create( event=event, user_first_name='User', user_last_name='35', primary_booking=booking2 ) resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Mark all bookings without status' in resp assert '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk) in resp assert '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk) in resp app.post( '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token} ) booking1.refresh_from_db() assert booking1.user_was_present is False assert booking1.user_check_type_slug is None assert booking1.user_check_type_label is None booking2.refresh_from_db() assert booking2.user_was_present is True assert booking2.user_check_type_slug is None assert booking2.user_check_type_label is None secondary_booking.refresh_from_db() assert secondary_booking.user_was_present is True assert secondary_booking.user_check_type_slug is None assert secondary_booking.user_check_type_label is None event.refresh_from_db() assert event.checked is True # event is checked booking3 = Booking.objects.create(event=event, user_first_name='User', user_last_name='51') resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Mark all bookings without status' in resp app.post( '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token, 'check_type': 'foo-reason'}, ) booking1.refresh_from_db() assert booking1.user_was_present is False assert booking1.user_check_type_slug is None assert booking1.user_check_type_label is None booking2.refresh_from_db() assert booking2.user_was_present is True assert booking2.user_check_type_slug is None assert booking2.user_check_type_label is None booking3.refresh_from_db() assert booking3.user_was_present is False assert booking3.user_check_type_slug == 'foo-reason' assert booking3.user_check_type_label == 'Foo reason' booking4 = Booking.objects.create(event=event, user_first_name='User', user_last_name='52') resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Mark all bookings without status' in resp app.post( '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token, 'check_type': 'bar-reason'}, ) booking1.refresh_from_db() assert booking1.user_was_present is False assert booking1.user_check_type_slug is None assert booking1.user_check_type_label is None booking2.refresh_from_db() assert booking2.user_was_present is True assert booking2.user_check_type_slug is None assert booking2.user_check_type_label is None booking3.refresh_from_db() assert booking3.user_was_present is False assert booking3.user_check_type_slug == 'foo-reason' assert booking3.user_check_type_label == 'Foo reason' booking4.refresh_from_db() assert booking4.user_was_present is True assert booking4.user_check_type_slug == 'bar-reason' assert booking4.user_check_type_label == 'Bar reason' # now disable check update agenda.disable_check_update = True agenda.save() Booking.objects.create(event=event, user_first_name='User', user_last_name='52') resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Mark all bookings without status' not in resp app.post( '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token, 'check_type': 'foo-reason'}, status=404, ) resp = app.post( '/manage/agendas/%s/events/%s/absence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token}, status=404, ) app.post( '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token, 'check_type': 'bar-reason'}, status=404, ) resp = app.post( '/manage/agendas/%s/events/%s/presence' % (agenda.pk, event.pk), params={'csrfmiddlewaretoken': token}, status=404, ) def test_event_check_primary_booking(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( label='xyz', start_datetime=now() - datetime.timedelta(days=1), places=10, waiting_list_places=5, agenda=agenda, ) booking = Booking.objects.create(event=event, user_first_name='User', user_last_name='42') Booking.objects.create(event=event, user_first_name='John', user_last_name='Doe') booking_3 = Booking.objects.create( event=event, user_first_name='Jane', user_last_name='Doe', in_waiting_list=True ) # create secondary bookings Booking.objects.create(event=event, user_first_name='User', user_last_name='42', primary_booking=booking) Booking.objects.create(event=event, user_first_name='User', user_last_name='42', primary_booking=booking) Booking.objects.create( event=event, user_first_name='Jane', user_last_name='Doe', primary_booking=booking_3, in_waiting_list=True, ) login(app) resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk)) assert 'Bookings (4/10)' in resp.text user_bookings = resp.pyquery.find('td.booking-username.main-list') assert len(user_bookings) == 2 assert user_bookings[0].text == 'User 42 (3 places)' assert user_bookings[1].text == 'John Doe' assert 'Waiting List (2/5)' in resp.text user_bookings = resp.pyquery.find('td.booking-username.waiting') assert len(user_bookings) == 1 assert user_bookings[0].text == 'Jane Doe (2 places)' def test_events_timesheet_wrong_kind(app, admin_user): agenda = Agenda.objects.create(label='Foo bar', kind='meetings') app = login(app) app.get('/manage/agendas/%s/events/timesheet' % agenda.id, status=404) agenda.kind = 'virtual' agenda.save() app.get('/manage/agendas/%s/events/timesheet' % agenda.id, status=404) def test_events_timesheet_form(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') login(app) resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk) resp.form['date_start'] = '2022-01-01' resp.form['date_end'] = '2021-12-31' resp = resp.form.submit() assert resp.context['form'].errors['date_end'] == ['End date must be greater than start date.'] resp.form['date_end'] = '2022-04-02' resp = resp.form.submit() assert resp.context['form'].errors['date_end'] == ['Please select an interval of no more than 3 months.'] resp.form['date_end'] = '2022-04-01' resp = resp.form.submit() assert resp.context['form'].errors == {} @pytest.mark.freeze_time('2022-02-15') def test_events_timesheet_slots(app, admin_user): start, end = ( now() - datetime.timedelta(days=15), now() + datetime.timedelta(days=14), ) # 2022-02-31, 2022-03-01 agenda = Agenda.objects.create(label='Events', kind='events') Event.objects.create(label='event 1', start_datetime=start, places=10, agenda=agenda) event2 = Event.objects.create( label='event 2', start_datetime=start + datetime.timedelta(days=1), places=10, agenda=agenda ) event3 = Event.objects.create(label='event 3', start_datetime=now(), places=10, agenda=agenda) Event.objects.create( label='event cancelled', start_datetime=now() + datetime.timedelta(days=4), places=10, agenda=agenda, cancelled=True, ) event4 = Event.objects.create( label='event 4', start_datetime=end - datetime.timedelta(days=1), places=10, agenda=agenda ) Event.objects.create(label='event 5', start_datetime=end, places=10, agenda=agenda) Subscription.objects.create( agenda=agenda, user_external_id='user:1', user_first_name='Subscription', user_last_name='42', date_start=start, date_end=end + datetime.timedelta(days=1), ) recurring_event1 = Event.objects.create( label='recurring 1', start_datetime=start, places=10, agenda=agenda, recurrence_days=[0, 1], recurrence_end_date=end, ) recurring_event1.create_all_recurrences() recurring_event2 = Event.objects.create( label='recurring 2', start_datetime=start, places=10, agenda=agenda, recurrence_days=[1, 2], recurrence_end_date=end, ) recurring_event2.create_all_recurrences() login(app) resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk) resp.form['date_start'] = '2022-02-01' resp.form['date_end'] = '2022-02-28' with CaptureQueriesContext(connection) as ctx: resp = resp.form.submit() assert len(ctx.captured_queries) == 7 slots = resp.context['form'].get_slots() assert slots['dates'] == [ [ datetime.date(2022, 2, 1), datetime.date(2022, 2, 2), datetime.date(2022, 2, 7), datetime.date(2022, 2, 8), datetime.date(2022, 2, 9), datetime.date(2022, 2, 14), datetime.date(2022, 2, 15), datetime.date(2022, 2, 16), datetime.date(2022, 2, 21), datetime.date(2022, 2, 22), datetime.date(2022, 2, 23), datetime.date(2022, 2, 28), ] ] assert slots['events'] == [ event2, recurring_event1, recurring_event2, event3, event4, ] assert slots['users'][0]['users'] == [ { 'user_id': 'user:1', 'user_first_name': 'Subscription', 'user_last_name': '42', 'extra_data': {}, 'events': [ { 'event': event2, 'dates': {date: False for date in slots['dates'][0] if date == datetime.date(2022, 2, 1)}, }, { 'event': recurring_event1, 'dates': {date: False for date in slots['dates'][0] if date.weekday() in [0, 1]}, }, { 'event': recurring_event2, 'dates': {date: False for date in slots['dates'][0] if date.weekday() in [1, 2]}, }, { 'event': event3, 'dates': { date: False for date in slots['dates'][0] if date == datetime.date(2022, 2, 15) }, }, { 'event': event4, 'dates': { date: False for date in slots['dates'][0] if date == datetime.date(2022, 2, 28) }, }, ], }, ] assert slots['extra_data'] == [] @pytest.mark.freeze_time('2022-02-15') def test_events_timesheet_subscription_limits(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event1 = Event.objects.create( start_datetime=make_aware(datetime.datetime(2022, 2, 1, 17, 0)), places=10, agenda=agenda ) event2 = Event.objects.create( start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda ) event3 = Event.objects.create( start_datetime=make_aware(datetime.datetime(2022, 2, 28, 17, 0)), places=10, agenda=agenda ) dates = [ ('2022-01-31', '2022-02-01'), ('2022-02-01', '2022-02-02'), ('2022-02-01', '2022-02-15'), ('2022-02-01', '2022-02-16'), ('2022-02-15', '2022-02-28'), ('2022-02-15', '2022-03-01'), ('2022-02-16', '2022-03-01'), ('2022-02-01', '2022-03-01'), ('2022-02-28', '2022-03-01'), ('2022-03-01', '2022-03-02'), ] for start, end in dates: Subscription.objects.create( agenda=agenda, user_external_id='user:%s-%s' % (start, end), user_first_name='Subscription', user_last_name='%s - %s' % (start, end), date_start=datetime.datetime.strptime(start, '%Y-%m-%d'), date_end=datetime.datetime.strptime(end, '%Y-%m-%d'), ) login(app) resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk) resp.form['date_start'] = '2022-02-01' resp.form['date_end'] = '2022-02-28' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert slots['dates'] == [ [ datetime.date(2022, 2, 1), datetime.date(2022, 2, 15), datetime.date(2022, 2, 28), ] ] assert slots['events'] == [ event1, event2, event3, ] users = slots['users'][0]['users'] assert len(users) == 8 assert users[0]['user_id'] == 'user:2022-02-01-2022-02-02' assert users[1]['user_id'] == 'user:2022-02-01-2022-02-15' assert users[2]['user_id'] == 'user:2022-02-01-2022-02-16' assert users[3]['user_id'] == 'user:2022-02-01-2022-03-01' assert users[4]['user_id'] == 'user:2022-02-15-2022-02-28' assert users[5]['user_id'] == 'user:2022-02-15-2022-03-01' assert users[6]['user_id'] == 'user:2022-02-16-2022-03-01' assert users[7]['user_id'] == 'user:2022-02-28-2022-03-01' def test_events_timesheet_users(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda ) booking1 = Booking.objects.create( event=event, user_external_id='user:1', user_first_name='User', user_last_name='42' ) Booking.objects.create( event=event, user_external_id='user:2', user_first_name='User', user_last_name='01' ) Booking.objects.create( event=event, user_external_id='user:3', user_first_name='User', user_last_name='17' ) Booking.objects.create( event=event, user_external_id='user:4', user_first_name='User', user_last_name='35' ) Booking.objects.create( event=event, user_external_id='user:5', user_first_name='User', user_last_name='05' ) booking6 = Booking.objects.create( event=event, user_external_id='user:6', user_first_name='User', user_last_name='12 Cancelled' ) booking6.cancel() Booking.objects.create( event=event, user_external_id='user:7', user_first_name='User', user_last_name='Waiting', in_waiting_list=True, ) booking8 = Booking.objects.create( event=event, user_external_id='user:8', user_first_name='User', user_last_name='Waiting and Cancelled', in_waiting_list=True, ) booking8.cancel() Booking.objects.create( event=event, user_external_id='user:1', user_first_name='User', user_last_name='Secondary', primary_booking=booking1, ) login(app) resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk) resp.form['date_start'] = '2022-02-01' resp.form['date_end'] = '2022-02-28' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert [u['user_id'] for u in slots['users'][0]['users']] == [ 'user:2', 'user:5', 'user:3', 'user:4', 'user:1', ] start = datetime.date(2022, 2, 1) end = datetime.date(2022, 3, 1) Subscription.objects.create( agenda=agenda, user_external_id='user:1', user_first_name='Subscription', user_last_name='42', date_start=start, date_end=end, ) Subscription.objects.create( agenda=agenda, user_external_id='user:9', user_first_name='Subscription', user_last_name='43', date_start=start, date_end=end, ) Subscription.objects.create( agenda=agenda, user_external_id='user:10', user_first_name='Subscription', user_last_name='14', date_start=start, date_end=end, ) Subscription.objects.create( agenda=agenda, user_external_id='user:7', user_first_name='Subscription', user_last_name='Waiting', date_start=start, date_end=end, ) Subscription.objects.create( agenda=agenda, user_external_id='user:42', user_first_name='Subscription', user_last_name='Too soon', date_start=start - datetime.timedelta(days=1), date_end=start, ) Subscription.objects.create( agenda=agenda, user_external_id='user:43', user_first_name='Subscription', user_last_name='Too late', date_start=end + datetime.timedelta(days=1), date_end=end + datetime.timedelta(days=2), ) resp = resp.form.submit() slots = resp.context['form'].get_slots() assert [u['user_id'] for u in slots['users'][0]['users']] == [ 'user:2', 'user:5', 'user:6', 'user:10', 'user:3', 'user:4', 'user:1', 'user:9', 'user:7', ] def test_events_timesheet_user_ids(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda ) booking = Booking.objects.create(event=event, user_first_name='User', user_last_name='42') login(app) resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk) resp.form['date_start'] = '2022-02-01' resp.form['date_end'] = '2022-02-28' resp = resp.form.submit() slots = resp.context['form'].get_slots() # no user_id found assert [u['user_id'] for u in slots['users'][0]['users']] == [] assert [u['user_first_name'] for u in slots['users'][0]['users']] == [] assert [u['user_last_name'] for u in slots['users'][0]['users']] == [] booking.user_external_id = 'user:1' booking.save() resp = resp.form.submit() slots = resp.context['form'].get_slots() assert [u['user_id'] for u in slots['users'][0]['users']] == [ 'user:1', ] assert [u['user_first_name'] for u in slots['users'][0]['users']] == ['User'] assert [u['user_last_name'] for u in slots['users'][0]['users']] == ['42'] Subscription.objects.create( agenda=agenda, user_external_id='user:1', user_first_name='Subscription', user_last_name='41', date_start=datetime.date(2022, 2, 1), date_end=datetime.date(2022, 3, 1), ) resp = resp.form.submit() slots = resp.context['form'].get_slots() assert [u['user_id'] for u in slots['users'][0]['users']] == [ 'user:1', ] assert [u['user_first_name'] for u in slots['users'][0]['users']] == [ 'Subscription', ] assert [u['user_last_name'] for u in slots['users'][0]['users']] == [ '41', ] @pytest.mark.freeze_time('2022-02-01') def test_events_timesheet_booked(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event_date = make_aware(datetime.datetime(2022, 2, 15, 17, 0)) event1 = Event.objects.create(label='event 1', start_datetime=event_date, places=10, agenda=agenda) event2 = Event.objects.create(label='event 2', start_datetime=event_date, places=10, agenda=agenda) event3 = Event.objects.create(label='event 3', start_datetime=event_date, places=10, agenda=agenda) recurring_event1 = Event.objects.create( label='recurring 1', start_datetime=event_date, places=10, agenda=agenda, recurrence_days=[1], recurrence_end_date=event_date + datetime.timedelta(days=1), ) recurring_event1.create_all_recurrences() recurring_event1_occurence = recurring_event1.recurrences.first() recurring_event2 = Event.objects.create( label='recurring 2', start_datetime=event_date, places=10, agenda=agenda, recurrence_days=[1], recurrence_end_date=event_date + datetime.timedelta(days=1), ) recurring_event2.create_all_recurrences() recurring_event2_occurence = recurring_event2.recurrences.first() Subscription.objects.create( agenda=agenda, user_external_id='user:1', user_first_name='Subscription', user_last_name='42', date_start=datetime.date(2022, 2, 1), date_end=datetime.date(2022, 3, 1), ) Booking.objects.create( event=event1, user_external_id='user:1', user_first_name='User', user_last_name='42', ) Booking.objects.create( event=event2, user_external_id='user:1', user_first_name='User', user_last_name='42', cancellation_datetime=now(), ) Booking.objects.create( event=recurring_event1_occurence, user_external_id='user:1', user_first_name='User', user_last_name='42', ) Booking.objects.create( event=recurring_event2_occurence, user_external_id='user:1', user_first_name='User', user_last_name='42', cancellation_datetime=now(), ) login(app) resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk) resp.form['date_start'] = '2022-02-01' resp.form['date_end'] = '2022-02-28' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert slots['events'] == [ event1, event2, event3, recurring_event1, recurring_event2, ] assert len(slots['users'][0]['users']) == 1 assert slots['users'][0]['users'][0]['events'] == [ { 'event': event1, 'dates': {datetime.date(2022, 2, 15): True}, }, { 'event': event2, 'dates': {datetime.date(2022, 2, 15): False}, }, { 'event': event3, 'dates': {datetime.date(2022, 2, 15): False}, }, { 'event': recurring_event1, 'dates': {datetime.date(2022, 2, 15): True}, }, { 'event': recurring_event2, 'dates': {datetime.date(2022, 2, 15): False}, }, ] def test_events_timesheet_extra_data(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda ) Booking.objects.create( event=event, user_first_name='User', user_last_name='42', user_external_id='user:1', extra_data={'foo': 'bar', 'baz': 'blah'}, ) login(app) resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk) resp.form['date_start'] = '2022-02-01' resp.form['date_end'] = '2022-02-28' resp.form['extra_data'] = ' foo ' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert resp.text.count('
    ') == 0 assert len(slots['users']) == 1 assert slots['users'][0]['grouper'] == '' assert len(slots['users'][0]['users']) == 1 assert slots['extra_data'] == ['foo'] assert slots['users'][0]['users'][0]['extra_data']['foo'] == 'bar' resp.form['extra_data'] = ' foo ,baz,,' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert len(slots['users'][0]['users']) == 1 assert slots['extra_data'] == ['foo', 'baz'] assert slots['users'][0]['users'][0]['extra_data']['foo'] == 'bar' assert slots['users'][0]['users'][0]['extra_data']['baz'] == 'blah' resp.form['extra_data'] = 'unknown' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert len(slots['users'][0]['users']) == 1 assert slots['extra_data'] == ['unknown'] assert slots['users'][0]['users'][0]['extra_data']['unknown'] == '' Subscription.objects.create( agenda=agenda, user_external_id='user:1', user_first_name='Subscription', user_last_name='41', date_start=datetime.date(2022, 2, 1), date_end=datetime.date(2022, 3, 1), extra_data={'foo': 'baz'}, ) resp.form['extra_data'] = ' foo ' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert len(slots['users'][0]['users']) == 1 assert slots['extra_data'] == ['foo'] assert slots['users'][0]['users'][0]['extra_data']['foo'] == 'baz' resp.form['extra_data'] = ' foo ,baz,,' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert len(slots['users'][0]['users']) == 1 assert slots['extra_data'] == ['foo', 'baz'] assert slots['users'][0]['users'][0]['extra_data']['foo'] == 'baz' assert slots['users'][0]['users'][0]['extra_data']['baz'] == '' Booking.objects.create( event=event, user_first_name='User', user_last_name='43', user_external_id='user:2', extra_data={'foo': 'bar', 'baz': 'aa'}, ) Booking.objects.create( event=event, user_first_name='User', user_last_name='44', user_external_id='user:3', extra_data={'foo': 'bar2', 'baz': 'aa'}, ) resp.form['extra_data'] = '' resp.form['group_by'] = 'foo' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert resp.text.count('
    ') == 0 assert len(slots['users']) == 3 assert slots['users'][0]['grouper'] == 'bar' assert len(slots['users'][0]['users']) == 1 assert slots['users'][0]['users'][0]['user_id'] == 'user:2' assert slots['users'][1]['grouper'] == 'bar2' assert len(slots['users'][1]['users']) == 1 assert slots['users'][1]['users'][0]['user_id'] == 'user:3' assert slots['users'][2]['grouper'] == 'baz' assert len(slots['users'][2]['users']) == 1 assert slots['users'][2]['users'][0]['user_id'] == 'user:1' resp.form['with_page_break'] = True resp = resp.form.submit() assert resp.text.count('
    ') == 2 resp.form['group_by'] = 'baz' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert len(slots['users']) == 2 assert slots['users'][0]['grouper'] == 'aa' assert len(slots['users'][0]['users']) == 2 assert slots['users'][0]['users'][0]['user_id'] == 'user:2' assert slots['users'][0]['users'][1]['user_id'] == 'user:3' assert slots['users'][1]['grouper'] == '' assert len(slots['users'][1]['users']) == 1 assert slots['users'][1]['users'][0]['user_id'] == 'user:1' Subscription.objects.update(extra_data={'foo': 'baz', 'baz': 'blah'}) resp = resp.form.submit() slots = resp.context['form'].get_slots() assert len(slots['users']) == 2 assert slots['users'][0]['grouper'] == 'aa' assert len(slots['users'][0]['users']) == 2 assert slots['users'][0]['users'][0]['user_id'] == 'user:2' assert slots['users'][0]['users'][1]['user_id'] == 'user:3' assert slots['users'][1]['grouper'] == 'blah' assert len(slots['users'][1]['users']) == 1 assert slots['users'][1]['users'][0]['user_id'] == 'user:1' resp.form['group_by'] = 'unknown' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert len(slots['users']) == 1 assert slots['users'][0]['grouper'] == '' assert len(slots['users'][0]['users']) == 3 def test_events_timesheet_ordering(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') event = Event.objects.create( start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda ) Booking.objects.create( event=event, user_first_name='BB', user_last_name='XX', user_external_id='user:1', ) Booking.objects.create( event=event, user_first_name='AA', user_last_name='YY', user_external_id='user:2', cancellation_datetime=now(), ) Subscription.objects.create( agenda=agenda, user_first_name='CC', user_last_name='WW', user_external_id='user:3', date_start=datetime.date(2022, 2, 1), date_end=datetime.date(2022, 3, 1), ) login(app) resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk) resp.form['date_start'] = '2022-02-01' resp.form['date_end'] = '2022-02-28' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert slots['users'][0]['users'][0]['user_id'] == 'user:3' assert slots['users'][0]['users'][1]['user_id'] == 'user:1' assert slots['users'][0]['users'][2]['user_id'] == 'user:2' resp.form['sort'] = 'lastname,firstname' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert slots['users'][0]['users'][0]['user_id'] == 'user:3' assert slots['users'][0]['users'][1]['user_id'] == 'user:1' assert slots['users'][0]['users'][2]['user_id'] == 'user:2' resp.form['sort'] = 'firstname,lastname' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert slots['users'][0]['users'][0]['user_id'] == 'user:2' assert slots['users'][0]['users'][1]['user_id'] == 'user:1' assert slots['users'][0]['users'][2]['user_id'] == 'user:3' @pytest.mark.freeze_time('2022-04-01') def test_events_timesheet_date_display(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') recurring_event = Event.objects.create( label='recurring 1', start_datetime=make_aware(datetime.datetime(2022, 1, 1, 12, 0)), places=10, agenda=agenda, recurrence_days=[0], recurrence_end_date=datetime.date(2022, 4, 1), ) recurring_event.create_all_recurrences() Subscription.objects.create( agenda=agenda, user_external_id='user:1', user_first_name='Subscription', user_last_name='42', date_start=datetime.date(2022, 1, 1), date_end=datetime.date(2022, 4, 1), extra_data={'foo': 'bar'}, ) login(app) resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk) resp.form['date_start'] = '2022-01-01' resp.form['date_end'] = '2022-03-31' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert slots['dates'] == [ [ datetime.date(2022, 1, 3), datetime.date(2022, 1, 10), datetime.date(2022, 1, 17), datetime.date(2022, 1, 24), datetime.date(2022, 1, 31), datetime.date(2022, 2, 7), datetime.date(2022, 2, 14), datetime.date(2022, 2, 21), datetime.date(2022, 2, 28), datetime.date(2022, 3, 7), datetime.date(2022, 3, 14), datetime.date(2022, 3, 21), datetime.date(2022, 3, 28), ] ] resp.form['date_display'] = 'month' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert slots['dates'] == [ [ datetime.date(2022, 1, 3), datetime.date(2022, 1, 10), datetime.date(2022, 1, 17), datetime.date(2022, 1, 24), datetime.date(2022, 1, 31), ], [ datetime.date(2022, 2, 7), datetime.date(2022, 2, 14), datetime.date(2022, 2, 21), datetime.date(2022, 2, 28), ], [ datetime.date(2022, 3, 7), datetime.date(2022, 3, 14), datetime.date(2022, 3, 21), datetime.date(2022, 3, 28), ], ] resp.form['date_display'] = 'week' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert resp.text.count('
    ') == 12 assert slots['dates'] == [ [datetime.date(2022, 1, 3)], [datetime.date(2022, 1, 10)], [datetime.date(2022, 1, 17)], [datetime.date(2022, 1, 24)], [datetime.date(2022, 1, 31)], [datetime.date(2022, 2, 7)], [datetime.date(2022, 2, 14)], [datetime.date(2022, 2, 21)], [datetime.date(2022, 2, 28)], [datetime.date(2022, 3, 7)], [datetime.date(2022, 3, 14)], [datetime.date(2022, 3, 21)], [datetime.date(2022, 3, 28)], ] resp.form['date_display'] = 'custom' resp = resp.form.submit() assert resp.context['form'].errors['custom_nb_dates_per_page'] == ['This field is required.'] resp.form['custom_nb_dates_per_page'] = 10 resp = resp.form.submit() slots = resp.context['form'].get_slots() assert slots['dates'] == [ [ datetime.date(2022, 1, 3), datetime.date(2022, 1, 10), datetime.date(2022, 1, 17), datetime.date(2022, 1, 24), datetime.date(2022, 1, 31), datetime.date(2022, 2, 7), datetime.date(2022, 2, 14), datetime.date(2022, 2, 21), datetime.date(2022, 2, 28), datetime.date(2022, 3, 7), ], [ datetime.date(2022, 3, 14), datetime.date(2022, 3, 21), datetime.date(2022, 3, 28), ], ] resp.form['custom_nb_dates_per_page'] = 3 resp = resp.form.submit() slots = resp.context['form'].get_slots() assert slots['dates'] == [ [ datetime.date(2022, 1, 3), datetime.date(2022, 1, 10), datetime.date(2022, 1, 17), ], [ datetime.date(2022, 1, 24), datetime.date(2022, 1, 31), datetime.date(2022, 2, 7), ], [ datetime.date(2022, 2, 14), datetime.date(2022, 2, 21), datetime.date(2022, 2, 28), ], [ datetime.date(2022, 3, 7), datetime.date(2022, 3, 14), datetime.date(2022, 3, 21), ], [ datetime.date(2022, 3, 28), ], ] Subscription.objects.create( agenda=agenda, user_external_id='user:2', user_first_name='Subscription', user_last_name='43', date_start=datetime.date(2022, 1, 1), date_end=datetime.date(2022, 4, 1), extra_data={'foo': 'baz'}, ) resp.form['date_display'] = 'week' resp.form['group_by'] = 'foo' resp = resp.form.submit() slots = resp.context['form'].get_slots() assert resp.text.count('
    ') == 12 resp.form['with_page_break'] = True resp = resp.form.submit() assert resp.text.count('
    ') == 25 def test_events_timesheet_pdf(app, admin_user): agenda = Agenda.objects.create(label='Events', kind='events') login(app) resp = app.get( '/manage/agendas/%s/events/timesheet?pdf=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname&date_display=all&orientation=portrait' % agenda.pk ) assert resp.headers['Content-Type'] == 'application/pdf' assert ( resp.headers['Content-Disposition'] == 'attachment; filename="timesheet_events_2022-02-01_2022-02-28.pdf"' ) # form invalid resp = app.get( '/manage/agendas/%s/events/timesheet?pdf=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname&date_display=all' % agenda.pk ) assert resp.context['form'].errors['orientation'] == ['This field is required.']