chrono/tests/manager/test_all.py

3049 lines
123 KiB
Python

import datetime
import json
from unittest import mock
import freezegun
import pytest
import requests
from django.contrib.auth.models import Group
from django.core.management import call_command
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import (
Agenda,
AgendaReminderSettings,
Booking,
Desk,
Event,
MeetingType,
Resource,
TimePeriod,
TimePeriodException,
TimePeriodExceptionSource,
UnavailabilityCalendar,
VirtualMember,
)
from chrono.utils.signature import check_query
pytestmark = pytest.mark.django_db
def login(app, username='admin', password='admin'):
login_page = app.get('/login/')
login_form = login_page.forms[0]
login_form['username'] = username
login_form['password'] = password
resp = login_form.submit()
assert resp.status_int == 302
return app
def test_unlogged_access(app):
# connect while not being logged in
assert app.get('/manage/', status=302).location.endswith('/login/?next=/manage/')
def test_simple_user_access(app, simple_user):
# connect while being logged as a simple user, access should be forbidden
app = login(app, username='user', password='user')
resp = app.get('/manage/', status=403)
assert 'Unfortunately, there is still no agenda' in resp.html.find('div', {'class': 'big-msg-sorry'}).text
def test_manager_user_access(app, manager_user):
# connect while being logged as a manager user, access should be granted if
# there's at least an agenda that is viewable or editable.
app = login(app, username='manager', password='manager')
assert app.get('/manage/', status=403)
agenda = Agenda(label='Foo bar')
agenda.save()
assert app.get('/manage/', status=403)
agenda.view_role = manager_user.groups.all()[0]
agenda.edit_role = None
agenda.save()
assert app.get('/manage/', status=200)
agenda.edit_role = None
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
assert app.get('/manage/', status=200)
def test_home_redirect(app):
assert app.get('/', status=302).location.endswith('/manage/')
def test_access(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
assert '<h2>Agendas</h2>' in resp.text
assert "This site doesn't have any agenda yet." in resp.text
def test_logout(app, admin_user):
app = login(app)
app.get('/logout/')
assert app.get('/manage/', status=302).location.endswith('/login/?next=/manage/')
def test_menu_json(app, admin_user):
app = login(app)
resp = app.get('/manage/menu.json', status=200)
assert resp.content_type == 'application/json'
assert resp.json[0]['url'] == 'http://testserver/manage/'
assert resp.json[0]['label'] == 'Agendas'
resp2 = app.get('/manage/menu.json?callback=Q', status=200)
assert resp2.text == 'Q(%s);' % resp.text
assert resp2.content_type == 'application/javascript'
def test_menu_json_manager(app, simple_user, manager_user):
app.get('/manage/menu.json', status=302) # redirect to login
app = login(app, username='user', password='user')
app.get('/manage/menu.json', status=403)
app = login(app, username='manager', password='manager')
app.get('/manage/menu.json', status=403)
agenda = Agenda(label='Foo bar')
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
app.get('/manage/menu.json', status=200)
def test_events_agenda_redirect(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar', kind='events')
app = login(app)
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
agenda.default_view = 'open_events'
agenda.save()
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/events/open/' % agenda.pk)
agenda.default_view = 'day'
agenda.save()
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
@freezegun.freeze_time('2020-07-12')
def test_events_agenda_month_redirect(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar', kind='events')
app = login(app)
# no event, redirect to current month
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/' % agenda.pk)
# only past events, redirect to last event month
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() - datetime.timedelta(days=60),
)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/5/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() - datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/6/' % agenda.pk)
# future events
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() + datetime.timedelta(days=60),
)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/9/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() + datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/8/' % agenda.pk)
# don't check events for meetings
agenda.kind = 'virtual'
agenda.save()
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/' % agenda.pk)
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/' % agenda.pk)
@freezegun.freeze_time('2020-07-12')
def test_events_agenda_day_redirect(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar', kind='events')
app = login(app)
# no event, redirect to current day
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/12/' % agenda.pk)
# only past events, redirect to last event day
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() - datetime.timedelta(days=60),
)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/5/13/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() - datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/6/12/' % agenda.pk)
# future events
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() + datetime.timedelta(days=60),
)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/9/10/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() + datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/8/11/' % agenda.pk)
# don't check events for meetings
agenda.kind = 'virtual'
agenda.save()
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/12/' % agenda.pk)
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/12/' % agenda.pk)
def test_meetings_agenda_redirect(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar', kind='meetings')
app = login(app)
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
agenda.default_view = 'month'
agenda.save()
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
def test_virtual_agenda_redirect(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar', kind='virtual')
app = login(app)
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
agenda.default_view = 'month'
agenda.save()
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
def test_view_agendas_as_admin(app, admin_user):
Agenda.objects.create(label='Bar Foo')
app = login(app)
resp = app.get('/manage/', status=200)
assert 'Bar Foo <span class="identifier">[identifier: bar-foo]</span>' in resp.text
def test_view_agendas_as_manager(app, manager_user):
agenda = Agenda(label='Foo Bar')
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
agenda2 = Agenda(label='Bar Foo')
agenda2.save()
app = login(app, username='manager', password='manager')
resp = app.get('/manage/', status=200)
assert 'Foo Bar' in resp.text
assert 'Bar Foo <span class="identifier">[identifier: bar-foo]</span>' not in resp.text
assert 'Bar Foo' not in resp.text
assert 'New' not in resp.text
# check user doesn't have access
app.get('/manage/agendas/%s/' % agenda2.id, status=403)
# check there's no access to the settings page for "events" agenda
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403)
app.get('/manage/agendas/%s/add-event' % agenda.id, status=403)
app.get('/manage/agendas/%s/edit' % agenda.id, status=403)
# check it doesn't give access for "meetings" agenda
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403)
# or virtual agenda
agenda.kind = 'virtual'
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403)
# check it gives a 404 on unknown agendas
resp = app.get('/manage/agendas/0/settings', status=404)
def test_add_agenda(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp.form['label'] = 'Foo bar'
resp = resp.form.submit()
agenda = Agenda.objects.get(label='Foo bar')
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
resp = resp.follow()
assert 'Foo bar' in resp.text
assert '<h2>Settings' in resp.text
assert agenda.minimal_booking_delay == 1
assert agenda.maximal_booking_delay == 56
assert agenda.kind == 'events'
assert agenda.desk_simple_management is False
resp = app.get('/manage/agendas/add/')
resp.form['label'] = 'Foo bar 2'
resp.form['kind'] = 'meetings'
resp = resp.form.submit()
agenda = Agenda.objects.latest('pk')
assert agenda.kind == 'meetings'
assert agenda.desk_simple_management is True
resp = app.get('/manage/agendas/add/')
resp.form['label'] = 'Foo bar 3'
resp.form['kind'] = 'virtual'
resp = resp.form.submit()
agenda = Agenda.objects.latest('pk')
assert agenda.kind == 'virtual'
assert agenda.desk_simple_management is False
def test_add_agenda_as_manager(app, manager_user):
# open /manage/ access to manager_user, and check agenda creation is not
# allowed.
agenda = Agenda(label='Foo bar')
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
app = login(app, username='manager', password='manager')
app.get('/manage/agendas/add/', status=403)
def test_add_agenda_and_set_role(app, admin_user, manager_user):
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp.form['label'] = 'Foo bar'
resp.form['kind'] = 'meetings'
resp = resp.form.submit().follow()
agenda = Agenda.objects.get(label='Foo bar')
assert agenda.desk_set.count() == 1
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Configure', href='roles')
resp.form['edit_role'] = manager_user.groups.all()[0].pk
resp = resp.form.submit().follow()
assert 'Edit Role: Managers' in resp.text
# still only one desk
assert agenda.desk_set.count() == 1
def test_options_agenda(app, admin_user):
agenda_events = Agenda.objects.create(label='Foo bar', kind='events')
Desk.objects.create(agenda=agenda_events, slug='_exceptions_holder')
agenda_meetings = Agenda.objects.create(label='Foo bar', kind='meetings')
Agenda.objects.create(label='Foo bar', kind='virtual')
app = login(app)
resp = app.get('/manage/agendas/%s/edit' % agenda_events.pk)
assert resp.form['label'].value == 'Foo bar'
resp.form['label'] = 'Foo baz'
resp.form['anonymize_delay'] = 365
assert 'default_view' in resp.context['form'].fields
assert resp.context['form'].initial['default_view'] == 'month'
assert 'open_events' in [k for k, v in resp.context['form'].fields['default_view'].choices]
assert 'booking_form_url' in resp.context['form'].fields
assert 'event_display_template' in resp.context['form'].fields
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda_events.pk)
resp = resp.follow()
assert 'has_resources' not in resp.context
assert 'Foo baz' in resp.text
assert '<h2>Settings' in resp.text
agenda_events.refresh_from_db()
assert agenda_events.anonymize_delay == 365
resp = app.get('/manage/agendas/%s/edit' % agenda_meetings.pk)
assert 'default_view' in resp.context['form'].fields
assert 'open_events' not in [k for k, v in resp.context['form'].fields['default_view'].choices]
assert 'booking_form_url' not in resp.context['form'].fields
assert 'event_display_template' not in resp.context['form'].fields
resp.form['default_view'] = 'month'
resp.form.submit()
agenda_meetings.refresh_from_db()
assert agenda_meetings.default_view == 'month'
resp = app.get('/manage/agendas/%s/edit' % agenda_meetings.pk)
assert resp.form['default_view'].value == 'month'
assert 'default_view' in resp.context['form'].fields
assert 'booking_form_url' not in resp.context['form'].fields
assert 'event_display_template' not in resp.context['form'].fields
assert 'open_events' not in [k for k, v in resp.context['form'].fields['default_view'].choices]
def test_options_events_agenda_delays(settings, app, admin_user):
settings.WORKING_DAY_CALENDAR = None
agenda = Agenda.objects.create(label='Foo bar')
assert agenda.minimal_booking_delay == 1
app = login(app)
url = '/manage/agendas/%s/booking-delays' % agenda.pk
resp = app.get(url)
assert 'minimal_booking_delay_in_working_days' not in resp.context['form'].fields
resp.form['minimal_booking_delay'] = None
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.minimal_booking_delay == 1
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
resp = app.get(url)
resp.form['minimal_booking_delay_in_working_days'] = True
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.minimal_booking_delay_in_working_days is True
def test_options_meetings_agenda_delays(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', maximal_booking_delay=2)
assert agenda.maximal_booking_delay == 2
app = login(app)
url = '/manage/agendas/%s/booking-delays' % agenda.pk
resp = app.get(url)
assert 'minimal_booking_delay_in_working_days' not in resp.context['form'].fields
resp.form['maximal_booking_delay'] = None
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.maximal_booking_delay == 2
def test_options_virtual_agenda_delays(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='virtual', maximal_booking_delay=2)
assert agenda.maximal_booking_delay == 2
app = login(app)
url = '/manage/agendas/%s/booking-delays' % agenda.pk
resp = app.get(url)
assert 'minimal_booking_delay_in_working_days' not in resp.context['form'].fields
resp.form['maximal_booking_delay'] = None
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.maximal_booking_delay is None
def test_options_agenda_booking_check_options(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
# check user template
assert agenda.booking_user_block_template == ''
assert (
agenda.get_booking_user_block_template()
== '{{ booking.user_name|default:booking.label|default:"Anonymous" }}'
)
app = login(app)
url = '/manage/agendas/%s/check-options' % agenda.pk
resp = app.get(url)
resp.form['booking_user_block_template'] = '{{ booking.user_name }} Foo Bar'
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.booking_user_block_template == '{{ booking.user_name }} Foo Bar'
assert agenda.get_booking_user_block_template() == '{{ booking.user_name }} Foo Bar'
resp = app.get(url)
resp.form['booking_user_block_template'] = ''
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.booking_user_block_template == ''
assert (
agenda.get_booking_user_block_template()
== '{{ booking.user_name|default:booking.label|default:"Anonymous" }}'
)
# check filters
assert agenda.booking_check_filters == ''
assert agenda.get_booking_check_filters() == []
resp = app.get(url)
resp.form['booking_check_filters'] = 'foo,bar,baz'
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.booking_check_filters == 'foo,bar,baz'
assert agenda.get_booking_check_filters() == ['foo', 'bar', 'baz']
# check auto checked
assert agenda.mark_event_checked_auto is False
resp = app.get(url)
resp.form['mark_event_checked_auto'] = True
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.mark_event_checked_auto is True
# check disable check
assert agenda.disable_check_update is False
resp = app.get(url)
resp.form['disable_check_update'] = True
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.disable_check_update is True
# check kind
agenda.kind = 'meetings'
agenda.save()
app.get(url, status=404)
agenda.kind = 'virtual'
agenda.save()
app.get(url, status=404)
def test_options_agenda_event_display_template(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/edit' % agenda.pk)
valid_template = '{{ event.label|default:event.slug }} - {{ event.remaining_places|add:"5" }} / {{ event.start_datetime|date }} - {{ event.agenda.name }}'
resp.form['event_display_template'] = valid_template
resp = resp.form.submit().follow()
agenda.refresh_from_db()
assert agenda.event_display_template == valid_template
invalid_templates = [
'{{ syntax error }}',
'{{ event.label|invalidfilter }}',
'{{ event.label|default:notexist }}',
]
for template in invalid_templates:
resp = app.get('/manage/agendas/%s/edit' % agenda.pk)
resp.form['event_display_template'] = template
resp = resp.form.submit()
assert 'syntax error' in resp.text
def test_options_agenda_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/', status=200)
resp = resp.click('Foo bar').follow().follow()
assert 'Settings' not in resp.text
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403)
resp = app.get('/manage/agendas/%s/edit' % agenda.id, status=403)
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403)
resp = app.get('/manage/agendas/%s/edit' % agenda.id, status=403)
agenda.kind = 'events'
agenda.save()
agenda.edit_role = manager_user.groups.all()[0]
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Options')
assert resp.form['label'].value == 'Foo bar'
resp.form['label'] = 'Foo baz'
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
resp = resp.follow()
assert 'Foo baz' in resp.text
assert '<h2>Settings' in resp.text
def test_options_meetings_agenda_num_queries(app, admin_user, managers_group):
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', edit_role=managers_group, view_role=managers_group
)
resource = Resource.objects.create(label='Resource')
agenda.resources.add(resource)
for i in range(0, 10):
MeetingType.objects.create(agenda=agenda, label='MT %s' % i)
desk = Desk.objects.create(agenda=agenda, label='Desk')
source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
calendar = UnavailabilityCalendar.objects.create(label='foo')
calendar.desks.add(desk)
for weekday in (0, 6):
TimePeriod.objects.create(
weekday=weekday, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
desk2 = desk.duplicate()
assert agenda.is_available_for_simple_management() is True
app = login(app)
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/agendas/%s/settings' % agenda.pk)
assert len(ctx.captured_queries) == 13
# check with different kind of exceptions:
# exception starts and ends in the past
exception = TimePeriodException.objects.create(
desk=desk,
start_datetime=now() - datetime.timedelta(days=2),
end_datetime=now() - datetime.timedelta(days=1),
)
desk2.delete()
desk2 = desk.duplicate()
assert agenda.is_available_for_simple_management() is True
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/agendas/%s/settings' % agenda.pk)
assert len(ctx.captured_queries) == 13
# exception starts in the past but ends in the futur
exception.delete()
exception = TimePeriodException.objects.create(
desk=desk,
source=source,
start_datetime=now() - datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=1),
)
desk2.delete()
desk2 = desk.duplicate()
assert agenda.is_available_for_simple_management() is True
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/agendas/%s/settings' % agenda.pk)
assert len(ctx.captured_queries) == 13
# exception in more than 2 weeks
exception.delete()
exception = TimePeriodException.objects.create(
unavailability_calendar=calendar,
start_datetime=now() + datetime.timedelta(days=20),
end_datetime=now() + datetime.timedelta(days=21),
)
desk2.delete()
desk2 = desk.duplicate()
assert agenda.is_available_for_simple_management() is True
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/agendas/%s/settings' % agenda.pk)
assert len(ctx.captured_queries) == 13
@mock.patch('chrono.agendas.models.Agenda.is_available_for_simple_management')
def test_agenda_options_desk_simple_management(available_mock, app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
app = login(app)
available_mock.return_value = True
assert agenda.desk_simple_management is False
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/desk-management-toggle' % agenda.pk in resp.text
agenda.desk_simple_management = True
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/desk-management-toggle' % agenda.pk in resp.text
available_mock.return_value = False
agenda.desk_simple_management = False
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/desk-management-toggle' % agenda.pk not in resp.text
agenda.desk_simple_management = True
agenda.save()
# always possible to disable this flag
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert '/manage/agendas/%s/desk-management-toggle' % agenda.pk in resp.text
available_mock.return_value = True
for old_value in [True, False]:
agenda.desk_simple_management = old_value
agenda.save()
resp = app.get('/manage/agendas/%s/desk-management-toggle' % agenda.pk)
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
agenda.refresh_from_db()
# was changed
assert agenda.desk_simple_management is not old_value
available_mock.return_value = False
for old_value in [True, False]:
agenda.desk_simple_management = old_value
agenda.save()
resp = app.get('/manage/agendas/%s/desk-management-toggle' % agenda.pk)
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
agenda.refresh_from_db()
# not possible to enable flag
assert agenda.desk_simple_management is False
# unknown pk
app.get('/manage/agendas/0/desk-management-toggle', status=404)
# check kind
agenda.kind = 'events'
agenda.save()
app.get('/manage/agendas/%s/desk-management-toggle' % agenda.pk, status=404)
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/agendas/%s/desk-management-toggle' % agenda.pk, status=404)
def test_delete_agenda(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/', status=200)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Delete')
resp = resp.form.submit()
assert resp.location.endswith('/manage/')
resp = resp.follow()
assert 'Foo bar' not in resp.text
def test_delete_busy_agenda(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)
resp = resp.click('Delete')
assert 'Are you sure you want to delete this?' in resp.text
booking = Booking(event=event)
booking.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.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)
resp = resp.click('Delete')
assert 'Are you sure you want to delete this?' 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_agenda_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')
app = login(app, username='manager', password='manager')
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert 'Options' in resp.text
assert 'Delete' not in resp.text
resp = app.get('/manage/agendas/%s/delete' % agenda.id, status=403)
def test_delete_busy_desk(app, admin_user):
agenda = Agenda(label='Foo bar', kind='meetings')
agenda.save()
desk_a = Desk.objects.create(agenda=agenda, label='Desk A')
Desk.objects.create(agenda=agenda, label='Desk B')
event = Event(start_datetime=now() + datetime.timedelta(days=10), places=10, agenda=agenda, desk=desk_a)
event.save()
app = login(app)
resp = app.get('/manage/', status=200)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
desk_page = resp.click('Desk A')
desk_delete_page = desk_page.click('Delete')
assert 'Are you sure you want to delete this?' in desk_delete_page.text
# make sure the deleting is not disabled
assert 'disabled' not in desk_delete_page.text
booking = Booking(event=event)
booking.save()
resp = desk_page.click('Delete')
assert 'This cannot be removed' in resp.text
# the button is disabled
assert 'disabled' in resp.text
app.post('/manage/desks/%s/delete' % desk_a.pk, status=403)
def test_add_meetings_agenda(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp.form['label'] = 'Foo bar'
resp.form['kind'] = 'meetings'
resp = resp.form.submit()
agenda = Agenda.objects.get(label='Foo bar')
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
resp = resp.follow()
assert 'Foo bar' in resp.text
assert '<h2>Settings' in resp.text
assert 'Meeting Types' in resp.text
agenda = Agenda.objects.get(label='Foo bar')
assert agenda.kind == 'meetings'
def test_meetings_agenda_add_meeting_type(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert "This agenda doesn't have any meeting type yet." in resp.text
resp = resp.click('New Meeting Type')
resp.form['label'] = 'Blah'
resp.form['duration'] = '0'
assert 'deleted' not in resp.form.fields
resp = resp.form.submit()
assert 'Ensure this value is greater than or equal to 1.' in resp.text
assert not MeetingType.objects.exists()
resp.form['duration'] = '60'
resp = resp.form.submit()
meeting_type = MeetingType.objects.get(agenda=agenda)
assert meeting_type.label == 'Blah'
assert meeting_type.duration == 60
assert meeting_type.deleted is False
resp = resp.follow()
assert 'Blah' in resp.text
def test_meetings_agenda_edit_meeting_type(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah')
meeting_type2 = MeetingType.objects.create(agenda=agenda, label='Other')
other_agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
other_meeting_type = MeetingType.objects.create(agenda=other_agenda, label='Blah')
assert meeting_type.slug == other_meeting_type.slug
app = login(app)
resp = app.get('/manage/meetingtypes/%s/edit' % meeting_type.pk)
resp.form['label'] = 'Foo'
resp.form['duration'] = '120'
assert 'deleted' not in resp.form.fields
resp.form.submit().follow()
meeting_type.refresh_from_db()
assert meeting_type.label == 'Foo'
assert meeting_type.slug == other_meeting_type.slug
assert meeting_type.duration == 120
assert meeting_type.deleted is False
# check slug edition
resp = app.get('/manage/meetingtypes/%s/edit' % meeting_type.pk)
resp.form['slug'] = meeting_type2.slug
resp = resp.form.submit()
assert resp.context['form'].errors['slug'] == ['Another meeting type exists with the same identifier.']
def test_meetings_agenda_delete_meeting_type(app, admin_user):
agenda = Agenda(label='Foo bar', kind='meetings')
agenda.save()
meeting_type = MeetingType(agenda=agenda, label='Blah')
meeting_type.save()
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Blah')
resp = resp.click('Delete')
assert 'Are you sure you want to delete this?' in resp.text
assert 'disabled' not in resp.text
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
meeting_type.refresh_from_db()
assert meeting_type.deleted is True
assert '__deleted__' in meeting_type.slug
# meeting type not showing up anymore
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert 'Meeting Type Foo' not in resp.text
# it is possible to add a new meeting type with the same slug
new_meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah')
assert new_meeting_type.slug == 'blah'
def test_meetings_agenda_add_desk(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp.form['label'] = 'Foo bar'
resp.form['kind'] = 'meetings'
resp = resp.form.submit()
assert Desk.objects.count() == 1
assert str(Desk.objects.first()) == 'Desk 1'
agenda = Agenda.objects.get(slug='foo-bar')
agenda.desk_simple_management = False
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('New Desk')
resp.form['label'] = 'Desk A'
resp = resp.form.submit().follow()
assert Desk.objects.count() == 2
desk = Desk.objects.latest('pk')
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
resp = resp.click('New Desk')
resp.form['label'] = 'Desk A'
resp = resp.form.submit().follow()
assert Desk.objects.count() == 3
assert Desk.objects.filter(slug='desk-a-1').count() == 1
assert 'Desk A' in resp.text
new_desk = Desk.objects.latest('pk')
assert new_desk.timeperiod_set.count() == 0
# unknown pk
app.get('/manage/agendas/0/add-desk', status=404)
# wrong kind
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/agendas/%s/add-desk' % agenda.pk, status=404)
agenda.kind = 'events'
agenda.save()
app.get('/manage/agendas/%s/add-desk' % agenda.pk, status=404)
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_meetings_agenda_add_desk_from_another(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
assert Desk.objects.count() == 1
assert desk.timeperiodexceptionsource_set.count() == 0
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('New Desk')
resp.form['label'] = 'Desk B'
resp.form['copy_from'] = desk.pk
resp = resp.form.submit().follow()
assert Desk.objects.count() == 2
new_desk = Desk.objects.latest('pk')
assert new_desk.label == 'Desk B'
assert new_desk.timeperiod_set.count() == 1
assert (
new_desk.timeperiodexceptionsource_set.count() == 0
) # holidays not automatically added via duplication
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_meetings_agenda_add_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
assert Desk.objects.count() == 1
assert desk.timeperiodexceptionsource_set.count() == 0
app = login(app)
resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
resp.form['label'] = 'Desk B'
assert 'copy_from' not in resp.context['form'].fields
resp = resp.form.submit().follow()
assert Desk.objects.count() == 2
new_desk = Desk.objects.latest('pk')
assert new_desk.label == 'Desk B'
assert new_desk.timeperiod_set.count() == 1
assert (
new_desk.timeperiodexceptionsource_set.count() == 0
) # holidays not automatically added via duplication
# ok if no desks (should not happen)
Desk.objects.all().delete()
resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
resp.form['label'] = 'Desk'
assert 'copy_from' not in resp.context['form'].fields
resp = resp.form.submit().follow()
assert Desk.objects.count() == 1
new_desk = Desk.objects.latest('pk')
assert new_desk.timeperiodexceptionsource_set.count() == 1
def test_meetings_agenda_edit_desk(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
other_agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
other_desk = Desk.objects.create(agenda=other_agenda, label='Desk A')
assert other_desk.slug == desk.slug
app = login(app)
resp = app.get('/manage/desks/%s/edit' % desk.pk)
resp.form['label'] = 'Desk C'
resp.form['slug'] = desk.slug
resp = resp.form.submit().follow()
assert 'Desk A' not in resp.text
assert 'Desk B' in resp.text
assert 'Desk C' in resp.text
desk.refresh_from_db()
assert desk.label == 'Desk C'
assert desk.slug == other_desk.slug
# check slug edition
resp = app.get('/manage/desks/%s/edit' % desk.pk)
resp.form['slug'] = desk2.slug
resp = resp.form.submit()
assert resp.context['form'].errors['slug'] == ['Another desk exists with the same identifier.']
# unknown pk
app.get('/manage/desks/0/edit', status=404)
# wrong kind
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/desks/%s/edit' % desk.pk, status=404)
agenda.kind = 'events'
agenda.save()
app.get('/manage/desks/%s/edit' % desk.pk, status=404)
def test_meetings_agenda_delete_desk(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
Desk.objects.create(agenda=agenda, label='Desk A')
desk_b = Desk.objects.create(agenda=agenda, label='Desk B')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Desk A')
resp = resp.click('Delete')
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
assert Desk.objects.count() == 1
# only one desk
app.get('/manage/desks/%s/delete' % desk_b.pk, status=404)
desk_a = Desk.objects.create(agenda=agenda, label='Desk A')
# unknown pk
app.get('/manage/desks/0/delete', status=404)
# wrong kind
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/desks/%s/delete' % desk_a.pk, status=404)
agenda.kind = 'events'
agenda.save()
app.get('/manage/desks/%s/delete' % desk_a.pk, status=404)
def test_agenda_day_view(app, admin_user, manager_user, api_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
desk.save()
today = datetime.date.today()
meetingtype = MeetingType(agenda=agenda, label='Bar', duration=30)
meetingtype.save()
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' in resp.text # no time pediod
timeperiod = TimePeriod(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
timeperiod.save()
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' not in resp.text
assert 'div class="booking' not in resp.text
assert resp.text.count('<tr') == 9 # 10->18 (not included)
timeperiod.end_time = datetime.time(18, 30) # end during an hour
timeperiod.save()
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 10 # 10->18 (included)
# check opening hours cells
assert '<div class="opening-hours"' in resp.text
assert 'style="height: 850%; top: 0%;"' in resp.text
# book some slots
app.reset()
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
booking_url = resp.json['data'][0]['api']['fillslot_url']
booking_url2 = resp.json['data'][2]['api']['fillslot_url']
resp = app.post(booking_url)
resp = app.post_json(booking_url2, params={'label': 'foo', 'user': 'bar', 'url': 'http://baz/'})
app.reset()
login(app)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 2
assert 'hourspan-2' in resp.text # table CSS class
assert 'height: 50%; top: 0%;' in resp.text # booking cells
# create a shorter meeting type, this will change the table CSS class
# (and visually this will give more room for events)
meetingtype = MeetingType(agenda=agenda, label='Baz', duration=15)
meetingtype.save()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 2
assert 'hourspan-4' in resp.text # table CSS class
# cancel a booking
app.reset()
app.authorization = ('Basic', ('john.doe', 'password'))
booking = Booking.objects.all()[0]
resp = app.post('/api/booking/%s/cancel/' % booking.id)
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 1
app.reset()
login(app)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 1
# not enough permissions
app.reset()
app = login(app, username='manager', password='manager')
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=403)
# just enough permissions
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200)
# display exception
unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar')
TimePeriodException.objects.create(
label='Calendar exception',
unavailability_calendar=unavailability_calendar,
start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 13, 0)),
end_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 15, 0)),
)
unavailability_calendar.desks.add(desk)
TimePeriodException.objects.create(
label='Exception for the afternoon',
desk=desk,
start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 16, 0)),
end_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 23, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get(
'/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200
)
assert len(ctx.captured_queries) == 14
# day is displaying rows from 10am to 6pm,
# opening hours, 10am to 1pm gives top: 300%
# calendar exception, 1pm to 3pm gives heigh: 200%
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height: 200%; top: 300%;',
}
assert resp.pyquery.find('.exception-hours span')[0].text == 'Calendar exception'
# rest of the day, opened from 3pm to 4pm, since we left off at 500% it gives top: 600%
# then exception from 4pm to 6pm included, gives height: 300%
assert resp.pyquery.find('.exception-hours')[1].attrib == {
'class': 'exception-hours',
'style': 'height: 300%; top: 600%;',
}
assert resp.pyquery.find('.exception-hours span')[1].text == 'Exception for the afternoon'
@pytest.mark.parametrize(
'view',
(
'/manage/agendas/%(agenda)s/%(year)d/%(month)d/%(day)d/',
'/manage/agendas/%(agenda)s/%(year)d/%(month)d/',
),
)
def test_agenda_day_month_view_backoffice_url_translation(
app, admin_user, manager_user, api_user, settings, view
):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
desk.save()
today = datetime.date.today()
meetingtype = MeetingType(agenda=agenda, label='Bar', duration=30)
meetingtype.save()
timeperiod = TimePeriod.objects.create(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 30)
)
timeperiod.save()
app.authorization = ('Basic', ('john.doe', 'password'))
login(app)
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
booking_url = resp.json['data'][0]['api']['fillslot_url']
# unkown service, backoffice url stored and displayed as is
backoffice_url = 'http://example.net/foo/bar/'
resp = app.post(booking_url, params={'backoffice_url': backoffice_url})
cancel_url = resp.json['api']['cancel_url']
booking_id = resp.json['booking_id']
booking = Booking.objects.get(pk=booking_id)
assert booking.backoffice_url == backoffice_url
date = booking.event.start_datetime
url = view % {'agenda': agenda.id, 'year': date.year, 'month': date.month, 'day': date.day}
resp = app.get(url)
assert resp.text.count('div class="booking') == 1
assert backoffice_url in resp.text
# reset booking
resp = app.post(cancel_url)
assert resp.json['err'] == 0
# known service, backoffice url stored translated and displayed as it was passed
backoffice_url = 'http://example.org/backoffice/bar/'
resp = app.post(booking_url, params={'backoffice_url': backoffice_url})
cancel_url = resp.json['api']['cancel_url']
booking_id = resp.json['booking_id']
booking = Booking.objects.get(pk=booking_id)
assert booking.backoffice_url == 'publik://default/backoffice/bar/'
date = booking.event.start_datetime
resp = app.get(url)
assert resp.text.count('div class="booking') == 1
assert backoffice_url in resp.text
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_day_view_late_meeting(app, admin_user, kind):
today = datetime.date.today()
if kind == 'meetings':
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
MeetingType.objects.create(agenda=real_agenda, label='Bar', duration=30)
TimePeriod.objects.create(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(23, 30)
)
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 15
assert '<th class="hour">11 p.m.</th>' in resp.text
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_invalid_day_view(app, admin_user, kind):
if kind == 'meetings':
agenda = Agenda.objects.create(label='New Example', kind='meetings')
Desk.objects.create(agenda=agenda, label='New Desk')
MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
Desk.objects.create(agenda=real_agenda, label='New Desk')
MeetingType.objects.create(agenda=real_agenda, label='Bar', duration=30)
login(app)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, 2018, 11, 31), status=302)
assert resp.location.endswith('2018/11/30/')
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_day_view_event_outside_timeperiod(app, admin_user, kind):
if kind == 'meetings':
today = datetime.date.today()
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
else:
today = datetime.date.today()
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=real_agenda, label='Bar', duration=30)
login(app)
# no time period - no events
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' in resp.text
assert 'div class="booking' not in resp.text
# book some slots
for hour, minute in [(9, 0), (17, 0)]:
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now()).replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
# no time period - events are displayed
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('div class="booking') == 2
# bookings are cancelled
Booking.objects.update(cancellation_datetime=now())
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' in resp.text
assert resp.text.count('div class="booking') == 0
# events outside time period
Booking.objects.update(cancellation_datetime=None) # reset
TimePeriod.objects.create(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(16, 0)
)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('div class="booking') == 2
assert '<div class="opening-hours"' in resp.text
assert 'style="height: 600%; top: 100%;"' in resp.text
@freezegun.freeze_time('2020-10-01')
def test_agenda_events_day_view(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
today = datetime.date.today()
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Month view' in resp.text
assert "This day doesn't have any event configured." in resp.text
# event
Event.objects.create(
label='xyz', start_datetime=localtime().replace(day=11, month=11, year=2020), places=10, agenda=agenda
)
recurring_start_datetime = localtime().replace(day=4, month=11, year=2020)
event = Event.objects.create(
label='abc',
start_datetime=recurring_start_datetime,
places=10,
agenda=agenda,
recurrence_days=[recurring_start_datetime.weekday()],
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/2020/11/11/' % agenda.pk)
assert len(ctx.captured_queries) == 6
assert len(resp.pyquery.find('.event-info')) == 2
assert 'abc' in resp.pyquery.find('.event-info')[0].text
assert 'xyz' in resp.pyquery.find('.event-info')[1].text
# trying to access event recurrence creates it
event_count = Event.objects.count()
time = localtime(event.start_datetime).strftime('%H%M')
resp = resp.click(href='abc:2020-11-11-%s' % time)
assert Event.objects.count() == event_count + 1
resp = app.get('/manage/agendas/%s/2020/11/11/' % agenda.pk)
assert len(resp.pyquery.find('.event-info')) == 2
# create another event with recurrence, the same day/time
start_datetime = localtime().replace(day=4, month=11, year=2020)
event = Event.objects.create(
label='def',
start_datetime=start_datetime,
places=10,
agenda=agenda,
recurrence_days=[start_datetime.weekday()],
)
resp = app.get('/manage/agendas/%s/2020/11/11/' % agenda.pk)
# the event occurence in DB does not hide recurrence of the second recurrent event
assert len(resp.pyquery.find('.event-info')) == 3
def test_agenda_events_day_view_midnight(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events', default_view='day')
midnight = localtime(now().replace(day=11, month=11, year=2020)).replace(hour=0, minute=0)
Event.objects.create(label='xyz', start_datetime=midnight, places=10, agenda=agenda)
login(app)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk)
assert resp.location.endswith('/2020/11/11/')
resp = resp.follow()
assert len(resp.pyquery.find('.event-info')) == 1
@freezegun.freeze_time('2020-10-01')
def test_agenda_events_month_view(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
today = datetime.date.today()
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
assert 'Day view' in resp.text
assert "This month doesn't have any event configured." in resp.text
# add event in a future month, a wednesday
Event.objects.create(
label='xyz', start_datetime=localtime().replace(day=11, month=11, year=2020), places=10, agenda=agenda
)
# current month still doesn't have events
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert "This month doesn't have any event configured." in resp.text
# add recurring event on every Wednesday
start_datetime = localtime().replace(day=4, month=11, year=2020)
event = Event.objects.create(
label='abc',
start_datetime=start_datetime,
places=10,
agenda=agenda,
recurrence_days=[start_datetime.weekday()],
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 11))
assert len(ctx.captured_queries) == 9
assert len(resp.pyquery.find('.event-info')) == 5
assert 'abc' in resp.pyquery.find('.event-info')[0].text
assert 'abc' in resp.pyquery.find('.event-info')[1].text
assert 'xyz' in resp.pyquery.find('.event-info')[2].text
assert 'abc' in resp.pyquery.find('.event-info')[3].text
assert 'abc' in resp.pyquery.find('.event-info')[4].text
TimePeriodException.objects.create(
desk=agenda.desk_set.get(),
start_datetime=start_datetime + datetime.timedelta(days=6),
end_datetime=start_datetime + datetime.timedelta(days=8),
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 11))
assert len(resp.pyquery.find('.event-info')) == 4
assert 'abc' in resp.pyquery.find('li')[4].text_content()
assert 'Exception: 11/10/2020' in resp.pyquery.find('li')[5].text_content()
assert 'xyz' in resp.pyquery.find('li')[6].text_content()
# 12/2020 has 5 Wednesday
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
assert len(resp.pyquery.find('.event-info')) == 5
# trying to access event recurrence creates it
event_count = Event.objects.count()
time = localtime(event.start_datetime).strftime('%H%M')
resp = resp.click(href='abc:2020-12-02-%s' % time).follow()
assert Event.objects.count() == event_count + 1
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
assert len(resp.pyquery.find('.event-info')) == 5
# create another event with recurrence, the same day/time
start_datetime = localtime().replace(day=4, month=11, year=2020)
event = Event.objects.create(
label='def',
start_datetime=start_datetime,
places=10,
agenda=agenda,
recurrence_days=[start_datetime.weekday()],
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
# the event occurence in DB does not hide recurrence of the second recurrent event
assert len(resp.pyquery.find('.event-info')) == 10
resp = resp.click(href='abc:2020-12-09-%s' % time).follow()
resp = resp.click('Options')
resp.form['start_datetime_1'] = '13:12'
resp = resp.form.submit(status=302).follow()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
assert len(resp.pyquery.find('.event-info')) == 10
assert '1:12 p.m.' in resp.text
Event.objects.get(slug='abc--2020-12-02-%s' % time).cancel()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
assert 'Cancelled' in resp.text
bad_event_url = '/manage/agendas/%s/create_event_recurrence/abc:2020-10-8-1130/' % agenda.id
resp = app.get(bad_event_url, status=404)
def test_agenda_events_month_view_midnight(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events', default_view='day')
midnight = localtime(now().replace(day=1, month=11, year=2020)).replace(hour=0, minute=0)
Event.objects.create(label='xyz', start_datetime=midnight, places=10, agenda=agenda)
login(app)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk)
assert resp.location.endswith('/2020/11/')
resp = resp.follow()
assert len(resp.pyquery.find('.event-info')) == 1
def test_agenda_open_events_view(app, admin_user, manager_user):
agenda = Agenda.objects.create(
label='Events', kind='events', minimal_booking_delay=2, maximal_booking_delay=5
)
today = datetime.date.today()
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
assert 'Open events' in resp.text
resp = app.get('/manage/agendas/%s/events/open/' % agenda.pk)
assert 'Month view' in resp.text
assert "This agenda doesn't have any open event configured." in resp.text
# create some events
# past event
Event.objects.create(
agenda=agenda, label='event A', start_datetime=now() - datetime.timedelta(days=1), places=42
)
# too late
Event.objects.create(
agenda=agenda, label='event B', start_datetime=now() + datetime.timedelta(days=1), places=42
)
# too soon
Event.objects.create(
agenda=agenda, label='event C', start_datetime=now() + datetime.timedelta(days=6), places=42
)
# in range
Event.objects.create(
agenda=agenda, label='event D', start_datetime=now() + datetime.timedelta(days=3), places=42
)
# publication date in future
Event.objects.create(
agenda=agenda,
label='event E',
start_datetime=now() + datetime.timedelta(days=3),
publication_datetime=now() + datetime.timedelta(days=1),
places=42,
)
# publication date in past
Event.objects.create(
agenda=agenda,
label='event F',
start_datetime=now() + datetime.timedelta(days=3),
publication_datetime=now() - datetime.timedelta(days=1),
places=42,
)
# weekly recurring event, first recurrence is in the past but second is in range
start_datetime = now() - datetime.timedelta(days=3)
Event.objects.create(
label='event G',
start_datetime=start_datetime,
places=10,
agenda=agenda,
recurrence_days=[start_datetime.weekday()],
)
resp = app.get('/manage/agendas/%s/events/open/' % agenda.pk)
assert 'event A' not in resp.text
assert 'event B' not in resp.text
assert 'event C' not in resp.text
assert 'event D' in resp.text
assert 'event E' not in resp.text
assert 'event F' in resp.text
assert resp.text.count('event G') == 1
# event the first of February in 2 years at 00:00, already publicated
# and another event in January in 2 years
agenda.minimal_booking_delay = 0
agenda.maximal_booking_delay = 0
agenda.save()
Event.objects.create(
agenda=agenda,
label='event H',
start_datetime=now().replace(year=today.year + 2, month=1, day=15),
publication_datetime=now() - datetime.timedelta(days=1),
places=42,
)
start_datetime = localtime(now().replace(year=today.year + 2, month=2, day=1)).replace(hour=0, minute=0)
Event.objects.create(
agenda=agenda,
label='event H',
start_datetime=start_datetime,
publication_datetime=now() - datetime.timedelta(days=1),
places=42,
)
resp = app.get('/manage/agendas/%s/events/open/' % agenda.pk)
assert 'event H' in resp.text
assert '<h4>February %s</h4>' % (today.year + 2) in resp.text
# not enough permissions
app.reset()
app = login(app, username='manager', password='manager')
app.get('/manage/agendas/%s/events/open/' % agenda.pk, status=403)
# just enough permissions
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
resp = app.get('/manage/agendas/%s/events/open/' % agenda.pk)
assert 'Month view' in resp.text
# wrong kind
agenda.kind = 'meetings'
agenda.save()
app.get('/manage/agendas/%s/events/open/' % agenda.pk, status=404)
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/agendas/%s/events/open/' % agenda.pk, status=404)
def test_agenda_month_view(app, admin_user, manager_user, api_user):
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
today = datetime.date.today()
meetingtype = MeetingType(agenda=agenda, label='passeport', duration=20)
meetingtype.save()
login(app)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'Month view' in resp.text
resp = resp.click('Month view')
assert resp.request.url.endswith('%s/%s/' % (today.year, today.month))
assert 'Day view' in resp.text # date view link should be present
assert 'No opening hours this month.' in resp.text
today = datetime.date(2018, 11, 10) # fixed day
timeperiod_weekday = today.weekday()
timeperiod = TimePeriod(
desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
timeperiod.save()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert 'No opening hours this month.' not in resp.text
assert '<div class="booking' not in resp.text
first_month_day = today.replace(day=1)
last_month_day = today.replace(day=1, month=today.month + 1) - datetime.timedelta(days=1)
start_week_number = first_month_day.isocalendar()[1]
end_week_number = last_month_day.isocalendar()[1]
weeks_number = end_week_number - start_week_number + 1
assert resp.text.count('<tr') == 9 * weeks_number
# check opening hours cells
assert '<div class="opening-hours" style="height:800.0%;top:0.0%;width:97.0%;left:1.0%' in resp.text
# book some slots
app.reset()
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
booking_url = resp.json['data'][0]['api']['fillslot_url']
booking_url2 = resp.json['data'][2]['api']['fillslot_url']
booking = app.post(booking_url)
booking_2 = app.post_json(booking_url2, params={'label': 'foo book', 'user': 'bar', 'url': 'http://baz/'})
app.reset()
login(app)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
assert resp.text.count('<div class="booking" style="left:1.0%;height:33.0%;') == 2 # booking cells
assert len(resp.pyquery.find('span.desk')) == 0
desk = Desk.objects.create(agenda=agenda, label='Desk B')
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
assert len(resp.pyquery.find('span.desk')) == 2
timeperiod = TimePeriod(
desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
timeperiod.save()
app.reset()
booking_3 = app.post(booking_url)
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
# count occurences of timeperiod weekday in current month
d = first_month_day
weekdays = 0
while d <= last_month_day:
if d.weekday() == timeperiod_weekday:
weekdays += 1
d += datetime.timedelta(days=1)
assert resp.text.count('<div class="opening-hours"') == 2 * weekdays
current_month = today.strftime('%Y-%m')
if current_month in booking_url or current_month in booking_url2:
assert resp.text.count('<div class="booking"') == 3
# cancel bookings
app.reset()
app.post(booking.json['api']['cancel_url'])
app.post(booking_2.json['api']['cancel_url'])
app.post(booking_3.json['api']['cancel_url'])
# make sure the are not
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert resp.text.count('<div class="booking"') == 0
# check December is correctly displayed
today = datetime.date(2018, 12, 10)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert 'No opening hours this month.' not in resp.text
# display exception
unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar')
TimePeriodException.objects.create(
label='Calendar exception',
unavailability_calendar=unavailability_calendar,
start_datetime=make_aware(datetime.datetime(2018, 12, 15, 5, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 15, 14, 0)),
)
unavailability_calendar.desks.add(desk)
TimePeriodException.objects.create(
label='Exception for a December day',
desk=desk,
start_datetime=make_aware(datetime.datetime(2018, 12, 15, 14, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 15, 23, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert len(ctx.captured_queries) == 11
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height:400.0%;top:0.0%;width:48.0%;left:50.0%;',
'title': 'Calendar exception',
}
assert resp.pyquery.find('.exception-hours')[1].attrib == {
'class': 'exception-hours',
'style': 'height:400.0%;top:400.0%;width:48.0%;left:50.0%;',
'title': 'Exception for a December day',
}
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_weekend(app, admin_user, kind):
month, year = 1, 2019
monday = 0
if kind == 'meetings':
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
TimePeriod.objects.create(
desk=desk, weekday=monday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert 'Sunday' not in resp.text
assert 'Saturday' not in resp.text
# No Monday on first row since month starts a Tuesday
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 1
# When weekend is hidden, do not display an empty first week
month, year = 12, 2019 # month starts a Sunday
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
month, year = 6, 2019 # month starts a Saturday
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
saturday = 5
timeperiod_sat = TimePeriod.objects.create(
desk=desk, weekday=saturday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 5
sunday = 6
TimePeriod.objects.create(
desk=desk, weekday=sunday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
timeperiod_sat.delete()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_dst_change(app, admin_user, kind):
if kind == 'meetings':
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
meetingtype = MeetingType.objects.create(agenda=agenda, label='passeport', duration=20)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=real_agenda, label='passeport', duration=20)
for weekday in range(0, 7): # open all mornings
TimePeriod.objects.create(
desk=desk, weekday=weekday, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
)
login(app)
for date in ('2019-10-01', '2019-10-31'):
with freezegun.freeze_time(date):
resp = app.get('/manage/agendas/%s/2019/10/' % agenda.id)
# check all days are correctly aligned
assert resp.text.count('height:300.0%;top:0.0%') == 31
# book some slots
for date_tuple in [(2019, 10, 2, 10, 0), (2019, 10, 29, 10, 0)]:
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(make_aware(datetime.datetime(*date_tuple))),
)
Booking.objects.create(event=event)
# check booked slots are similarly aligned
login(app)
resp = app.get('/manage/agendas/%s/2019/10/' % agenda.id)
assert resp.text.count('height:33.0%;top:100.0%;') == 2
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_januaries(app, admin_user, kind):
if kind == 'meetings':
agenda = Agenda.objects.create(label='Passports', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
MeetingType.objects.create(agenda=agenda, label='passport', duration=20)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
MeetingType.objects.create(agenda=real_agenda, label='passport', duration=20)
TimePeriod(desk=desk, weekday=2, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)).save()
for year in range(2020, 2030):
date = datetime.date(year, 1, 1)
with freezegun.freeze_time(date):
login(app)
resp = app.get('/manage/agendas/%s/%s/1/' % (agenda.id, date.year))
assert resp.text.count('<th></th>') in (4, 5)
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_event_outside_timeperiod(app, admin_user, kind):
today = datetime.date.today()
if kind == 'meetings':
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=real_agenda, label='passport', duration=20)
login(app)
# no time period - no events
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert 'No opening hours this month.' in resp.text
assert 'div class="booking' not in resp.text
# book some slots
middle_day = now().replace(day=15)
for hour, minute in [(9, 0), (17, 0)]:
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now().replace(day=middle_day.day - middle_day.weekday() + 2)).replace(
hour=hour, minute=minute
),
)
Booking.objects.create(event=event)
# no time period - events are displayed
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 2
# bookings are cancelled
Booking.objects.update(cancellation_datetime=now())
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert 'No opening hours this month.' in resp.text
assert resp.text.count('div class="booking') == 0
# events outside time period
Booking.objects.update(cancellation_datetime=None) # reset
TimePeriod.objects.create(
desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0)
)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 2
assert '<div class="opening-hours" style="height:600.0%;top:100.0%;width:97.0%;left:1.0%' in resp.text
assert 'Sunday' not in resp.text
assert 'Saturday' not in resp.text
# create an event on saturday
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now()).replace(
day=middle_day.day - middle_day.weekday() + 5, hour=10, minute=0
),
)
Booking.objects.create(event=event)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 3
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
# bookings are cancelled
Booking.objects.update(cancellation_datetime=now())
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 0
# and a timeperiod
Booking.objects.update(cancellation_datetime=None) # reset
TimePeriod.objects.create(
desk=desk, weekday=5, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0)
)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 3
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
# create an event on sunday
middle_day = now().replace(day=15)
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now()).replace(
day=middle_day.day - middle_day.weekday() + 6, hour=10, minute=0
),
)
Booking.objects.create(event=event)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 4
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
# bookings are cancelled
Booking.objects.update(cancellation_datetime=now())
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 0
# and a timeperiod
Booking.objects.update(cancellation_datetime=None) # reset
TimePeriod.objects.create(
desk=desk, weekday=6, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0)
)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 4
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
def test_agenda_view_event(app, manager_user):
agenda = Agenda(label='Foo bar')
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
event = Event.objects.create(
label='xyz',
start_datetime=make_aware(datetime.datetime(2019, 12, 22, 17, 0)),
places=10,
agenda=agenda,
)
for i in range(8):
booking = Booking.objects.create(event=event)
if i < 5:
booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 14, 0 + i))
if i == 5:
booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 15, 0))
booking.user_first_name = 'Foo Bar'
booking.user_last_name = 'User'
if i == 6:
booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 16, 0))
booking.user_first_name = 'Foo Bar'
booking.user_last_name = 'User 2'
booking.label = 'Foo Bar Label 2'
if i == 7:
booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 17, 0))
booking.label = 'Foo Bar Label 3'
booking.save()
Booking.objects.create(event=event, cancellation_datetime=now())
app = login(app, username='manager', password='manager')
resp = app.get('/manage/agendas/%s/2019/12/' % agenda.id, status=200)
resp = resp.click('xyz')
assert 'Bookings (8/10): 2 remaining places' in resp.text
assert 'Waiting' not in resp.text
assert 'This event is overbooked.' not in resp.text
assert 'This event is full.' not in resp.text
event.waiting_list_places = 5
event.save()
resp = app.get(resp.request.url)
assert 'Waiting List (0/5): 5 remaining places' in resp.text
assert 'Anonymous, Dec. 21, 2019, 2 p.m.' in resp.text
assert 'Anonymous, Dec. 21, 2019, 2:01 p.m.' in resp.text
assert 'Anonymous, Dec. 21, 2019, 2:02 p.m.' in resp.text
assert 'Anonymous, Dec. 21, 2019, 2:03 p.m.' in resp.text
assert 'Anonymous, Dec. 21, 2019, 2:04 p.m.' in resp.text
assert 'Foo Bar User, Dec. 21, 2019, 3 p.m.' in resp.text
assert 'Foo Bar User 2, Dec. 21, 2019, 4 p.m.' in resp.text
assert 'Foo Bar Label 3, Dec. 21, 2019, 5 p.m.' in resp.text
booking = Booking.objects.order_by('pk')[0]
booking.in_waiting_list = True
booking.save()
booking = Booking.objects.order_by('pk')[1]
booking.in_waiting_list = True
booking.save()
resp = app.get(resp.request.url)
assert 'Waiting List (2/5): 3 remaining places' in resp.text
assert 'Bookings (6/10): 4 remaining places' in resp.text
assert list(resp.context['booked']) == list(Booking.objects.order_by('creation_datetime')[2:8])
assert list(resp.context['waiting']) == list(Booking.objects.order_by('creation_datetime')[0:2])
event.places = 5
event.save()
resp = app.get(resp.request.url)
assert 'This event is overbooked.' in resp.text
assert 'This event is full.' not in resp.text
event.places = 6
event.save()
resp = app.get(resp.request.url)
assert 'This event is overbooked.' not in resp.text
assert 'This event is full.' in resp.text
def test_agenda_view_edit_event(app, manager_user):
test_agenda_view_event(app, manager_user)
agenda = Agenda.objects.first()
resp = app.get('/manage/agendas/%s/2019/12/' % agenda.id, status=200)
resp = resp.click('xyz')
assert 'Options' not in resp.text
assert 'Delete' not in resp.text
agenda.edit_role = manager_user.groups.all()[0]
agenda.save()
event_url = resp.request.url
resp = app.get(event_url)
assert 'Options' in resp.text
resp = resp.click('Options')
resp.form['start_datetime_0'] = agenda.event_set.first().start_datetime.strftime('%Y-%m-%d')
resp.form['start_datetime_1'] = agenda.event_set.first().start_datetime.strftime('%H:%M')
resp = resp.form.submit(status=302).follow()
assert event_url == resp.request.url
resp = resp.click('Delete')
resp = resp.form.submit()
assert Event.objects.count() == 0
def test_virtual_agenda_add(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp.form['label'] = 'Virtual agenda'
resp.form['kind'] = 'virtual'
resp = resp.form.submit()
agenda = Agenda.objects.get(label='Virtual agenda')
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
assert agenda.minimal_booking_delay is None
assert agenda.maximal_booking_delay is None
def test_virtual_agenda_day_view(app, admin_user, manager_user):
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda_1 = Agenda.objects.create(label='Real 1', kind='meetings')
real_agenda_2 = Agenda.objects.create(label='Real 2', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_1)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_2)
desk1 = Desk.objects.create(agenda=real_agenda_1, label='New Desk')
desk2 = Desk.objects.create(agenda=real_agenda_2, label='New Desk')
today = datetime.date.today()
meetingtype1 = MeetingType.objects.create(agenda=real_agenda_1, label='Bar', duration=30)
meetingtype2 = MeetingType.objects.create(agenda=real_agenda_2, label='Bar', duration=30)
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' in resp.text # no time pediod
timeperiod = TimePeriod.objects.create(
desk=desk1, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' not in resp.text
assert 'div class="booking' not in resp.text
assert resp.text.count('<tr') == 9 # 10->18 (not included)
timeperiod.end_time = datetime.time(18, 30) # end during an hour
timeperiod.save()
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 10 # 10->18 (included)
# check opening hours cells
assert '<div class="opening-hours"' in resp.text
assert 'style="height: 850%; top: 0%;"' in resp.text
# book some slots
for hour, minute in [(10, 30), (14, 0)]:
event = Event.objects.create(
agenda=real_agenda_1,
places=1,
desk=desk1,
meeting_type=meetingtype1,
start_datetime=now().replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
event = Event.objects.create(
agenda=real_agenda_2,
places=1,
desk=desk2,
meeting_type=meetingtype2,
start_datetime=now().replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 4
assert 'hourspan-2' in resp.text # table CSS class
assert 'height: 50%; top: 0%;' in resp.text # booking cells
# create a shorter meeting type, this will change the table CSS class
# (and visually this will give more room for events)
MeetingType.objects.create(agenda=real_agenda_1, label='Baz', duration=15)
MeetingType.objects.create(agenda=real_agenda_2, label='Baz', duration=15)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 4
assert 'hourspan-4' in resp.text # table CSS class
# cancel a booking
booking = Booking.objects.first()
booking.cancel()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 3
# not enough permissions
app.reset()
app = login(app, username='manager', password='manager')
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=403)
# just enough permissions
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200)
# display exception
TimePeriodException.objects.create(
label='Exception for the afternoon',
desk=desk1,
start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 13, 0)),
end_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 23, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get(
'/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200
)
assert len(ctx.captured_queries) == 15
# day is displaying rows from 10am to 6pm,
# opening hours, 10am to 1pm gives top: 300%
# rest of the day, 1pm to 6(+1)pm gives 600%
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height: 600%; top: 300%;',
}
assert resp.pyquery.find('.exception-hours span')[0].text == 'Exception for the afternoon'
def test_virtual_agenda_month_view(app, admin_user):
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda_1 = Agenda.objects.create(label='Real 1', kind='meetings')
real_agenda_2 = Agenda.objects.create(label='Real 2', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_1)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_2)
desk1 = Desk.objects.create(agenda=real_agenda_1, label='New Desk')
desk2 = Desk.objects.create(agenda=real_agenda_2, label='New Desk')
today = datetime.date.today()
meetingtype1 = MeetingType.objects.create(agenda=real_agenda_1, label='Bar', duration=30)
meetingtype2 = MeetingType.objects.create(agenda=real_agenda_2, label='Bar', duration=30)
login(app)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'Month view' in resp.text
resp = resp.click('Month view')
assert resp.request.url.endswith('%s/%s/' % (today.year, today.month))
assert 'Day view' in resp.text # date view link should be present
assert 'No opening hours this month.' in resp.text
today = datetime.date(2018, 11, 10) # fixed day
timeperiod_weekday = today.weekday()
TimePeriod.objects.create(
desk=desk1, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert 'No opening hours this month.' not in resp.text
assert '<div class="booking' not in resp.text
first_month_day = today.replace(day=1)
last_month_day = today.replace(day=1, month=today.month + 1) - datetime.timedelta(days=1)
start_week_number = first_month_day.isocalendar()[1]
end_week_number = last_month_day.isocalendar()[1]
weeks_number = end_week_number - start_week_number + 1
assert resp.text.count('<tr') == 9 * weeks_number
# check opening hours cells
assert '<div class="opening-hours" style="height:800.0%;top:0.0%;width:48.0%;left:1.0%' in resp.text
# book some slots
for hour, minute in [(10, 30), (14, 0)]:
event = Event.objects.create(
agenda=real_agenda_1,
places=1,
desk=desk1,
meeting_type=meetingtype1,
start_datetime=now().replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
event = Event.objects.create(
agenda=real_agenda_2,
places=1,
desk=desk2,
meeting_type=meetingtype2,
start_datetime=now().replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
assert resp.text.count('<div class="booking" style="left:1.0%;height:50.0%;') == 2 # booking cells
assert (
resp.text.count('<div class="booking" style="left:50.0%;height:50.0%;min-height:50.0%;') == 2
) # booking cells
# cancel a booking
booking = Booking.objects.first()
booking.cancel()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, date.year, date.month))
assert resp.text.count('<div class="booking"') == 3
# check December is correctly displayed
today = datetime.date(2018, 12, 10)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert 'No opening hours this month.' not in resp.text
# display exception
TimePeriodException.objects.create(
label='Exception for a December day',
desk=desk1,
start_datetime=make_aware(datetime.datetime(2018, 12, 15, 5, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 15, 23, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert len(ctx.captured_queries) == 12
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height:800.0%;top:0.0%;width:48.0%;left:1.0%;',
'title': 'Exception for a December day',
}
def test_virtual_agenda_settings_empty(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert 'Include Agenda' in resp.text
assert 'Options' in resp.text
assert 'Export' in resp.text
assert 'Delete' in resp.text
assert 'Included Agendas' in resp.text
assert 'Add Excluded Period' in resp.text
assert "This virtual agenda doesn't include any agenda yet" in resp.text
# No meeting types yet
assert 'Meeting Types' not in resp.text
# No absence yet
assert 'Excluded Periods' not in resp.text
def test_virtual_agenda_settings(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
TimePeriod.objects.create(
agenda=agenda, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert "This virtual agenda doesn't include any agenda yet" not in resp.text
for real_agenda in [meeting_agenda_1, meeting_agenda_2]:
assert real_agenda.label in resp.text
assert '/manage/agendas/%s/settings' % real_agenda.pk in resp.text
assert 'Meeting Types' in resp.text
assert 'MT' in resp.text
assert 'mt' in resp.text
assert '10' in resp.text
assert 'Excluded Periods' in resp.text
assert 'Monday' in resp.text
# Error message when incompatible meeting types
mt2.delete()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert "This virtual agenda doesn't have any meeting type." in resp.text
def test_virtual_agenda_settings_include(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
Agenda.objects.create(label='Event agenda', kind='events')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
MeetingType.objects.create(label='MT', duration=30, agenda=meeting_agenda_1)
Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Include Agenda')
# Only meetings agenda are proposed (2) + 1 empty choice = 3
assert len(resp.form['real_agenda'].options) == 3
# Include a real agenda
resp.form['real_agenda'].value = meeting_agenda_1.pk
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
assert VirtualMember.objects.get(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
resp = resp.follow()
resp = resp.click('Include Agenda')
# The previously include agenda is not proposed any more
assert len(resp.form['real_agenda'].options) == 2
def test_virtual_agenda_settings_add_excluded_period(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Add Excluded Period')
resp.form.get('weekdays', index=0).checked = True
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '17:00'
resp = resp.form.submit()
tp = TimePeriod.objects.get(agenda=agenda)
assert tp.weekday == 0
assert tp.start_time.hour == 10
assert tp.start_time.minute == 0
assert tp.end_time.hour == 17
assert tp.end_time.minute == 0
resp = resp.follow()
assert 'Monday / 10 a.m. → 5 p.m.' in resp.text
def test_virtual_agenda_settings_edit_excluded_period(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
tp = TimePeriod.objects.create(
agenda=agenda, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
url = '/manage/timeperiods/%s/edit' % tp.pk
resp = resp.click(href=url)
resp.form['start_time'] = '11:00'
resp = resp.form.submit()
tp = TimePeriod.objects.get(agenda=agenda)
assert tp.weekday == 0
assert tp.start_time.hour == 11
assert tp.start_time.minute == 0
assert tp.end_time.hour == 18
assert tp.end_time.minute == 0
def test_virtual_agenda_settings_delete_excluded_period(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
tp = TimePeriod.objects.create(
agenda=agenda, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
url = '/manage/timeperiods/%s/delete' % tp.pk
resp = resp.click(href=url)
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
assert TimePeriod.objects.count() == 0
def test_virtual_agenda_settings_include_incompatible_agenda(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
app = login(app)
# refused because different slug
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mtt', duration=10)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;MT&quot; (10 minutes) (identifier: mt) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
# refused because different duration
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=15)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;MT&quot; (10 minutes) (identifier: mt) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
# refused because different label
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MTT', slug='mt', duration=10)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;MT&quot; (10 minutes) (identifier: mt) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
# refused because has one more meeting type
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='AA', slug='aa', duration=30)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Extra meeting type, &quot;AA&quot;.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
# ok because mt2 is marked as deleted
mt2.deleted = True
mt2.save()
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert meeting_agenda_2.virtual_agendas.count() == 1
VirtualMember.objects.filter(real_agenda=meeting_agenda_2).delete()
mt.delete()
mt2.delete()
# refused because has one less meeting type
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
mt2 = MeetingType.objects.create(agenda=meeting_agenda_1, label='AA', slug='aa', duration=30)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;AA&quot; (30 minutes) (identifier: aa) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
mt2.delete()
# deleted meeting type is not checked
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10, deleted=True)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;MT&quot; (10 minutes) (identifier: mt) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
def test_cant_delete_meetingtype_used_by_virtual_agenda(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
mt1 = MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
# ok because there is only one agenda in the virtual agenda
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % meeting_agenda_1.pk)
resp = resp.click('MT')
resp = resp.click('Delete')
resp = resp.form.submit()
assert not meeting_agenda_1.iter_meetingtypes()
mt1.deleted = False
mt1.save()
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
resp = app.get('/manage/agendas/%s/settings' % meeting_agenda_2.pk)
resp = resp.click('MT')
resp = resp.click('Delete')
assert 'This cannot be removed as it used by a virtual agenda' in resp.text
assert 'disabled' in resp.text
resp = app.post('/manage/meetingtypes/%s/delete' % mt2.pk, status=403)
def test_cant_modify_meetingtype_used_by_virtual_agenda(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
mt1 = MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
app = login(app)
# ok because there is only one agenda in the virtual agenda
resp = app.get('/manage/meetingtypes/%s/edit' % mt1.pk)
resp.form['label'].value = 'MTT'
resp = resp.form.submit()
assert MeetingType.objects.get(agenda=meeting_agenda_1, label='MTT', slug='mt', duration=10)
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='MTT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
app = login(app)
resp = app.get('/manage/meetingtypes/%s/edit' % mt2.pk)
resp.form['label'].value = 'Oho'
resp = resp.form.submit()
assert 'This meetingtype is used by a virtual agenda' in resp.text
mt = MeetingType.objects.get(pk=mt2.pk)
assert mt.label == 'MTT'
def test_cant_add_meetingtype_if_virtual_agenda(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
app = login(app)
# ok because there is only one agenda in the virtual agenda
resp = app.get('/manage/agendas/%s/add-meeting-type' % meeting_agenda_1.pk)
resp.form['duration'].value = '12'
resp.form['label'].value = 'Oho'
resp = resp.form.submit()
assert MeetingType.objects.filter(agenda=meeting_agenda_1).count() == 2
MeetingType.objects.get(agenda=meeting_agenda_1, label='Oho').delete()
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
app = login(app)
resp = app.get('/manage/agendas/%s/add-meeting-type' % meeting_agenda_1.pk)
resp.form['duration'].value = '12'
resp.form['label'].value = 'Oho'
resp = resp.form.submit()
assert 'Can&#39;t add a meetingtype to an agenda that is included in a virtual agenda.' in resp.text
assert MeetingType.objects.filter(agenda=meeting_agenda_1).count() == 1
def test_duplicate_agenda(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar', slug='foo-bar', kind='meetings')
assert Agenda.objects.count() == 1
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Duplicate')
resp = resp.form.submit()
assert Agenda.objects.count() == 2
new_agenda = Agenda.objects.exclude(pk=agenda.pk).first()
assert resp.location == '/manage/agendas/%s/settings' % new_agenda.pk
assert new_agenda.pk != agenda.pk
resp = resp.follow()
assert 'copy-of-foo-bar' in resp.text
resp = resp.click('Duplicate')
resp.form['label'] = 'hop'
resp = resp.form.submit().follow()
assert 'hop' in resp.text
def test_booking_cancellation_meetings_agenda(app, admin_user, manager_user, managers_group, api_user):
agenda = Agenda.objects.create(label='Passeports', kind='meetings', view_role=managers_group)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
meetingtype = MeetingType(agenda=agenda, label='passeport', duration=20)
meetingtype.save()
today = datetime.date(2018, 11, 10) # fixed day
timeperiod_weekday = today.weekday()
timeperiod = TimePeriod(
desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
timeperiod.save()
# book a slot
app.authorization = ('Basic', ('john.doe', 'password'))
bookings_resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
booking_url = bookings_resp.json['data'][0]['api']['fillslot_url']
booking_json = app.post_json(booking_url, params={'backoffice_url': 'http://example.org/'}).json
booking = Booking.objects.get(pk=booking_json['booking_id'])
date = booking.event.start_datetime
month_view_url = '/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month)
app.reset()
login(app, username='admin', password='admin')
resp = app.get(month_view_url)
assert len(resp.pyquery.find('div.booking a.cancel')) == 1 # cancel button is shown
resp = resp.click('Cancel')
# no callback url was provided at booking, warn user but allow cancellation as they are admin
assert 'no callback url' in resp.text
assert 'Proceed with cancellation' in resp.text
resp = resp.form.submit()
booking.refresh_from_db()
assert booking.cancellation_datetime
app.reset()
booking.cancellation_datetime = None
booking.save()
login(app, username='manager', password='manager')
resp = app.get(month_view_url)
assert len(resp.pyquery.find('div.booking a.cancel')) == 1 # cancel button is shown
resp = resp.click('Cancel')
# no callback url was provided at booking, warn user cancellation is forbidden
assert 'no callback url' in resp.text
assert not 'Proceed with cancellation' in resp.text
booking.delete()
# provide callback url this time
booking_url2 = bookings_resp.json['data'][1]['api']['fillslot_url']
booking_json2 = app.post_json(
booking_url2, params={'cancel_callback_url': 'http://example.org/jump/trigger/'}
).json
resp = app.get(month_view_url)
resp = resp.click('Cancel')
assert not 'no callback url' in resp.text
# a signed request is sent to callback_url
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
resp = resp.form.submit()
url = mock_send.call_args[0][0].url
assert check_query(url.split('?', 1)[-1], 'chrono')
booking2 = Booking.objects.get(pk=booking_json2['booking_id'])
resp = resp.follow()
assert not resp.pyquery.find('div.booking')
assert booking2.cancellation_datetime
# request fails
booking_url3 = bookings_resp.json['data'][2]['api']['fillslot_url']
booking_json3 = app.post_json(
booking_url3, params={'cancel_callback_url': 'http://example.org/jump/trigger/'}
).json
booking3 = Booking.objects.get(pk=booking_json3['booking_id'])
def mocked_requests_connection_error(*args, **kwargs):
raise requests.exceptions.ConnectionError('unreachable')
resp = app.get(month_view_url)
resp = resp.click('Cancel')
assert resp.form['disable_trigger'].attrs['type'] == 'hidden'
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
resp = resp.form.submit()
assert 'error' in resp.text
booking3.refresh_from_db()
assert not booking3.cancellation_datetime
# there is an option to force cancellation
resp.form['disable_trigger'] = True
resp = resp.form.submit()
booking3.refresh_from_db()
assert booking3.cancellation_datetime
# test day view
day_view_url = '/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day)
booking_url4 = bookings_resp.json['data'][3]['api']['fillslot_url']
booking_json4 = app.post(booking_url4).json
resp = app.get(day_view_url)
resp = resp.click('Cancel')
resp = resp.form.submit()
assert resp.location.endswith(day_view_url)
booking4 = Booking.objects.get(pk=booking_json4['booking_id'])
assert booking4.cancellation_datetime
# again
app.get('/manage/agendas/%s/bookings/%s/cancel' % (agenda.pk, booking4.pk), status=404)
def test_booking_cancellation_meetings_agenda_backoffice_url_translation(
app, admin_user, manager_user, managers_group, api_user
):
agenda = Agenda.objects.create(label='Passeports', kind='meetings', view_role=managers_group)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
meetingtype = MeetingType(agenda=agenda, label='passeport', duration=20)
meetingtype.save()
today = datetime.date(2018, 11, 10) # fixed day
timeperiod_weekday = today.weekday()
timeperiod = TimePeriod(
desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
timeperiod.save()
# book a slot
app.authorization = ('Basic', ('john.doe', 'password'))
bookings_resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
booking_url = bookings_resp.json['data'][0]['api']['fillslot_url']
booking_json = app.post_json(booking_url, params={'backoffice_url': 'http://example.org/'}).json
booking = Booking.objects.get(pk=booking_json['booking_id'])
assert booking.backoffice_url == 'publik://default/'
date = booking.event.start_datetime
month_view_url = '/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month)
app.reset()
login(app, username='admin', password='admin')
resp = app.get(month_view_url)
resp = resp.click('Cancel')
assert 'http://example.org/' in resp.text
def test_agenda_notifications(app, admin_user, managers_group):
agenda = Agenda.objects.create(label='Events', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Notifications' in resp.text
assert 'Notifications are disabled' in resp.text
resp = resp.click('Configure', href='notifications')
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Notifications are disabled' in resp.text
resp = resp.click('Configure', href='notifications')
resp.form['cancelled_event'] = 'use-email-field'
resp = resp.form.submit().follow()
assert 'Notifications are disabled' in resp.text
agenda.view_role = managers_group
agenda.save()
resp = resp.click('Configure', href='notifications')
resp.form['cancelled_event_emails'] = 'hop@entrouvert.com, top@entrouvert.com'
resp.form['almost_full_event'] = 'edit-role'
option = resp.form['almost_full_event'].selectedIndex
assert resp.form['almost_full_event'].options[option][2] == 'Edit Role (undefined)'
resp.form['full_event'] = 'view-role'
option = resp.form['full_event'].selectedIndex
assert resp.form['full_event'].options[option][2] == 'View Role (Managers)'
resp = resp.form.submit().follow()
settings = agenda.notifications_settings
assert settings.almost_full_event == 'edit-role'
assert settings.full_event == 'view-role'
assert settings.cancelled_event == 'use-email-field'
assert settings.cancelled_event_emails == ['hop@entrouvert.com', 'top@entrouvert.com']
assert 'Cancelled event: hop@entrouvert.com, top@entrouvert.com will be notified' in resp.text
assert 'Almost full event (90%): Edit Role (undefined) will be notified' in resp.text
assert 'Full event: View Role (Managers) will be notified' in resp.text
agenda.edit_role = Group.objects.create(name='hop')
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Almost full event (90%): Edit Role (hop) will be notified' in resp.text
def test_agenda_notifications_no_old_events(app, admin_user, mailoutbox):
agenda = Agenda.objects.create(label='Events', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10, label='Old event')
event.cancelled = True
event.save()
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Configure', href='notifications')
resp.form['cancelled_event'] = 'use-email-field'
resp.form['cancelled_event_emails'] = 'hop@entrouvert.com'
resp.form.submit()
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10, label='New event')
event.cancelled = True
event.save()
call_command('send_email_notifications')
# no notification is sent for old event
assert len(mailoutbox) == 1
assert 'New event' in mailoutbox[0].subject
def test_manager_reminders(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Booking reminders' in resp.text
assert 'Reminders are disabled' in resp.text
resp = resp.click('Configure', href='reminder')
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Reminders are disabled' in resp.text
resp = resp.click('Configure', href='reminder')
assert not 'SMS' in resp.text
resp.form['days'] = 3
resp.form['send_email'] = True
resp.form['email_extra_info'] = 'test'
resp = resp.form.submit().follow()
assert 'Users will be reminded of their booking by email, 3 days in advance.' in resp.text
with override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO'):
resp = resp.click('Configure', href='reminder')
resp.form['send_sms'] = True
resp = resp.form.submit().follow()
assert 'Users will be reminded of their booking both by email and by SMS, 3 days in advance.' in resp.text
agenda = Agenda.objects.create(label='Meetings', kind='meetings')
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Booking reminders' in resp.text
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert not 'Booking reminders' in resp.text
def test_manager_reminders_preview(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
AgendaReminderSettings.objects.create(
agenda=agenda,
days=1,
send_email=True,
email_extra_info='An ID will be required in order to process your form.',
send_sms=True,
sms_extra_info='Take ID card.',
)
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert (
'Users will be reminded of their booking both by email and by SMS, one day in advance.' in resp.text
)
resp = resp.click('Preview email')
assert 'Users will receive the following email:' in resp.text
assert len(resp.pyquery.find('p.email-subject')) == 1
assert '<strong>Subject:</strong> Reminder for your booking tomorrow at 2:30 p.m.' in resp.text
assert (
'You have a booking for event "Lorem Ipsum <small>(event label)</small>", on Tuesday 2 June at 2:30 p.m..'
in resp.text
)
assert 'An ID will be required' in resp.text
assert ' ea commodo consequat. <small>(event description, if present)</small>' in resp.text
assert 'Pricing: ... <small>(event pricing, if present)</small>' in resp.text
assert '<a href="#">More information<br>(link to event url, if present)</a>' in resp.text
resp = resp.click('Return to settings')
resp = resp.click('Preview SMS')
assert 'Users will receive the following SMS:' in resp.text
assert (
'Reminder: you have a booking for event "Lorem Ipsum <small>(event label)</small>", on 02/06 at 2:30 p.m.. Take ID card.'
in resp.text
)
def test_export_site(app, admin_user):
login(app)
resp = app.get('/manage/')
resp = resp.click('Export')
with freezegun.freeze_time('2020-06-15'):
resp = resp.form.submit()
assert resp.headers['content-type'] == 'application/json'
assert resp.headers['content-disposition'] == 'attachment; filename="export_agendas_20200615.json"'
site_json = json.loads(resp.text)
assert site_json == {'unavailability_calendars': [], 'agendas': [], 'absence_reason_groups': []}
agenda = Agenda.objects.create(label='Foo Bar', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
UnavailabilityCalendar.objects.create(label='Calendar 1')
resp = app.get('/manage/agendas/export/')
resp = resp.form.submit()
site_json = json.loads(resp.text)
assert len(site_json['agendas']) == 1
assert len(site_json['unavailability_calendars']) == 1
assert len(site_json['absence_reason_groups']) == 0
resp = app.get('/manage/agendas/export/')
resp.form['agendas'] = False
resp.form['absence_reason_groups'] = False
resp = resp.form.submit()
site_json = json.loads(resp.text)
assert 'agendas' not in site_json
assert 'unavailability_calendars' in site_json
assert 'absence_reason_groups' not in site_json
def test_manager_agenda_roles(app, admin_user, manager_user):
agenda = Agenda.objects.create(label='Events', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Configure', href='roles')
resp.form['edit_role'] = manager_user.groups.all()[0].pk
resp = resp.form.submit().follow()
assert 'Edit Role: Managers' in resp.text
def test_manager_agenda_booking_delays(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Configure', href='delays')
resp.form['maximal_booking_delay'] = 42
resp = resp.form.submit().follow()
assert '42 days' in resp.text
agenda.refresh_from_db()
assert agenda.maximal_booking_delay == 42
@pytest.mark.parametrize(
'view',
(
'/manage/agendas/%(agenda)s/%(year)d/%(month)d/%(day)d/',
'/manage/agendas/%(agenda)s/%(year)d/%(month)d/',
),
)
def test_agenda_booking_colors(app, admin_user, api_user, view):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
today = datetime.date.today()
TimePeriod.objects.create(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
app.authorization = ('Basic', ('john.doe', 'password'))
datetimes_resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
booking_url = datetimes_resp.json['data'][0]['api']['fillslot_url']
# book first slot without colors
resp = app.post(booking_url)
date = Booking.objects.all()[0].event.start_datetime
app.reset()
login(app)
url = view % {'agenda': agenda.id, 'year': date.year, 'month': date.month, 'day': date.day}
resp = app.get(url)
assert len(resp.pyquery.find('div.booking')) == 1
assert 'booking-color-' not in resp.text
assert 'Booking colors:' not in resp.text
app.reset()
app.authorization = ('Basic', ('john.doe', 'password'))
booking_url2 = datetimes_resp.json['data'][1]['api']['fillslot_url']
booking_url3 = datetimes_resp.json['data'][2]['api']['fillslot_url']
resp = app.post_json(booking_url2, params={'use_color_for': 'Cooking'})
resp = app.post_json(booking_url3, params={'use_color_for': 'Cooking'})
booking = Booking.objects.get(pk=resp.json['booking_id'])
app.reset()
login(app)
resp = app.get(url)
assert len(resp.pyquery.find('div.booking')) == 3
assert len(resp.pyquery.find('div.booking.booking-color-%s' % booking.color.index)) == 2
assert 'Booking colors:' in resp.text
assert len(resp.pyquery.find('div.booking-colors span.booking-color-label')) == 1
assert resp.text.count('Cooking') == 3 # 2 bookings + legend
app.reset()
app.authorization = ('Basic', ('john.doe', 'password'))
booking_url4 = datetimes_resp.json['data'][3]['api']['fillslot_url']
resp = app.post_json(booking_url4, params={'use_color_for': 'Swimming'})
new_booking = Booking.objects.get(pk=resp.json['booking_id'])
app.reset()
login(app)
resp = app.get(url)
assert len(resp.pyquery.find('div.booking')) == 4
assert len(resp.pyquery.find('div.booking.booking-color-%s' % booking.color.index)) == 2
assert len(resp.pyquery.find('div.booking.booking-color-%s' % new_booking.color.index)) == 1
assert resp.text.count('Swimming') == 2 # 1 booking + legend
assert 'Booking colors:' in resp.text
assert len(resp.pyquery.find('div.booking-colors span.booking-color-label')) == 2