chrono/tests/manager/test_all.py

3998 lines
167 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 chrono.agendas.models import (
Agenda,
AgendaReminderSettings,
Booking,
Desk,
Event,
EventsType,
MeetingType,
TimePeriod,
TimePeriodException,
UnavailabilityCalendar,
VirtualMember,
)
from chrono.utils.signature import check_query
from chrono.utils.timezone import localtime, make_aware, now
from tests.utils import login
pytestmark = pytest.mark.django_db
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)
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
agenda.default_view = 'open_events'
agenda.save()
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/events/open/' % agenda.pk)
agenda.default_view = 'day'
agenda.save()
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
agenda.default_view = 'week'
agenda.save()
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/' % agenda.pk)
# old month/week/days views
resp = app.get('/manage/agendas/%s/2022/12/15/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/' % agenda.pk)
resp = app.get('/manage/agendas/%s/2022/12/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/' % agenda.pk)
resp = app.get('/manage/agendas/%s/2022/week/1/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/' % 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/month/2020/07/12/' % 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/month/2020/05/13/' % 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/month/2020/06/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/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/2020/09/10/' % 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/month/2020/08/11/' % 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/month/2020/07/12/' % 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/month/2020/07/12/' % agenda.pk)
@freezegun.freeze_time('2020-07-12')
def test_events_agenda_week_redirect(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar', kind='events')
app = login(app)
# no event, redirect to current week
resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/2020/07/12/' % 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/week/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/2020/05/13/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() - datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/2020/06/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/week/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/2020/09/10/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() + datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/2020/08/11/' % agenda.pk)
# don't check events for meetings
agenda.kind = 'virtual'
agenda.save()
resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/2020/07/12/' % agenda.pk)
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/2020/07/12/' % 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/day/2020/07/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/day/2020/05/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/day/2020/06/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/day/2020/09/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/day/2020/08/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/day/2020/07/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/day/2020/07/12/' % agenda.pk)
def test_meetings_agenda_redirect(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar', kind='meetings')
app = login(app)
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
agenda.default_view = 'month'
agenda.save()
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
agenda.default_view = 'week'
agenda.save()
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/' % agenda.pk)
# old month/week/days views
resp = app.get('/manage/agendas/%s/2022/12/15/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/' % agenda.pk)
resp = app.get('/manage/agendas/%s/2022/12/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/' % agenda.pk)
resp = app.get('/manage/agendas/%s/2022/week/1/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/' % agenda.pk)
def test_virtual_agenda_redirect(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar', kind='virtual')
app = login(app)
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
agenda.default_view = 'month'
agenda.save()
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
agenda.default_view = 'week'
agenda.save()
for agenda_id in [agenda.pk, agenda.slug]:
resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
assert resp.location.endswith('/manage/agendas/%s/week/' % agenda.pk)
# old month/week/days views
resp = app.get('/manage/agendas/%s/2022/12/15/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/' % agenda.pk)
resp = app.get('/manage/agendas/%s/2022/12/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/' % agenda.pk)
resp = app.get('/manage/agendas/%s/2022/week/1/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/' % 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_redirect(app, admin_user):
agenda = Agenda.objects.create(label='Foo Bar')
app = login(app)
for kind in ['events', 'meetings', 'virtual']:
agenda.kind = kind
agenda.save()
resp = app.get('/manage/agendas/%s/settings/' % agenda.slug, status=302)
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
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_virtual = 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
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
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 'open_events' not in [k for k, v in resp.context['form'].fields['default_view'].choices]
resp = app.get('/manage/agendas/%s/edit' % agenda_virtual.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
def test_options_events_agenda_events_type(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
app = login(app)
resp = app.get('/manage/agendas/%s/edit' % agenda.pk)
assert 'events_type' not in resp.context['form'].fields
events_type = EventsType.objects.create(label='Foo', slug='foo')
resp = app.get('/manage/agendas/%s/edit' % agenda.pk)
assert 'events_type' in resp.context['form'].fields
resp.form['events_type'] = events_type.pk
resp.form.submit()
agenda.refresh_from_db()
assert agenda.events_type == events_type
# check kind
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/edit' % agenda.pk)
assert 'events_type' not in resp.context['form'].fields
agenda.kind = 'virtual'
agenda.save()
resp = app.get('/manage/agendas/%s/edit' % agenda.pk)
assert 'events_type' not in resp.context['form'].fields
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_events_agenda_lingo_link(settings, app, admin_user):
settings.KNOWN_SERVICES = {}
agenda = Agenda.objects.create(label='Foo bar')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert 'Pricing' not in resp
assert '/manage/pricing/agenda/%s/' % agenda.slug not in resp
settings.KNOWN_SERVICES['lingo'] = {'default': {'url': 'https://lingo.dev/'}}
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert 'https://lingo.dev/manage/pricing/agenda/%s/' % agenda.slug in resp
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_display_options(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
app = login(app)
# check user template
assert agenda.booking_user_block_template == ''
assert (
agenda.get_booking_user_block_template()
== '{{ booking.user_name|default:booking.label|default:"Anonymous" }}'
)
url = '/manage/agendas/%s/display-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" }}'
)
resp = app.get(url)
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(url)
resp.form['event_display_template'] = template
resp = resp.form.submit()
assert 'syntax error' in resp.text
# and for meetings agenda
agenda.kind = 'meetings'
agenda.save()
resp = app.get(url)
assert 'event_display_template' not in resp.form.fields
assert 'booking_user_block_template' in resp.form.fields
# check kind
agenda.kind = 'virtual'
agenda.save()
app.get(url, status=404)
def test_options_agenda_booking_check_options(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
app = login(app)
# check filters
assert agenda.booking_check_filters == ''
assert agenda.get_booking_check_filters() == []
url = '/manage/agendas/%s/check-options' % agenda.pk
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 enable check future events
assert agenda.enable_check_for_future_events is False
resp = app.get(url)
resp.form['enable_check_for_future_events'] = True
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.enable_check_for_future_events is True
# check extra user block
assert agenda.booking_extra_user_block_template == ''
resp = app.get(url)
resp.form['booking_extra_user_block_template'] = '{{ booking.user_name }} Foo Bar'
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.booking_extra_user_block_template == '{{ booking.user_name }} Foo Bar'
invalid_templates = [
'{{ syntax error }}',
'{{ booking.user_name|invalidfilter }}',
]
for template in invalid_templates:
resp = app.get(url)
resp.form['booking_extra_user_block_template'] = template
resp = resp.form.submit()
assert 'syntax error' in resp.text
# 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_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
@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_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)
app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, 42, today.day), status=404)
resp = app.get('/manage/agendas/%s/day/%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/day/%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/day/%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_last_name': "bar's", 'url': 'http://baz/'}
)
app.reset()
login(app)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 2
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's"
assert 'foo - bar&#x27;s' in resp
assert 'hourspan-2' in resp.text # table CSS class
assert 'height: 50%; top: 0%;' in resp.text # booking cells
agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
agenda.save()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Foo Bar"
assert '&lt;b&gt;bar&#x27;s&lt;/b&gt; Foo Bar' in resp
# 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/day/%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/day/%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/day/%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/day/%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/day/%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/day/%(year)d/%(month)d/%(day)d/',
'/manage/agendas/%(agenda)s/week/%(year)d/%(month)d/%(day)d/',
'/manage/agendas/%(agenda)s/month/%(year)d/%(month)d/%(day)d/',
),
)
def test_agenda_day_week_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/day/%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/day/%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/day/%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/day/%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/day/%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/day/%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/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert '>Month<' in resp.text
assert '>Week<' in resp.text
assert '>Day<' 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()],
recurrence_end_date=recurring_start_datetime + datetime.timedelta(days=15),
)
event.create_all_recurrences()
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/day/2020/11/11/' % agenda.pk)
assert len(ctx.captured_queries) == 5
assert len(resp.pyquery.find('.event-title')) == 2
assert resp.pyquery.find('.event-title')[0].text.strip() == 'abc'
assert resp.pyquery.find('.event-title')[1].text.strip() == 'xyz'
resp = app.get('/manage/agendas/%s/day/2020/11/11/' % agenda.pk)
assert len(resp.pyquery.find('.event-title')) == 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()],
recurrence_end_date=start_datetime + datetime.timedelta(days=15),
)
event.create_all_recurrences()
resp = app.get('/manage/agendas/%s/day/2020/11/11/' % agenda.pk)
assert len(resp.pyquery.find('.event-title')) == 3
def test_agenda_events_day_view_midnight(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events', default_view='day')
midnight = make_aware(datetime.datetime(2020, 11, 11, 0, 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('day/2020/11/11/')
resp = resp.follow()
assert len(resp.pyquery.find('.event-title')) == 1
@freezegun.freeze_time('2020-10-01')
def test_agenda_events_week_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/week/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert '28 September 04 October 2020' in resp.text
assert 'Events - Week 40' in resp.text
assert '>Month<' in resp.text
assert '>Week<' in resp.text
assert '>Day<' in resp.text
assert "This week 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/week/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert "This week 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()],
recurrence_end_date=start_datetime + datetime.timedelta(days=60),
)
event.create_all_recurrences()
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, 2020, 11, 11))
assert len(ctx.captured_queries) == 7
assert len(resp.pyquery.find('.event-title')) == 2
assert resp.pyquery.find('.event-title')[0].text.strip() == 'abc'
assert resp.pyquery.find('.event-title')[1].text.strip() == 'xyz'
assert '09 15 November 2020' in resp.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),
)
agenda.update_event_recurrences()
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, 2020, 11, 11))
assert len(resp.pyquery.find('.event-title')) == 1
assert 'Exception: 11/10/2020' in resp.pyquery('li a.disabled')[0].text_content()
assert 'xyz' in resp.pyquery('li.bookable')[0].text_content()
# 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()],
recurrence_end_date=start_datetime + datetime.timedelta(days=60),
)
event.create_all_recurrences()
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, 2020, 12, 7))
assert len(resp.pyquery.find('.event-title')) == 2
time = localtime(event.start_datetime).strftime('%H%M')
resp = resp.click('Dec. 9, 2020, 2 a.m.', index=1)
resp = resp.click('Options')
resp.form['start_datetime_1'] = '13:12'
resp = resp.form.submit(status=302).follow()
agenda.update_event_recurrences()
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, 2020, 12, 7))
assert len(resp.pyquery.find('.event-title')) == 2
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/week/%s/%s/%s/' % (agenda.pk, 2020, 11, 30))
assert 'Cancelled' in resp.text
def test_agenda_events_week_view_midnight(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events', default_view='day')
midnight = make_aware(datetime.datetime(2020, 11, 1, 0, 0))
Event.objects.create(label='xyz', start_datetime=midnight, places=10, agenda=agenda)
login(app)
resp = app.get('/manage/agendas/%s/week/' % agenda.pk)
assert resp.location.endswith('week/2020/11/01/')
resp = resp.follow()
assert len(resp.pyquery.find('.event-title')) == 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/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert '>Month<' in resp.text
assert '>Week<' in resp.text
assert '>Day<' 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/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
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()],
recurrence_end_date=start_datetime + datetime.timedelta(days=60),
)
event.create_all_recurrences()
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, 2020, 11, 1))
assert len(ctx.captured_queries) == 7
assert len(resp.pyquery.find('.event-title')) == 5
assert resp.pyquery.find('.event-title')[0].text.strip() == 'abc'
assert resp.pyquery.find('.event-title')[1].text.strip() == 'abc'
assert resp.pyquery.find('.event-title')[2].text.strip() == 'xyz'
assert resp.pyquery.find('.event-title')[3].text.strip() == 'abc'
assert resp.pyquery.find('.event-title')[4].text.strip() == 'abc'
TimePeriodException.objects.create(
desk=agenda.desk_set.get(),
start_datetime=start_datetime + datetime.timedelta(days=6),
end_datetime=start_datetime + datetime.timedelta(days=8),
)
agenda.update_event_recurrences()
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, 2020, 11, 1))
assert len(resp.pyquery.find('.event-title')) == 4
assert 'abc' in resp.pyquery('li.bookable')[0].text_content()
assert 'Exception: 11/10/2020' in resp.pyquery('li a.disabled')[0].text_content()
assert 'xyz' in resp.pyquery('li.bookable')[1].text_content()
# 12/2020 has 5 Wednesday
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, 2020, 12, 1))
assert len(resp.pyquery.find('.event-title')) == 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()],
recurrence_end_date=start_datetime + datetime.timedelta(days=60),
)
event.create_all_recurrences()
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, 2020, 12, 1))
assert len(resp.pyquery.find('.event-title')) == 10
time = localtime(event.start_datetime).strftime('%H%M')
resp = resp.click('Dec. 9, 2020, 2 a.m.', index=1)
resp = resp.click('Options')
resp.form['start_datetime_1'] = '13:12'
resp = resp.form.submit(status=302).follow()
agenda.update_event_recurrences()
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, 2020, 12, 1))
assert len(resp.pyquery.find('.event-title')) == 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/month/%s/%s/%s/' % (agenda.pk, 2020, 12, 1))
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 = make_aware(datetime.datetime(2020, 11, 1, 0, 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('month/2020/11/01/')
resp = resp.follow()
assert len(resp.pyquery.find('.event-title')) == 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/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Open events' in resp.text
resp = app.get('/manage/agendas/%s/events/open/' % agenda.pk)
assert '>Month<' in resp.text
assert '>Week<' in resp.text
assert '>Day<' 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 = Event.objects.create(
label='event G',
start_datetime=start_datetime,
places=10,
agenda=agenda,
recurrence_end_date=start_datetime + datetime.timedelta(days=30),
)
event.create_all_recurrences()
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)
# 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/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
resp = resp.click('Month')
assert resp.request.url.endswith('month/%s/%02d/%02d/' % (today.year, today.month, today.day))
assert '>Month<' in resp.text
assert '>Week<' in resp.text
assert '>Day<' in resp.text
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()
app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, 42, today.day), status=404)
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
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_last_name': "bar's", 'url': 'http://baz/'}
)
app.reset()
login(app)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, date.year, date.month, date.day))
assert resp.text.count('<div class="booking" style="left:1.0%;height:33.0%;') == 2 # booking cells
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo book - bar's"
assert 'foo book - bar&#x27;s' in resp
assert len(resp.pyquery.find('span.desk')) == 0
agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
agenda.save()
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, date.year, date.month, date.day))
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Foo Bar"
assert '&lt;b&gt;bar&#x27;s&lt;/b&gt; Foo Bar' in resp
desk = Desk.objects.create(agenda=agenda, label='Desk B')
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, date.year, date.month, date.day))
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/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
# 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/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
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/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
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)),
)
TimePeriodException.objects.create(
label='Exception spanning multiple days',
desk=desk,
start_datetime=make_aware(datetime.datetime(2018, 12, 20, 14, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 22, 16, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert len(ctx.captured_queries) == 10
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',
}
assert resp.pyquery.find('.exception-hours')[2].attrib == {
'class': 'exception-hours',
'style': 'height:400.0%;top:400.0%;width:48.0%;left:50.0%;',
'title': 'Exception spanning multiple days',
}
assert resp.pyquery.find('.exception-hours')[3].attrib == {
'class': 'exception-hours',
'style': 'height:800.0%;top:0.0%;width:48.0%;left:50.0%;',
'title': 'Exception spanning multiple days',
}
assert resp.pyquery.find('.exception-hours')[4].attrib == {
'class': 'exception-hours',
'style': 'height:600.0%;top:0.0%;width:48.0%;left:50.0%;',
'title': 'Exception spanning multiple days',
}
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_weekend(app, admin_user, kind):
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/month/%s/%s/%s/' % (agenda.pk, 2019, 1, 1))
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
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, 2019, 12, 1)) # month starts a Sunday
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, 2019, 6, 1)) # month starts a Saturday
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/month/%s/%s/%s/' % (agenda.pk, 2019, 6, 1))
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/month/%s/%s/%s/' % (agenda.pk, 2019, 6, 1))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
timeperiod_sat.delete()
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, 2019, 6, 1))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
def test_agenda_meetings_view_opening_not_even_an_hour(app, admin_user):
month, year = 1, 2019
monday = 0
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
TimePeriod.objects.create(
desk=desk, weekday=monday, start_time=datetime.time(10, 0), end_time=datetime.time(10, 30)
)
login(app)
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.id, year, month, 1))
assert resp.pyquery('.opening-hours').length == 4 # four weeks
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, year, month, 15))
assert resp.pyquery('.opening-hours').length == 1
@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/month/2019/10/01/' % 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/month/2019/10/01/' % 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/month/%s/1/1/' % (agenda.id, date.year))
assert resp.text.count('<th class="weeknum">') in (4, 5)
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_event_outside_timeperiod(app, admin_user, kind):
today = now().date()
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/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
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/month/%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/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
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/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
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/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
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/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
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/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
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/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
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/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
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/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('div class="booking') == 4
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
def test_agenda_week_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/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
resp = resp.click('Week')
assert resp.request.url.endswith('week/%s/%02d/%02d/' % (today.year, today.month, today.day))
assert '>Month<' in resp.text
assert '>Week<' in resp.text
assert '>Day<' in resp.text
assert 'No opening hours this week.' 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()
app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, today.year, 72, today.day), status=404)
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
assert 'No opening hours this week.' not in resp.text
assert '<div class="booking' not in resp.text
assert resp.text.count('<tr') == 9
assert 'Week45' in resp.text
# 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_last_name': "bar's", 'url': 'http://baz/'}
)
app.reset()
login(app)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('<div class="booking" style="left:1.0%;height:33.0%;') == 2 # booking cells
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo book - bar's"
assert 'foo book - bar&#x27;s' in resp
assert len(resp.pyquery.find('span.desk')) == 0
agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
agenda.save()
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, date.year, date.month, date.day))
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Foo Bar"
assert '&lt;b&gt;bar&#x27;s&lt;/b&gt; Foo Bar' in resp
desk = Desk.objects.create(agenda=agenda, label='Desk B')
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, date.year, date.month, date.day))
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/week/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
# count occurences of timeperiod weekday in current month
assert resp.text.count('<div class="opening-hours"') == 2
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/week/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
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/week/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
assert 'No opening hours this week.' 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)),
)
TimePeriodException.objects.create(
label='Exception spanning multiple days',
desk=desk,
start_datetime=make_aware(datetime.datetime(2018, 12, 11, 14, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 13, 16, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
assert len(ctx.captured_queries) == 10
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height:400.0%;top:400.0%;width:48.0%;left:50.0%;',
'title': 'Exception spanning multiple days',
}
assert resp.pyquery.find('.exception-hours')[1].attrib == {
'class': 'exception-hours',
'style': 'height:800.0%;top:0.0%;width:48.0%;left:50.0%;',
'title': 'Exception spanning multiple days',
}
assert resp.pyquery.find('.exception-hours')[2].attrib == {
'class': 'exception-hours',
'style': 'height:600.0%;top:0.0%;width:48.0%;left:50.0%;',
'title': 'Exception spanning multiple days',
}
assert resp.pyquery.find('.exception-hours')[3].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')[4].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_week_view_weekend(app, admin_user, kind):
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/week/%s/%s/%s/' % (agenda.id, 2019, 1, 1))
assert 'Sunday' not in resp.text
assert 'Saturday' not in resp.text
# Month starts a Tuesday, but monday is displayed
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
assert '31 December 2018 04 January 2019' in resp.text
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, 2019, 6, 1)) # month starts a Saturday
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
assert '27 31 May 2019' in resp.text
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/week/%s/%s/%s/' % (agenda.id, 2019, 6, 1))
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
assert '27 May 01 June 2019' in resp.text
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/week/%s/%s/%s/' % (agenda.id, 2019, 6, 1))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
assert '27 May 02 June 2019' in resp.text
timeperiod_sat.delete()
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, 2019, 6, 1))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
assert '27 May 02 June 2019' in resp.text
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_week_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-28', '2019-11-03'):
# dst change was on 2019-11-03
with freezegun.freeze_time(date):
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, 2019, 10, 28))
# check all days are correctly aligned
assert resp.text.count('height:300.0%;top:0.0%') == 7
# book some slots
for date_tuple in [(2019, 10, 29, 10, 0), (2019, 10, 30, 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
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, 2019, 10, 28))
assert resp.text.count('height:33.0%;top:100.0%;') == 2
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_week_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/week/%s/%s/%s/' % (agenda.id, year, 1, 1))
assert resp.text.count('<th class="weeknum">') == 1
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_week_view_event_outside_timeperiod(app, admin_user, kind):
middle_day = now().replace(day=15)
middle_day = middle_day + datetime.timedelta(days=4 - middle_day.weekday())
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/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
assert 'No opening hours this week.' 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/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
assert resp.text.count('div class="booking') == 2
# bookings are cancelled
Booking.objects.update(cancellation_datetime=now())
resp = app.get(
'/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
assert 'No opening hours this week.' 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/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
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
middle_day = now().replace(day=15)
middle_day = middle_day + datetime.timedelta(days=5 - middle_day.weekday())
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now().replace(day=middle_day.day, hour=10, minute=0)),
)
Booking.objects.create(event=event)
resp = app.get(
'/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
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/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
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/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
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)
middle_day = middle_day + datetime.timedelta(days=6 - middle_day.weekday())
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now().replace(day=middle_day.day, hour=10, minute=0)),
)
Booking.objects.create(event=event)
resp = app.get(
'/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
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/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
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/week/%s/%s/%s/' % (agenda.pk, middle_day.year, middle_day.month, middle_day.day)
)
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/month/2019/12/01/' % 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/month/2019/12/01/' % 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 = now().date()
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/day/%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/day/%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/day/%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, label='foo', user_last_name="bar's")
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 4
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's"
assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == 'booked'
assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "foo - bar's"
assert 'foo - bar&#x27;s' in resp
assert 'hourspan-2' in resp.text # table CSS class
assert 'height: 50%; top: 0%;' in resp.text # booking cells
real_agenda_1.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
real_agenda_1.save()
real_agenda_2.booking_user_block_template = '<b>{{ booking.user_name }}</b> Bar Foo'
real_agenda_2.save()
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Bar Foo"
assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "<b>bar's</b> Bar Foo"
assert '&lt;b&gt;bar&#x27;s&lt;/b&gt; Bar Foo' in resp
# 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/day/%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/day/%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/day/%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/day/%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/day/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200
)
assert len(ctx.captured_queries) == 16
# 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'
# display excluded period
date += datetime.timedelta(days=7)
TimePeriod.objects.create(
agenda=agenda, weekday=today.weekday(), start_time=datetime.time(14, 0), end_time=datetime.time(23, 0)
)
resp = app.get(
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200
)
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height: 500%; top: 400%;',
}
# check excluded period is only displayed on relevant weekday
date += datetime.timedelta(days=1)
TimePeriod.objects.create(
desk=desk1, weekday=date.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get(
'/manage/agendas/%s/day/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200
)
assert resp.text.count('<tr') == 9
assert 'exceptions-hours' not in resp.text
def test_virtual_agenda_week_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/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
resp = resp.click('Week')
assert resp.request.url.endswith('week/%s/%02d/%02d/' % (today.year, today.month, today.day))
assert '>Month<' in resp.text
assert '>Week<' in resp.text
assert '>Day<' in resp.text
assert 'No opening hours this week.' 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/week/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
assert 'No opening hours this week.' not in resp.text
assert '<div class="booking' not in resp.text
assert resp.text.count('<tr') == 9
# 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, label='foo', user_last_name="bar's")
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, date.year, date.month, date.day))
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
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's"
assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == 'booked'
assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "foo - bar's"
assert 'foo - bar&#x27;s' in resp
real_agenda_1.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
real_agenda_1.save()
real_agenda_2.booking_user_block_template = '<b>{{ booking.user_name }}</b> Bar Foo'
real_agenda_2.save()
resp = app.get('/manage/agendas/%s/day/%s/%s/%s/' % (agenda.id, date.year, date.month, date.day))
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Bar Foo"
assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "<b>bar's</b> Bar Foo"
assert '&lt;b&gt;bar&#x27;s&lt;/b&gt; Bar Foo' in resp
# cancel a booking
booking = Booking.objects.first()
booking.cancel()
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, date.year, date.month, date.day))
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/week/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
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/week/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
assert len(ctx.captured_queries) == 12
assert len(resp.pyquery.find('.exception-hours')) == 1
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',
}
# display excluded period
TimePeriod.objects.create(
agenda=agenda, weekday=today.weekday(), start_time=datetime.time(13, 0), end_time=datetime.time(23, 0)
)
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
assert len(resp.pyquery.find('.exception-hours')) == 3
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height:500.0%;top:300.0%;width:48.0%;left:1.0%;',
}
assert resp.pyquery.find('.exception-hours')[1].attrib == {
'class': 'exception-hours',
'style': 'height:500.0%;top:300.0%;width:48.0%;left:50.0%;',
}
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/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
resp = resp.click('Month')
assert resp.request.url.endswith('month/%s/%02d/%02d/' % (today.year, today.month, today.day))
assert '>Month<' in resp.text
assert '>Week<' in resp.text
assert '>Day<' in resp.text
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/month/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
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, label='foo', user_last_name="bar's")
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.id, date.year, date.month, date.day))
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
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's"
assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == 'booked'
assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "foo - bar's"
assert 'foo - bar&#x27;s' in resp
real_agenda_1.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
real_agenda_1.save()
real_agenda_2.booking_user_block_template = '<b>{{ booking.user_name }}</b> Bar Foo'
real_agenda_2.save()
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.id, date.year, date.month, date.day))
assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Bar Foo"
assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == '<b></b> Foo Bar'
assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "<b>bar's</b> Bar Foo"
assert '&lt;b&gt;bar&#x27;s&lt;/b&gt; Bar Foo' in resp
# cancel a booking
booking = Booking.objects.first()
booking.cancel()
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.id, date.year, date.month, date.day))
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/month/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
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/month/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
assert len(ctx.captured_queries) == 12
assert len(resp.pyquery.find('.exception-hours')) == 1
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',
}
# display excluded period
TimePeriod.objects.create(
agenda=agenda, weekday=today.weekday(), start_time=datetime.time(13, 0), end_time=datetime.time(23, 0)
)
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.id, today.year, today.month, today.day))
assert len(resp.pyquery.find('.exception-hours')) == 11 # five occurences on two desks
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height:500.0%;top:300.0%;width:48.0%;left:1.0%;',
}
assert resp.pyquery.find('.exception-hours')[1].attrib == {
'class': 'exception-hours',
'style': 'height:500.0%;top:300.0%;width:48.0%;left:50.0%;',
}
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 'Excluded Periods' in resp.text
assert "This virtual agenda doesn't have any excluded period yet" 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
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')
assert 'repeat' not in resp.form.fields
assert 'weekday_indexes' not in resp.form.fields
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)
assert 'repeat' not in resp.form.fields
assert 'weekday_indexes' not in resp.form.fields
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#open:time-periods' % 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&#x27;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/month/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day)
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 'Proceed with cancellation' not 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 'no callback url' not 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/day/%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/month/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day)
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 'SMS' not in resp.text
resp.form['days_before_email'] = 3
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
assert 'reminded of their booking by SMS' not 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['days_before_sms'] = 3
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
with override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO'):
resp = resp.click('Configure', href='reminder')
resp.form['days_before_sms'] = 2
resp = resp.form.submit().follow()
assert 'Users will be reminded of their booking by email, 3 days in advance.' in resp.text
assert 'Users will be reminded of their booking by SMS, 2 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['days_before_email'] = ''
resp = resp.form.submit().follow()
assert 'reminded of their booking by email' not in resp.text
assert 'Users will be reminded of their booking by SMS, 2 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 'Booking reminders' not in resp.text
@override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO', TIME_ZONE='UTC')
@pytest.mark.parametrize('extra_info_field', ('sms_extra_info', 'email_extra_info'))
def test_manager_reminders_templated_extra_info(app, admin_user, extra_info_field):
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='reminder')
extra_info = 'test {{ booking.extra_data.xxx }} {{ booking.event.label|default:booking.extra_data.yyy }}'
resp.form[extra_info_field] = extra_info
resp = resp.form.submit().follow()
assert getattr(agenda.reminder_settings, extra_info_field) == extra_info
invalid_templates = [
'{{ syntax error }}',
'{{ booking.label|invalidfilter }}',
]
for template in invalid_templates:
resp = app.get('/manage/agendas/%s/reminder' % agenda.id)
resp.form[extra_info_field] = template
resp = resp.form.submit()
assert 'syntax error' 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_before_email=1,
email_extra_info='An ID will be required in order to process your form.',
days_before_sms=1,
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 booked 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 booked event "Lorem Ipsum <small>(event label)</small>", on 02/06 at 2:30 p.m.. Take ID card.'
in resp.text
)
# templates in extra info should not be interpreted
agenda.reminder_settings.sms_extra_info = '{{ booking.extra_data.xxx }}'
agenda.reminder_settings.email_extra_info = '{{ booking.extra_data.xxx }}'
agenda.reminder_settings.save()
resp = resp.click('Return to settings')
resp = resp.click('Preview SMS')
assert '{{ booking.extra_data.xxx }}' in resp.text
resp = resp.click('Return to settings')
resp = resp.click('Preview email')
assert '{{ booking.extra_data.xxx }}' in resp.text
def test_manager_reminders_test_sending(app, admin_user, freezer, mailoutbox, settings):
agenda = Agenda.objects.create(label='Events', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
AgendaReminderSettings.objects.create(
agenda=agenda,
days_before_email=1,
email_extra_info='Take your {{ booking.extra_data.document_type }}.',
days_before_sms=1,
sms_extra_info='Take {{ booking.extra_data.document_type }}.',
)
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Test reminder sending')
assert 'phone_number' not in resp.form.fields
assert resp.form['msg_type'].attrs['type'] == 'hidden'
assert resp.form['booking'].options == [('', True, '---------')]
# add bookings
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
freezer.move_to('2020-01-01 14:00')
Booking.objects.create(user_first_name='oldest', user_email='t@test.org', event=event)
freezer.move_to('2020-01-02 14:00')
for _ in range(10):
Booking.objects.create(
event=event,
user_first_name='Jon',
user_last_name='Doe',
user_email='t@test.org',
user_phone_number='+336123456780',
)
freezer.move_to('2020-01-03 14:00')
last_booking = Booking.objects.create(
event=event,
user_first_name='Jane',
user_last_name='Doe',
user_email='t@test.org',
extra_emails=['u@test.org'],
user_phone_number='+33122334455',
extra_phone_numbers=['+33122334456'],
extra_data={'document_type': 'receipt'},
)
resp = app.get('/manage/agendas/%s/reminder/test/' % agenda.id)
assert [x[2] for x in resp.form['booking'].options[:2]] == [
'---------',
'Jane Doe, t@test.org, u@test.org (01/03/2020 3 p.m.)',
]
assert [x[2] for x in resp.form['booking'].options[2:]] == ['Jon Doe, t@test.org (01/02/2020 3 p.m.)'] * 9
resp.form['booking'] = last_booking.pk
resp = resp.form.submit().follow()
assert len(mailoutbox) == 2
assert {x.to[0] for x in mailoutbox} == {'t@test.org', 'u@test.org'}
assert all('Take your receipt' in mail.body for mail in mailoutbox)
mailoutbox.clear()
settings.SMS_URL = 'https://passerelle.test.org/sms/send/'
settings.SMS_SENDER = 'EO'
resp = app.get('/manage/agendas/%s/reminder/test/' % agenda.id)
assert [x[2] for x in resp.form['booking'].options[:2]] == [
'---------',
'Jane Doe, t@test.org, u@test.org, +33122334455, +33122334456 (01/03/2020 3 p.m.)',
]
assert [x[2] for x in resp.form['booking'].options[2:]] == [
'Jon Doe, t@test.org, +336123456780 (01/02/2020 3 p.m.)'
] * 9
resp.form['booking'] = last_booking.pk
resp.form['msg_type'] = ['email', 'sms']
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
mock_send.return_value = mock.Mock(status_code=200)
resp = resp.form.submit().follow()
body = json.loads(mock_send.call_args[0][0].body.decode())
assert 'Take receipt' in body['message']
assert set(body['to']) == {'+33122334455', '+33122334456'}
assert len(mailoutbox) == 2
mailoutbox.clear()
resp = app.get('/manage/agendas/%s/reminder/test/' % agenda.id)
resp.form['booking'] = last_booking.pk
resp.form['msg_type'] = ['sms', 'email']
resp.form['email'] = 'v@test.org'
resp.form['phone_number'] = '+33333333333'
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
mock_send.return_value = mock.Mock(status_code=200)
resp = resp.form.submit().follow()
body = json.loads(mock_send.call_args[0][0].body.decode())
assert body['to'] == ['+33333333333']
assert len(mailoutbox) == 1
assert mailoutbox[0].to == ['v@test.org']
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/day/%(year)d/%(month)d/%(day)d/',
'/manage/agendas/%(agenda)s/month/%(year)d/%(month)d/%(day)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
@freezegun.freeze_time('2022-03-01 14:00')
def test_agenda_day_and_month_views_weekday_indexes(app, admin_user):
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)
today = datetime.date.today()
TimePeriod.objects.create(
desk=desk,
weekday=today.weekday(),
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
weekday_indexes=[1, 3],
)
TimePeriod.objects.create(
desk=desk,
weekday=today.weekday(),
start_time=datetime.time(14, 0),
end_time=datetime.time(17, 0),
weekday_indexes=[3, 5],
)
login(app)
# check day view
resp = app.get('/manage/agendas/%s/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 5 # 10->14
assert 'style="height: 400%; top: 0%;"' in resp.text
resp = app.get('/manage/agendas/%s/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day + 7))
assert 'No opening hours this day.' in resp.text
resp = app.get('/manage/agendas/%s/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day + 14))
assert resp.text.count('<tr') == 8 # 10->14, 14->17
assert 'style="height: 700%; top: 0%;"' in resp.text
resp = app.get('/manage/agendas/%s/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day + 21))
assert 'No opening hours this day.' in resp.text
resp = app.get('/manage/agendas/%s/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day + 28))
assert resp.text.count('<tr') == 4 # 14->17
assert 'style="height: 300%; top: 0%;"' in resp.text
# check month view
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('height:') == 3
assert resp.text.count('height:400.0%') == 1
assert resp.text.count('height:700.0%') == 1
assert resp.text.count('height:300.0%') == 1
@freezegun.freeze_time('2022-11-15 14:00')
def test_agenda_calendar_views_date_time_period(app, admin_user):
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)
today = datetime.date.today()
TimePeriod.objects.create(
desk=desk,
date=today,
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
)
login(app)
# check day view
resp = app.get('/manage/agendas/%s/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 5 # 10->14
assert 'style="height: 400%; top: 0%;"' in resp.text
resp = app.get('/manage/agendas/%s/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day + 7))
assert 'No opening hours this day.' in resp.text
# check week view
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('height:400.0%') == 1
# check month view
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('height:400.0%') == 1
# check month boundaries
TimePeriod.objects.create(
desk=desk,
date=today.replace(day=1),
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
)
TimePeriod.objects.create(
desk=desk,
date=today.replace(day=30),
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
)
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('height:400.0%') == 3
TimePeriod.objects.create(
desk=desk,
date=today.replace(day=31, month=10),
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
)
TimePeriod.objects.create(
desk=desk,
date=today.replace(day=1, month=12),
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
)
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('height:400.0%') == 3
# check week boundaries
TimePeriod.objects.create(
desk=desk,
date=today.replace(day=14),
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
)
TimePeriod.objects.create(
desk=desk,
date=today.replace(day=20),
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
)
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('height:400.0%') == 3
TimePeriod.objects.create(
desk=desk,
date=today.replace(day=13),
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
)
TimePeriod.objects.create(
desk=desk,
date=today.replace(day=21),
start_time=datetime.time(10, 0),
end_time=datetime.time(14, 0),
)
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('height:400.0%') == 3
@freezegun.freeze_time('2022-11-15 14:00')
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_date_time_period_hide_weekend(app, admin_user, kind):
today = datetime.date.today() # Tuesday
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, date=today, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
login(app)
# check month view
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Sunday' not in resp.text
assert 'Saturday' not in resp.text
# check week view
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Sunday' not in resp.text
assert 'Saturday' not in resp.text
assert '14 18 November 2022' in resp.text
TimePeriod.objects.create(
desk=desk, date=today.replace(day=19), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
) # Saturday
# check month view
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
# check week view
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
assert '14 19 November 2022' in resp.text
TimePeriod.objects.create(
desk=desk, date=today.replace(day=20), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
) # Sunday
# check month view
resp = app.get('/manage/agendas/%s/month/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
# check week view
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
assert '14 20 November 2022' in resp.text
@freezegun.freeze_time('2020-10-01')
def test_agenda_today_button(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
today = datetime.date.today()
login(app)
resp = app.get('/manage/agendas/%s/day/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Today' in resp.pyquery('a.active').text()
resp = resp.click('Next day')
assert 'Today' not in resp.pyquery('a.active').text()
resp = resp.click('Today')
assert 'Today' in resp.pyquery('a.active').text()
resp = app.get('/manage/agendas/%s/week/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Today' not in resp.pyquery('a.active').text()