chrono/tests/manager/test_all.py

4540 lines
185 KiB
Python

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import copy
import datetime
import json
import os
import freezegun
import mock
import pytest
import requests
from django.contrib.auth.models import Group, User
from django.core.files.base import ContentFile
from django.core.management import call_command
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.timezone import localtime, make_aware, now
from webtest import Upload
from chrono.agendas.models import (
Agenda,
AgendaReminderSettings,
Booking,
Desk,
Event,
MeetingType,
Resource,
TimePeriod,
TimePeriodException,
TimePeriodExceptionSource,
UnavailabilityCalendar,
VirtualMember,
)
from chrono.manager.forms import TimePeriodExceptionForm
from chrono.utils.signature import check_query
pytestmark = pytest.mark.django_db
@pytest.fixture
def simple_user():
try:
user = User.objects.get(username='user')
except User.DoesNotExist:
user = User.objects.create_user('user', password='user')
return user
@pytest.fixture
def managers_group():
group, _ = Group.objects.get_or_create(name='Managers')
return group
@pytest.fixture
def manager_user(managers_group):
try:
user = User.objects.get(username='manager')
except User.DoesNotExist:
user = User.objects.create_user('manager', password='manager')
user.groups.set([managers_group])
return user
@pytest.fixture
def admin_user():
try:
user = User.objects.get(username='admin')
except User.DoesNotExist:
user = User.objects.create_superuser('admin', email=None, password='admin')
return user
@pytest.fixture
def api_user():
try:
user = User.objects.get(username='api-user')
except User.DoesNotExist:
user = User.objects.create(
username='john.doe', first_name=u'John', last_name=u'Doe', email='john.doe@example.net'
)
user.set_password('password')
user.save()
return user
def login(app, username='admin', password='admin'):
login_page = app.get('/login/')
login_form = login_page.forms[0]
login_form['username'] = username
login_form['password'] = password
resp = login_form.submit()
assert resp.status_int == 302
return app
def test_unlogged_access(app):
# connect while not being logged in
assert app.get('/manage/', status=302).location.endswith('/login/?next=/manage/')
def test_simple_user_access(app, simple_user):
# connect while being logged as a simple user, access should be forbidden
app = login(app, username='user', password='user')
resp = app.get('/manage/', status=403)
assert 'Unfortunately, there is still no agenda' in resp.html.find('div', {'class': 'big-msg-sorry'}).text
def test_manager_user_access(app, manager_user):
# connect while being logged as a manager user, access should be granted if
# there's at least an agenda that is viewable or editable.
app = login(app, username='manager', password='manager')
assert app.get('/manage/', status=403)
agenda = Agenda(label=u'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_events_agenda_redirect(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo Bar', kind='events')
app = login(app)
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
agenda.default_view = 'open_events'
agenda.save()
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/events/open/' % agenda.pk)
agenda.default_view = 'day'
agenda.save()
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
@freezegun.freeze_time('2020-07-12')
def test_events_agenda_month_redirect(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo Bar', kind='events')
app = login(app)
# no event, redirect to current month
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/' % agenda.pk)
# only past events, redirect to last event month
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() - datetime.timedelta(days=60),
)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/5/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() - datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/6/' % agenda.pk)
# future events
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() + datetime.timedelta(days=60),
)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/9/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() + datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/8/' % agenda.pk)
# don't check events for meetings
agenda.kind = 'virtual'
agenda.save()
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/' % agenda.pk)
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/month/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/' % agenda.pk)
@freezegun.freeze_time('2020-07-12')
def test_events_agenda_day_redirect(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo Bar', kind='events')
app = login(app)
# no event, redirect to current day
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/12/' % agenda.pk)
# only past events, redirect to last event day
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() - datetime.timedelta(days=60),
)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/5/13/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() - datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/6/12/' % agenda.pk)
# future events
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() + datetime.timedelta(days=60),
)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/9/10/' % agenda.pk)
Event.objects.create(
agenda=agenda,
places=1,
start_datetime=now() + datetime.timedelta(days=30),
)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/8/11/' % agenda.pk)
# don't check events for meetings
agenda.kind = 'virtual'
agenda.save()
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/12/' % agenda.pk)
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/day/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/2020/7/12/' % agenda.pk)
def test_meetings_agenda_redirect(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo Bar', kind='meetings')
app = login(app)
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
agenda.default_view = 'month'
agenda.save()
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
def test_virtual_agenda_redirect(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo Bar', kind='virtual')
app = login(app)
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
agenda.default_view = 'month'
agenda.save()
resp = app.get('/manage/agendas/%s/' % agenda.pk, status=302)
assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
def test_view_agendas_as_admin(app, admin_user):
Agenda.objects.create(label=u'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=u'Foo Bar')
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
agenda2 = Agenda(label=u'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
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_add_agenda_exceptions_from_settings(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().follow()
agenda = Agenda.objects.get(label='Foo bar')
assert agenda.desk_set.count() == 1
default_desk = agenda.desk_set.first()
assert default_desk.timeperiodexception_set.exists()
assert default_desk.timeperiodexceptionsource_set.count() == 1
source = default_desk.timeperiodexceptionsource_set.first()
assert source.enabled
agenda.desk_simple_management = False
agenda.save()
resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
resp.form['label'] = 'Desk A'
resp = resp.form.submit().follow()
desk = Desk.objects.get(slug='desk-a')
assert desk.timeperiodexception_set.exists()
assert desk.timeperiodexceptionsource_set.count() == 1
source = desk.timeperiodexceptionsource_set.first()
assert source.enabled
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')
role = manager_user.groups.all()[0]
resp.form['edit_role'] = manager_user.groups.all()[0].pk
resp = resp.form.submit().follow()
assert 'Edit Role: Managers' in resp.text
# still only one desk
assert agenda.desk_set.count() == 1
def test_options_agenda(app, admin_user):
agenda_events = Agenda.objects.create(label=u'Foo bar', kind='events')
agenda_meetings = Agenda.objects.create(label=u'Foo bar', kind='meetings')
agenda_virtual = Agenda.objects.create(label=u'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 'default_view' in resp.context['form'].fields
assert 'booking_form_url' not in resp.context['form'].fields
assert 'open_events' not in [k for k, v in resp.context['form'].fields['default_view'].choices]
def test_options_agenda_cant_unset_delays(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar')
assert agenda.minimal_booking_delay == 1
app = login(app)
url = '/manage/agendas/%s/booking-delays' % agenda.pk
resp = app.get(url)
resp.form['minimal_booking_delay'] = None
resp = resp.form.submit()
agenda = Agenda.objects.get(label=u'Foo bar')
assert agenda.minimal_booking_delay == 1
def test_options_virtuale_agenda_can_unset_delays(app, admin_user):
agenda = Agenda.objects.create(label=u'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)
resp.form['maximal_booking_delay'] = None
resp = resp.form.submit()
agenda = Agenda.objects.get(label=u'Foo bar')
assert agenda.maximal_booking_delay is None
def test_options_agenda_as_manager(app, manager_user):
agenda = Agenda(label=u'Foo bar')
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
app = login(app, username='manager', password='manager')
resp = app.get('/manage/', status=200)
resp = resp.click('Foo bar').follow().follow()
assert 'Settings' not in resp.text
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403)
resp = app.get('/manage/agendas/%s/edit' % agenda.id, status=403)
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403)
resp = app.get('/manage/agendas/%s/edit' % agenda.id, status=403)
agenda.kind = 'events'
agenda.save()
agenda.edit_role = manager_user.groups.all()[0]
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Options')
assert resp.form['label'].value == 'Foo bar'
resp.form['label'] = 'Foo baz'
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
resp = resp.follow()
assert 'Foo baz' in resp.text
assert '<h2>Settings' in resp.text
def test_options_meetings_agenda_num_queries(app, admin_user, managers_group):
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', edit_role=managers_group, view_role=managers_group
)
resource = Resource.objects.create(label='Resource')
agenda.resources.add(resource)
for i in range(0, 10):
MeetingType.objects.create(agenda=agenda, label='MT %s' % i)
desk = Desk.objects.create(agenda=agenda, label='Desk')
source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
calendar = UnavailabilityCalendar.objects.create(label='foo')
calendar.desks.add(desk)
for weekday in (0, 6):
TimePeriod.objects.create(
weekday=weekday, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
desk2 = desk.duplicate()
assert agenda.is_available_for_simple_management() is True
app = login(app)
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/agendas/%s/settings' % agenda.pk)
assert len(ctx.captured_queries) == 13
# check with different kind of exceptions:
# exception starts and ends in the past
exception = TimePeriodException.objects.create(
desk=desk,
start_datetime=now() - datetime.timedelta(days=2),
end_datetime=now() - datetime.timedelta(days=1),
)
desk2.delete()
desk2 = desk.duplicate()
assert agenda.is_available_for_simple_management() is True
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/agendas/%s/settings' % agenda.pk)
assert len(ctx.captured_queries) == 13
# exception starts in the past but ends in the futur
exception.delete()
exception = TimePeriodException.objects.create(
desk=desk,
source=source,
start_datetime=now() - datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=1),
)
desk2.delete()
desk2 = desk.duplicate()
assert agenda.is_available_for_simple_management() is True
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/agendas/%s/settings' % agenda.pk)
assert len(ctx.captured_queries) == 13
# exception in more than 2 weeks
exception.delete()
exception = TimePeriodException.objects.create(
unavailability_calendar=calendar,
start_datetime=now() + datetime.timedelta(days=20),
end_datetime=now() + datetime.timedelta(days=21),
)
desk2.delete()
desk2 = desk.duplicate()
assert agenda.is_available_for_simple_management() is True
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/agendas/%s/settings' % agenda.pk)
assert len(ctx.captured_queries) == 13
@mock.patch('chrono.agendas.models.Agenda.is_available_for_simple_management')
def test_agenda_options_desk_simple_management(available_mock, app, admin_user):
agenda = Agenda.objects.create(label=u'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=u'Foo bar')
agenda.save()
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=u'Foo bar')
agenda.save()
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=u'Foo bar')
agenda.edit_role = manager_user.groups.all()[0]
agenda.save()
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=u'Foo bar', kind='meetings')
agenda.save()
desk_a = Desk.objects.create(agenda=agenda, label='Desk A')
desk_b = Desk.objects.create(agenda=agenda, label='Desk B')
event = Event(start_datetime=now() + datetime.timedelta(days=10), places=10, agenda=agenda, desk=desk_a)
event.save()
app = login(app)
resp = app.get('/manage/', status=200)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
desk_page = resp.click('Desk A')
desk_delete_page = desk_page.click('Delete')
assert 'Are you sure you want to delete this?' in desk_delete_page.text
# make sure the deleting is not disabled
assert 'disabled' not in desk_delete_page.text
booking = Booking(event=event)
booking.save()
resp = desk_page.click('Delete')
assert 'This cannot be removed' in resp.text
# the button is disabled
assert 'disabled' in resp.text
app.post('/manage/desks/%s/delete' % desk_a.pk, status=403)
def test_add_meetings_agenda(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp.form['label'] = 'Foo bar'
resp.form['kind'] = 'meetings'
resp = resp.form.submit()
agenda = Agenda.objects.get(label='Foo bar')
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
resp = resp.follow()
assert 'Foo bar' in resp.text
assert '<h2>Settings' in resp.text
assert 'Meeting Types' in resp.text
agenda = Agenda.objects.get(label='Foo bar')
assert agenda.kind == 'meetings'
def test_meetings_agenda_add_meeting_type(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert "This agenda doesn't have any meeting type yet." in resp.text
resp = resp.click('New Meeting Type')
resp.form['label'] = 'Blah'
resp.form['duration'] = '0'
assert 'deleted' not in resp.form.fields
resp = resp.form.submit()
assert 'Ensure this value is greater than or equal to 1.' in resp.text
assert not MeetingType.objects.exists()
resp.form['duration'] = '60'
resp = resp.form.submit()
meeting_type = MeetingType.objects.get(agenda=agenda)
assert meeting_type.label == 'Blah'
assert meeting_type.duration == 60
assert meeting_type.deleted is False
resp = resp.follow()
assert 'Blah' in resp.text
def test_meetings_agenda_edit_meeting_type(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah')
meeting_type2 = MeetingType.objects.create(agenda=agenda, label='Other')
other_agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
other_meeting_type = MeetingType.objects.create(agenda=other_agenda, label='Blah')
assert meeting_type.slug == other_meeting_type.slug
app = login(app)
resp = app.get('/manage/meetingtypes/%s/edit' % meeting_type.pk)
resp.form['label'] = 'Foo'
resp.form['duration'] = '120'
assert 'deleted' not in resp.form.fields
resp.form.submit().follow()
meeting_type.refresh_from_db()
assert meeting_type.label == 'Foo'
assert meeting_type.slug == other_meeting_type.slug
assert meeting_type.duration == 120
assert meeting_type.deleted is False
# check slug edition
resp = app.get('/manage/meetingtypes/%s/edit' % meeting_type.pk)
resp.form['slug'] = meeting_type2.slug
resp = resp.form.submit()
assert resp.context['form'].errors['slug'] == ['Another meeting type exists with the same identifier.']
def test_meetings_agenda_delete_meeting_type(app, admin_user):
agenda = Agenda(label=u'Foo bar', kind='meetings')
agenda.save()
meeting_type = MeetingType(agenda=agenda, label='Blah')
meeting_type.save()
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Blah')
resp = resp.click('Delete')
assert 'Are you sure you want to delete this?' in resp.text
assert 'disabled' not in resp.text
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
meeting_type.refresh_from_db()
assert meeting_type.deleted is True
assert '__deleted__' in meeting_type.slug
# meeting type not showing up anymore
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert 'Meeting Type Foo' not in resp.text
# it is possible to add a new meeting type with the same slug
new_meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah')
assert new_meeting_type.slug == 'blah'
def test_meetings_agenda_add_time_period(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
MeetingType.objects.create(agenda=agenda, label='Blah')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Add a time period', index=0)
resp.form.get('weekdays', index=2).checked = True
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '17:00'
resp = resp.form.submit()
assert TimePeriod.objects.get(desk=desk).weekday == 2
assert TimePeriod.objects.get(desk=desk).start_time.hour == 10
assert TimePeriod.objects.get(desk=desk).start_time.minute == 0
assert TimePeriod.objects.get(desk=desk).end_time.hour == 17
assert TimePeriod.objects.get(desk=desk).end_time.minute == 0
assert desk2.timeperiod_set.exists() is False
resp = resp.follow()
# add a second time period
resp = resp.click('Add a time period', index=0)
resp.form.get('weekdays', index=0).checked = True
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '13:00'
resp = resp.form.submit()
resp = resp.follow()
assert u'Monday / 10 a.m. → 1 p.m.' in resp.text
assert u'Wednesday / 10 a.m. → 5 p.m.' in resp.text
assert resp.text.index('Monday') < resp.text.index('Wednesday')
# invert start and end
resp2 = resp.click('Add a time period', index=0)
resp2.form.get('weekdays', index=0).checked = True
resp2.form['start_time'] = '13:00'
resp2.form['end_time'] = '10:00'
resp2 = resp2.form.submit()
assert 'End time must come after start time.' in resp2.text
# and add same time periods on multiple days
resp = resp.click('Add a time period', index=0)
resp.form.get('weekdays', index=4).checked = True
resp.form.get('weekdays', index=5).checked = True
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '13:00'
resp = resp.form.submit()
assert TimePeriod.objects.filter(desk=desk).count() == 4
def test_meetings_agenda_add_time_period_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
assert agenda.is_available_for_simple_management() is True
app = login(app)
resp = app.get('/manage/agendas/%s/desk/%s/add-time-period' % (agenda.pk, desk.pk))
resp.form.get('weekdays', index=0).checked = True
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '13:00'
resp.form.submit()
assert TimePeriod.objects.filter(desk=desk).count() == 1
assert TimePeriod.objects.filter(desk=desk2).count() == 1
assert agenda.is_available_for_simple_management() is True
def test_meetings_agenda_add_time_period_on_missing_desk(app, admin_user):
app = login(app)
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
app.get('/manage/agendas/%s/desk/0/add-time-period' % agenda.pk, status=404)
def test_meetings_agenda_add_time_period_as_manager(app, manager_user):
agenda = Agenda(label='Foo bar', kind='meetings')
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
desk = Desk.objects.create(agenda=agenda, label='Desk A')
app = login(app, username='manager', password='manager')
resp = app.get('/manage/agendas/%d/' % agenda.id)
assert not 'Settings' in resp.text
resp = app.get('/manage/agendas/%d/settings' % agenda.id, status=403)
MeetingType(agenda=agenda, label='Blah').save()
app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=403)
time_period = TimePeriod(
desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
)
time_period.save()
resp = app.get('/manage/agendas/%d/' % agenda.id)
app.get('/manage/timeperiods/%d/edit' % time_period.id, status=403)
app.get('/manage/timeperiods/%d/delete' % time_period.id, status=403)
# grant edit right to manager
agenda.edit_role = manager_user.groups.all()[0]
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert 'Add a time period' in resp.text
assert '/manage/timeperiods/%s/edit' % time_period.id in resp.text
assert '/manage/timeperiods/%s/delete' % time_period.id in resp.text
app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=200)
app.get('/manage/timeperiods/%d/edit' % time_period.id, status=200)
def test_meetings_agenda_edit_time_period(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
time_period = TimePeriod.objects.create(
desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
)
desk2 = desk.duplicate()
time_period2 = desk2.timeperiod_set.get()
app = login(app)
# edit
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Monday / 9 a.m. → noon', index=0)
assert 'Edit Time Period' in resp.text
resp.form['start_time'] = '10:00'
resp = resp.form.submit()
resp = resp.follow()
time_period.refresh_from_db()
assert time_period.start_time.hour == 10
time_period2.refresh_from_db()
assert time_period2.start_time.hour == 9
# edit with inverted start/end
resp2 = resp.click('Monday / 10 a.m. → noon')
resp2.form['start_time'] = '18:00'
resp2 = resp2.form.submit()
assert 'End time must come after start time.' in resp2.text
def test_meetings_agenda_edit_time_period_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
time_period = TimePeriod.objects.create(
desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
)
desk2 = desk.duplicate()
time_period2 = desk2.timeperiod_set.get()
assert agenda.is_available_for_simple_management() is True
app = login(app)
resp = app.get('/manage/timeperiods/%s/edit' % time_period.pk)
resp.form['weekday'] = 3
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '11:00'
resp.form.submit()
time_period.refresh_from_db()
time_period2.refresh_from_db()
assert time_period.weekday == 3
assert time_period.start_time.hour == 10
assert time_period.end_time.hour == 11
assert time_period2.weekday == 3
assert time_period2.start_time.hour == 10
assert time_period2.end_time.hour == 11
assert agenda.is_available_for_simple_management() is True
# should not happen: corresponding time period does not exist
time_period2.delete()
resp = app.get('/manage/timeperiods/%s/edit' % time_period.pk)
resp.form['weekday'] = 3
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '11:00'
# no error
resp.form.submit()
time_period.refresh_from_db()
assert time_period.weekday == 3
assert time_period.start_time.hour == 10
assert time_period.end_time.hour == 11
def test_meetings_agenda_delete_time_period(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
TimePeriod.objects.create(
desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
desk.duplicate()
assert TimePeriod.objects.count() == 2
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
resp = resp.click('Wednesday', index=0)
resp = resp.click('Delete')
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
assert TimePeriod.objects.count() == 1
def test_meetings_agenda_delete_time_period_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
time_period = TimePeriod.objects.create(
desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
)
desk.duplicate()
assert TimePeriod.objects.count() == 2
assert agenda.is_available_for_simple_management() is True
app = login(app)
resp = app.get('/manage/timeperiods/%s/delete' % time_period.pk)
resp.form.submit()
assert TimePeriod.objects.count() == 0
assert agenda.is_available_for_simple_management() is True
# should not happen: corresponding time period does not exist
time_period = TimePeriod.objects.create(
desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
)
resp = app.get('/manage/timeperiods/%s/delete' % time_period.pk)
resp.form.submit()
assert TimePeriod.objects.count() == 0
def test_meetings_agenda_add_desk(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp.form['label'] = 'Foo bar'
resp.form['kind'] = 'meetings'
resp = resp.form.submit()
assert Desk.objects.count() == 1
assert str(Desk.objects.first()) == 'Desk 1'
agenda = Agenda.objects.get(slug='foo-bar')
agenda.desk_simple_management = False
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('New Desk')
resp.form['label'] = 'Desk A'
resp = resp.form.submit().follow()
assert Desk.objects.count() == 2
desk = Desk.objects.latest('pk')
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
resp = resp.click('New Desk')
resp.form['label'] = 'Desk A'
resp = resp.form.submit().follow()
assert Desk.objects.count() == 3
assert Desk.objects.filter(slug='desk-a-1').count() == 1
assert 'Desk A' in resp.text
new_desk = Desk.objects.latest('pk')
assert new_desk.timeperiod_set.count() == 0
# unknown pk
app.get('/manage/agendas/0/add-desk', status=404)
# wrong kind
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/agendas/%s/add-desk' % agenda.pk, status=404)
agenda.kind = 'events'
agenda.save()
app.get('/manage/agendas/%s/add-desk' % agenda.pk, status=404)
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_meetings_agenda_add_desk_from_another(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
assert Desk.objects.count() == 1
assert desk.timeperiodexceptionsource_set.count() == 0
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('New Desk')
resp.form['label'] = 'Desk B'
resp.form['copy_from'] = desk.pk
resp = resp.form.submit().follow()
assert Desk.objects.count() == 2
new_desk = Desk.objects.latest('pk')
assert new_desk.label == 'Desk B'
assert new_desk.timeperiod_set.count() == 1
assert (
new_desk.timeperiodexceptionsource_set.count() == 0
) # holidays not automatically added via duplication
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_meetings_agenda_add_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
assert Desk.objects.count() == 1
assert desk.timeperiodexceptionsource_set.count() == 0
app = login(app)
resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
resp.form['label'] = 'Desk B'
assert 'copy_from' not in resp.context['form'].fields
resp = resp.form.submit().follow()
assert Desk.objects.count() == 2
new_desk = Desk.objects.latest('pk')
assert new_desk.label == 'Desk B'
assert new_desk.timeperiod_set.count() == 1
assert (
new_desk.timeperiodexceptionsource_set.count() == 0
) # holidays not automatically added via duplication
# ok if no desks (should not happen)
Desk.objects.all().delete()
resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
resp.form['label'] = 'Desk'
assert 'copy_from' not in resp.context['form'].fields
resp = resp.form.submit().follow()
assert Desk.objects.count() == 1
new_desk = Desk.objects.latest('pk')
assert new_desk.timeperiodexceptionsource_set.count() == 1
def test_meetings_agenda_edit_desk(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
other_agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
other_desk = Desk.objects.create(agenda=other_agenda, label='Desk A')
assert other_desk.slug == desk.slug
app = login(app)
resp = app.get('/manage/desks/%s/edit' % desk.pk)
resp.form['label'] = 'Desk C'
resp.form['slug'] = desk.slug
resp = resp.form.submit().follow()
assert 'Desk A' not in resp.text
assert 'Desk B' in resp.text
assert 'Desk C' in resp.text
desk.refresh_from_db()
assert desk.label == 'Desk C'
assert desk.slug == other_desk.slug
# check slug edition
resp = app.get('/manage/desks/%s/edit' % desk.pk)
resp.form['slug'] = desk2.slug
resp = resp.form.submit()
assert resp.context['form'].errors['slug'] == ['Another desk exists with the same identifier.']
# unknown pk
app.get('/manage/desks/0/edit', status=404)
# wrong kind
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/desks/%s/edit' % desk.pk, status=404)
agenda.kind = 'events'
agenda.save()
app.get('/manage/desks/%s/edit' % desk.pk, status=404)
def test_meetings_agenda_delete_desk(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
Desk.objects.create(agenda=agenda, label='Desk A')
desk_b = Desk.objects.create(agenda=agenda, label='Desk B')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Desk A')
resp = resp.click('Delete')
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
assert Desk.objects.count() == 1
# only one desk
app.get('/manage/desks/%s/delete' % desk_b.pk, status=404)
desk_a = Desk.objects.create(agenda=agenda, label='Desk A')
# unknown pk
app.get('/manage/desks/0/delete', status=404)
# wrong kind
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/desks/%s/delete' % desk_a.pk, status=404)
agenda.kind = 'events'
agenda.save()
app.get('/manage/desks/%s/delete' % desk_a.pk, status=404)
def test_meetings_agenda_add_time_period_exception(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Add a time period exception', index=0)
today = datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
tomorrow = make_aware(today + datetime.timedelta(days=1))
dt_format = '%Y-%m-%d %H:%M'
resp.form['label'] = 'Exception 1'
resp.form['start_datetime_0'] = tomorrow.strftime('%Y-%m-%d')
resp.form['start_datetime_1'] = '08:00'
resp.form['end_datetime_0'] = tomorrow.strftime('%Y-%m-%d')
resp.form['end_datetime_1'] = '16:00'
resp = resp.form.submit().follow()
assert TimePeriodException.objects.count() == 1
assert desk2.timeperiodexception_set.exists() is False
time_period_exception = TimePeriodException.objects.first()
assert localtime(time_period_exception.start_datetime).strftime(dt_format) == tomorrow.replace(
hour=8
).strftime(dt_format)
assert localtime(time_period_exception.end_datetime).strftime(dt_format) == tomorrow.replace(
hour=16
).strftime(dt_format)
# add an exception beyond 2 weeks and make sure it isn't listed
resp = resp.click('Add a time period exception', index=1)
future = tomorrow + datetime.timedelta(days=15)
resp.form['label'] = 'Exception 2'
resp.form['start_datetime_0'] = future.strftime('%Y-%m-%d')
resp.form['start_datetime_1'] = '00:00'
resp.form['end_datetime_0'] = future.strftime('%Y-%m-%d')
resp.form['end_datetime_1'] = '16:00'
resp = resp.form.submit().follow()
assert TimePeriodException.objects.count() == 2
assert 'Exception 1' in resp.text
assert 'Exception 2' not in resp.text
resp = resp.click(href="/manage/time-period-exceptions/%d/exception-extract-list" % desk.pk)
assert 'Exception 1' in resp.text
assert 'Exception 2' in resp.text
def test_meetings_agenda_add_time_period_exception_booking_overlaps(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah')
# different type of overlap
# event.start_datetime <= exception.start_datetime < event.start_datetime + meeting_type.duration
event = Event.objects.create(
agenda=agenda,
places=1,
desk=desk,
meeting_type=meeting_type,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30)),
)
Booking.objects.create(event=event)
app = login(app)
resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
resp.form['label'] = 'Exception'
resp.form['start_datetime_0'] = '2017-05-22'
resp.form['start_datetime_1'] = '10:45'
resp.form['end_datetime_0'] = '2017-05-22'
resp.form['end_datetime_1'] = '17:30'
resp = resp.form.submit().follow()
assert TimePeriodException.objects.count() == 1
assert 'Exception added.' in resp.text
assert 'One or several bookings exists within this time slot.' in resp.text
def test_meetings_agenda_add_time_period_exception_all_desks(app, admin_user):
agenda = Agenda.objects.create(label=u'Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
# add global time exception
# only one desk: no option to apply to all desks
app = login(app)
resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
assert 'all_desks' not in resp.context['form'].fields
# more than one desk
Desk.objects.create(agenda=agenda, label='Desk B')
agenda2 = Agenda.objects.create(label='Foo bar', kind='meetings')
Desk.objects.create(agenda=agenda2, label='Other Desk') # check exception is not created for this one
event = Event.objects.create(
agenda=agenda, places=1, desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
)
Booking.objects.create(event=event)
resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
resp.form['label'] = 'Exception'
resp.form['start_datetime_0'] = '2017-05-22'
resp.form['start_datetime_1'] = '08:00'
resp.form['end_datetime_0'] = '2017-05-26'
resp.form['end_datetime_1'] = '17:30'
resp.form['all_desks'] = True
resp = resp.form.submit().follow()
assert TimePeriodException.objects.count() == 2
assert 'Exceptions added.' in resp.text
assert 'One or several bookings exists within this time slot.' in resp.text
exception = TimePeriodException.objects.first()
resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
assert 'all_desks' not in resp.context['form'].fields
def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
MeetingType(agenda=agenda, label='Blah').save()
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
event = Event.objects.create(
agenda=agenda, places=1, desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
)
Booking.objects.create(event=event)
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Add a time period exception')
resp = resp.form.submit() # submit empty form
# fields should be marked with errors
assert resp.text.count('This field is required.') == 2
# try again with data in fields
resp.form['start_datetime_0'] = '2017-05-22'
resp.form['start_datetime_1'] = '08:00'
resp.form['end_datetime_0'] = '2017-05-26'
resp.form['end_datetime_1'] = '17:30'
resp = resp.form.submit().follow()
assert 'Exception added.' in resp.text
assert 'One or several bookings exists within this time slot.' in resp.text
assert TimePeriodException.objects.count() == 1
def test_meetings_agenda_add_time_period_exception_when_cancelled_booking_exists(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
MeetingType(agenda=agenda, label='Blah').save()
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
event = Event.objects.create(
agenda=agenda, places=1, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
)
Booking.objects.create(
event=event, cancellation_datetime=make_aware(datetime.datetime(2017, 5, 20, 10, 30))
)
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Add a time period exception')
resp.form['start_datetime_0'] = '2017-05-22'
resp.form['start_datetime_1'] = '08:00'
resp.form['end_datetime_0'] = '2017-05-26'
resp.form['end_datetime_1'] = '17:30'
resp = resp.form.submit().follow()
assert 'Exception added' in resp.text
assert 'One or several bookings exists within this time slot.' not in resp.text
assert TimePeriodException.objects.count() == 1
def test_meetings_agenda_add_time_period_exception_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
event = Event.objects.create(
agenda=agenda, places=1, desk=desk2, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
)
Booking.objects.create(event=event)
assert agenda.is_available_for_simple_management() is True
login(app)
resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
assert 'all_desks' not in resp.form.fields
resp.form['label'] = 'Exception'
resp.form['start_datetime_0'] = '2017-05-22'
resp.form['start_datetime_1'] = '8:00'
resp.form['end_datetime_0'] = '2017-05-22'
resp.form['end_datetime_1'] = '17:30'
resp = resp.form.submit().follow()
assert 'Exception added' in resp.text
assert 'One or several bookings exists within this time slot.' in resp.text
assert TimePeriodException.objects.count() == 2
assert agenda.is_available_for_simple_management() is True
def test_meetings_agenda_edit_time_period_exception(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
exception = TimePeriodException.objects.create(
label='Exception',
desk=desk,
start_datetime=make_aware(datetime.datetime(2018, 12, 16, 5, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 16, 23, 0)),
)
desk2 = desk.duplicate()
exception2 = desk2.timeperiodexception_set.get()
login(app)
resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
resp.form['start_datetime_1'] = '8:00'
resp.form.submit()
exception.refresh_from_db()
assert exception.start_datetime == make_aware(datetime.datetime(2018, 12, 16, 8, 0))
exception2.refresh_from_db()
assert exception2.start_datetime == make_aware(datetime.datetime(2018, 12, 16, 5, 0))
def test_meetings_agenda_edit_time_period_exception_overlaps(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
calendar = UnavailabilityCalendar.objects.create(label='foo')
calendar.desks.add(desk)
time_period_exception_desk = TimePeriodException.objects.create(
label='Exception',
desk=desk,
start_datetime=make_aware(datetime.datetime(2018, 12, 16, 5, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 16, 23, 0)),
)
time_period_exception_calendar = TimePeriodException.objects.create(
label='Exception',
unavailability_calendar=calendar,
start_datetime=make_aware(datetime.datetime(2018, 12, 16, 5, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 16, 23, 0)),
)
login(app)
for time_period_exception in (time_period_exception_desk, time_period_exception_calendar):
event = Event.objects.create(
agenda=agenda,
places=1,
desk=desk,
start_datetime=make_aware(datetime.datetime(2018, 12, 16, 10, 30)),
)
resp = app.get('/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
resp = resp.form.submit().follow()
assert 'One or several bookings exists within this time slot.' not in resp.text
booking = Booking.objects.create(event=event)
resp = app.get('/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
resp = resp.form.submit().follow()
assert 'One or several bookings exists within this time slot.' in resp.text
booking.cancel()
resp = app.get('/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
resp = resp.form.submit().follow()
assert 'One or several bookings exists within this time slot.' not in resp.text
booking.delete()
event.delete()
def test_meetings_agenda_edit_time_period_exception_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
exception = TimePeriodException.objects.create(
label='Exception',
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 21, 5, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 21, 23, 0)),
)
desk2 = desk.duplicate()
event = Event.objects.create(
agenda=agenda, places=1, desk=desk2, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
)
Booking.objects.create(event=event)
exception2 = desk2.timeperiodexception_set.get()
assert agenda.is_available_for_simple_management() is True
login(app)
resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
resp.form['label'] = 'Exception foo bar'
resp.form['start_datetime_0'] = '2017-05-22'
resp.form['start_datetime_1'] = '8:00'
resp.form['end_datetime_0'] = '2017-05-22'
resp.form['end_datetime_1'] = '17:30'
resp = resp.form.submit().follow()
assert 'One or several bookings exists within this time slot.' in resp.text
exception.refresh_from_db()
exception2.refresh_from_db()
assert exception.label == 'Exception foo bar'
assert exception.start_datetime == make_aware(datetime.datetime(2017, 5, 22, 8, 0))
assert exception.end_datetime == make_aware(datetime.datetime(2017, 5, 22, 17, 30))
assert exception2.label == 'Exception foo bar'
assert exception2.start_datetime == make_aware(datetime.datetime(2017, 5, 22, 8, 0))
assert exception2.end_datetime == make_aware(datetime.datetime(2017, 5, 22, 17, 30))
assert agenda.is_available_for_simple_management() is True
# should not happen: corresponding exception does not exist
exception2.delete()
resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
resp.form['label'] = 'Exception'
resp.form['start_datetime_0'] = '2017-05-21'
resp.form['start_datetime_1'] = '9:00'
resp.form['end_datetime_0'] = '2017-05-21'
resp.form['end_datetime_1'] = '18:30'
# no error
resp.form.submit()
exception.refresh_from_db()
assert exception.label == 'Exception'
assert exception.start_datetime == make_aware(datetime.datetime(2017, 5, 21, 9, 0))
assert exception.end_datetime == make_aware(datetime.datetime(2017, 5, 21, 18, 30))
def test_meetings_agenda_add_invalid_time_period_exception():
form = TimePeriodExceptionForm(
data={
'start_datetime_0': '2017-05-26',
'start_datetime_1': '17:30',
'end_datetime_0': '2017-05-22',
'end_datetime_1': '08:00',
}
)
assert form.is_valid() is False
assert form.errors['end_datetime'] == ['End datetime must be greater than start datetime.']
# start_datetime is invalid
form = TimePeriodExceptionForm(
data={
'start_datetime_0': '2017-05-26',
'start_datetime_1': 'foo',
'end_datetime_0': '2017-05-22',
'end_datetime_1': '08:00',
}
)
assert form.is_valid() is False
# end_datetime is invalid
form = TimePeriodExceptionForm(
data={
'start_datetime_0': '2017-05-26',
'start_datetime_1': '17:30',
'end_datetime_0': 'bar',
'end_datetime_1': '08:00',
}
)
assert form.is_valid() is False
def test_meetings_agenda_delete_time_period_exception(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
TimePeriodException.objects.create(
label='Exception Foo',
desk=desk,
start_datetime=now() + datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=2),
)
desk.duplicate()
assert TimePeriodException.objects.count() == 2
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Exception Foo', index=0)
resp = resp.click('Delete')
resp = resp.form.submit().follow()
assert TimePeriodException.objects.count() == 1
assert resp.request.url.endswith('/manage/agendas/%d/settings' % agenda.pk)
# stay on exception list
time_period_exception = TimePeriodException.objects.create(
label='Future Exception',
desk=desk,
start_datetime=now() + datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=2),
)
resp = app.get('/manage/time-period-exceptions/%d/exception-list' % desk.pk)
resp = resp.click(href='/manage/time-period-exceptions/%d/delete' % time_period_exception.pk)
resp = resp.form.submit(
extra_environ={'HTTP_REFERER': str('/manage/time-period-exceptions/%d/exception-list' % desk.pk)}
).follow()
assert resp.request.url.endswith('/manage/time-period-exceptions/%d/exception-list' % desk.pk)
def test_meetings_agenda_delete_time_period_exception_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
exception = TimePeriodException.objects.create(
label='Exception',
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 21, 5, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 21, 23, 0)),
)
desk.duplicate()
assert TimePeriodException.objects.count() == 2
assert agenda.is_available_for_simple_management() is True
login(app)
resp = app.get('/manage/time-period-exceptions/%d/delete' % exception.pk)
resp.form.submit()
assert TimePeriodException.objects.count() == 0
assert agenda.is_available_for_simple_management() is True
# should not happen: corresponding exception does not exist
exception = TimePeriodException.objects.create(
label='Exception',
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 21, 5, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 21, 23, 0)),
)
assert TimePeriodException.objects.count() == 1
resp = app.get('/manage/time-period-exceptions/%d/delete' % exception.pk)
resp.form.submit()
assert TimePeriodException.objects.count() == 0
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_exception_list(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
MeetingType(agenda=agenda, label='Blah').save()
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
past_exception = TimePeriodException.objects.create(
label='Past Exception',
desk=desk,
start_datetime=now() - datetime.timedelta(days=2),
end_datetime=now() - datetime.timedelta(days=1),
)
current_exception = TimePeriodException.objects.create(
label='Current Exception',
desk=desk,
start_datetime=now() - datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=1),
)
future_exception = TimePeriodException.objects.create(
label='Future Exception',
desk=desk,
start_datetime=now() + datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=2),
)
login(app)
resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk in resp.text
assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk in resp.text
resp = resp.click(href="/manage/time-period-exceptions/%d/exception-extract-list" % desk.pk)
assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk in resp.text
assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk in resp.text
resp = resp.click(href="/manage/time-period-exceptions/%d/exception-list" % desk.pk)
assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk in resp.text
assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk in resp.text
with CaptureQueriesContext(connection) as ctx:
app.get("/manage/time-period-exceptions/%d/exception-list" % desk.pk)
assert len(ctx.captured_queries) == 6
desk.import_timeperiod_exceptions_from_settings(enable=True)
with CaptureQueriesContext(connection) as ctx:
app.get("/manage/time-period-exceptions/%d/exception-list" % desk.pk)
assert len(ctx.captured_queries) == 6
# add an unavailability calendar
unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar')
past_exception = TimePeriodException.objects.create(
label='Calendar Past Exception',
unavailability_calendar=unavailability_calendar,
start_datetime=now() - datetime.timedelta(days=2),
end_datetime=now() - datetime.timedelta(days=1),
)
current_exception = TimePeriodException.objects.create(
label='Calendar Current Exception',
unavailability_calendar=unavailability_calendar,
start_datetime=now() - datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=1),
)
future_exception = TimePeriodException.objects.create(
label='Calendar Future Exception',
unavailability_calendar=unavailability_calendar,
start_datetime=now() + datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=2),
)
unavailability_calendar.desks.add(desk)
for url in (
"/manage/time-period-exceptions/%d/exception-extract-list" % desk.pk,
"/manage/time-period-exceptions/%d/exception-list" % desk.pk,
):
resp = app.get(url)
assert 'Calendar Past Exception' not in resp.text
assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
assert 'Calendar Current Exception' in resp.text
assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk not in resp.text
assert 'Calendar Future Exception' in resp.text
assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk not in resp.text
def test_agenda_import_time_period_exception_from_ics(app, admin_user):
agenda = Agenda.objects.create(label='Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Test Desk')
desk.duplicate()
login(app)
resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
assert 'Manage exception sources' in resp.text
resp = resp.click('manage exceptions', index=0)
assert "To add new exceptions, you can upload a file or specify an address to a remote calendar." in resp
resp = resp.form.submit(status=200)
assert 'Please provide an ICS File or an URL.' in resp.text
assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
resp.form['ics_file'] = Upload('exceptions.ics', b'invalid content', 'text/calendar')
resp = resp.form.submit(status=200)
assert 'File format is invalid' in resp.text
assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
ics_with_no_start_date = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_start_date, 'text/calendar')
resp = resp.form.submit(status=200)
assert 'Event &quot;New Year&#39;s Eve&quot; has no start date.' in resp.text
assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
ics_with_no_events = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
END:VCALENDAR"""
resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_events, 'text/calendar')
resp = resp.form.submit(status=200)
assert "The file doesn&#39;t contain any events." in resp.text
assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
ics_with_exceptions = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
resp.form['ics_file'] = Upload('exceptions.ics', ics_with_exceptions, 'text/calendar')
resp = resp.form.submit(status=302)
assert TimePeriodException.objects.filter(desk=desk).count() == 1
assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1
source = desk.timeperiodexceptionsource_set.get()
exception = desk.timeperiodexception_set.get()
assert exception.source == source
assert source.ics_filename == 'exceptions.ics'
assert 'exceptions.ics' in source.ics_file.name
assert source.ics_url is None
resp = resp.follow()
assert 'Exceptions will be imported in a few minutes.' in resp.text
@pytest.mark.freeze_time('2017-12-01')
def test_agenda_import_time_period_exception_from_ics_recurrent(app, admin_user):
agenda = Agenda.objects.create(label='Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Test Desk')
MeetingType(agenda=agenda, label='Foo').save()
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('manage exceptions')
ics_with_recurrent_exceptions = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR"""
resp.form['ics_file'] = Upload('exceptions.ics', ics_with_recurrent_exceptions, 'text/calendar')
resp = resp.form.submit(status=302).follow()
assert TimePeriodException.objects.filter(desk=desk).count() == 2
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_with_remote_ics(mocked_get, app, admin_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
desk.duplicate()
login(app)
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
assert 'ics_file' in resp.form.fields
assert 'ics_url' in resp.form.fields
resp.form['ics_url'] = 'http://example.com/foo.ics'
mocked_response = mock.Mock()
mocked_response.text = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
mocked_get.return_value = mocked_response
resp = resp.form.submit(status=302)
assert TimePeriodException.objects.filter(desk=desk).count() == 1
assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1
source = desk.timeperiodexceptionsource_set.get()
exception = desk.timeperiodexception_set.get()
assert exception.source == source
assert source.ics_filename is None
assert source.ics_file.name == ''
assert source.ics_url == 'http://example.com/foo.ics'
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_with_remote_ics_no_events(mocked_get, 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')
login(app)
resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
resp = resp.click('manage exceptions')
resp.form['ics_url'] = 'http://example.com/foo.ics'
mocked_response = mock.Mock()
mocked_response.text = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
mocked_get.return_value = mocked_response
resp = resp.form.submit(status=302)
assert TimePeriodException.objects.filter(desk=desk).count() == 1
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_from_remote_ics_with_connection_error(
mocked_get, app, admin_user
):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
Desk.objects.create(agenda=agenda, label='New Desk')
MeetingType.objects.create(agenda=agenda, label='Bar')
login(app)
resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
resp = resp.click('manage exceptions')
assert 'ics_file' in resp.form.fields
assert 'ics_url' in resp.form.fields
resp.form['ics_url'] = 'http://example.com/foo.ics'
mocked_response = mock.Mock()
mocked_get.return_value = mocked_response
def mocked_requests_connection_error(*args, **kwargs):
raise requests.exceptions.ConnectionError('unreachable')
mocked_get.side_effect = mocked_requests_connection_error
resp = resp.form.submit(status=200)
assert 'Failed to retrieve remote calendar (http://example.com/foo.ics, unreachable).' in resp.text
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_get, app, admin_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
Desk.objects.create(agenda=agenda, label='New Desk')
MeetingType.objects.create(agenda=agenda, label='Bar')
login(app)
resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
resp = resp.click('manage exceptions')
resp.form['ics_url'] = 'http://example.com/foo.ics'
mocked_response = mock.Mock()
mocked_response.status_code = 403
mocked_get.return_value = mocked_response
def mocked_requests_http_forbidden_error(*args, **kwargs):
raise requests.exceptions.HTTPError(response=mocked_response)
mocked_get.side_effect = mocked_requests_http_forbidden_error
resp = resp.form.submit(status=200)
assert 'Failed to retrieve remote calendar (http://example.com/foo.ics, HTTP error 403).' in resp.text
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mocked_get, app, admin_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
Desk.objects.create(agenda=agenda, label='New Desk')
MeetingType.objects.create(agenda=agenda, label='Bar')
login(app)
resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
resp = resp.click('manage exceptions')
resp.form['ics_url'] = 'https://example.com/foo.ics'
mocked_response = mock.Mock()
mocked_get.return_value = mocked_response
def mocked_requests_http_ssl_error(*args, **kwargs):
raise requests.exceptions.SSLError('SSL error')
mocked_get.side_effect = mocked_requests_http_ssl_error
resp = resp.form.submit(status=200)
assert 'Failed to retrieve remote calendar (https://example.com/foo.ics, SSL error).' in resp.text
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_url_desk_simple_management(mocked_get, app, admin_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='New Desk')
desk.duplicate()
assert agenda.is_available_for_simple_management() is True
login(app)
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
resp.form['ics_url'] = 'http://example.com/foo.ics'
mocked_response = mock.Mock()
mocked_response.text = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
mocked_get.return_value = mocked_response
resp = resp.form.submit(status=302)
assert TimePeriodException.objects.count() == 2
assert agenda.is_available_for_simple_management() is True
def test_agenda_import_time_period_exception_file_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='New Desk')
desk.duplicate()
assert agenda.is_available_for_simple_management() is True
login(app)
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
ics_exceptions = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
resp.form['ics_file'] = Upload('exceptions.ics', ics_exceptions, 'text/calendar')
resp = resp.form.submit(status=302)
assert TimePeriodException.objects.count() == 2
assert agenda.is_available_for_simple_management() is True
def test_meetings_agenda_delete_time_period_exception_source(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
source1 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
TimePeriodException.objects.create(
desk=desk,
source=source1,
start_datetime=now() - datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=1),
)
source2 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
TimePeriodException.objects.create(
desk=desk,
source=source2,
start_datetime=now() - datetime.timedelta(days=1),
end_datetime=now() + datetime.timedelta(days=1),
)
desk.duplicate()
assert TimePeriodException.objects.count() == 4
assert TimePeriodExceptionSource.objects.count() == 4
login(app)
resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source2.pk)
resp = resp.form.submit()
assert TimePeriodException.objects.count() == 3
assert TimePeriodExceptionSource.objects.count() == 3
assert source1.timeperiodexception_set.count() == 1
assert TimePeriodExceptionSource.objects.filter(pk=source2.pk).exists() is False
def test_meetings_agenda_delete_time_period_exception_source_desk_simple_management(app, admin_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='New Desk')
source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
desk.duplicate()
assert TimePeriodExceptionSource.objects.count() == 2
assert agenda.is_available_for_simple_management() is True
login(app)
resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source.pk)
resp.form.submit()
assert TimePeriodExceptionSource.objects.count() == 0
assert agenda.is_available_for_simple_management() is True
# should not happen: corresponding source does not exist
source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
assert TimePeriodExceptionSource.objects.count() == 1
resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source.pk)
resp.form.submit()
assert TimePeriodExceptionSource.objects.count() == 0
def test_meetings_agenda_replace_time_period_exception_source(app, admin_user, freezer):
freezer.move_to('2019-12-01')
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
ics_file_content = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR"""
login(app)
# import a source from a file
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
resp.form['ics_file'] = Upload('exceptions.ics', ics_file_content, 'text/calendar')
resp = resp.form.submit(status=302).follow()
assert TimePeriodException.objects.filter(desk=desk).count() == 2
source = TimePeriodExceptionSource.objects.latest('pk')
assert source.timeperiodexception_set.count() == 2
exceptions = list(source.timeperiodexception_set.order_by('pk'))
old_ics_file_path = source.ics_file.path
desk2 = desk.duplicate()
source2 = desk2.timeperiodexceptionsource_set.get()
exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
assert TimePeriodException.objects.count() == 4
old_ics_file_path2 = source2.ics_file.path
# replace the source
resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
resp.form['ics_newfile'] = Upload('exceptions-bis.ics', ics_file_content, 'text/calendar')
resp = resp.form.submit().follow()
source.refresh_from_db()
assert source.ics_file.path != old_ics_file_path
assert source.ics_filename == 'exceptions-bis.ics'
assert os.path.exists(old_ics_file_path) is False
assert TimePeriodException.objects.count() == 4
assert source.timeperiodexception_set.count() == 2
new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
assert exceptions[0].pk != new_exceptions[0].pk
assert exceptions[1].pk != new_exceptions[1].pk
source2.refresh_from_db()
assert source2.ics_file.path == old_ics_file_path2
assert source2.ics_filename == 'exceptions.ics'
new_exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
assert exceptions2[0].pk == new_exceptions2[0].pk
assert exceptions2[1].pk == new_exceptions2[1].pk
def test_meetings_agenda_replace_time_period_exception_source_desk_simple_management(app, admin_user):
ics_file_content = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
source = TimePeriodExceptionSource.objects.create(
desk=desk,
ics_filename='sample.ics',
ics_file=ContentFile(ics_file_content, name='sample.ics'),
)
desk2 = desk.duplicate()
source2 = desk2.timeperiodexceptionsource_set.get()
assert TimePeriodExceptionSource.objects.count() == 2
assert TimePeriodException.objects.count() == 0 # not imported yet
assert agenda.is_available_for_simple_management() is True
old_ics_file_path = source.ics_file.path
old_ics_file_path2 = source2.ics_file.path
login(app)
resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
resp.form['ics_newfile'] = Upload('exceptions-bis.ics', ics_file_content, 'text/calendar')
resp.form.submit()
assert TimePeriodExceptionSource.objects.count() == 2
assert TimePeriodException.objects.count() == 2
assert desk.timeperiodexception_set.count() == 1
assert desk2.timeperiodexception_set.count() == 1
source.refresh_from_db()
assert source.ics_file.path != old_ics_file_path
assert source.ics_filename == 'exceptions-bis.ics'
assert os.path.exists(old_ics_file_path) is False
source2.refresh_from_db()
assert source2.ics_file.path != old_ics_file_path2
assert source2.ics_filename == 'exceptions-bis.ics'
assert os.path.exists(old_ics_file_path2) is False
assert agenda.is_available_for_simple_management() is True
# should not happen: corresponding source does not exist
source2.delete()
resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
resp.form['ics_newfile'] = Upload('exceptions-ter.ics', ics_file_content, 'text/calendar')
resp.form.submit()
assert TimePeriodExceptionSource.objects.count() == 1
assert TimePeriodException.objects.count() == 1
assert desk.timeperiodexception_set.count() == 1
assert desk2.timeperiodexception_set.count() == 0
assert desk.timeperiodexceptionsource_set.get().ics_filename == 'exceptions-ter.ics'
@mock.patch('chrono.agendas.models.requests.get')
def test_meetings_agenda_refresh_time_period_exception_source(mocked_get, app, admin_user):
mocked_response = mock.Mock()
mocked_response.text = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
mocked_get.return_value = mocked_response
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
login(app)
# import a source from an url
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('manage exceptions')
resp.form['ics_url'] = 'http://example.com/foo.ics'
resp = resp.form.submit(status=302).follow()
assert TimePeriodException.objects.filter(desk=desk).count() == 1
source = TimePeriodExceptionSource.objects.latest('pk')
assert source.timeperiodexception_set.count() == 1
exceptions = list(source.timeperiodexception_set.order_by('pk'))
desk2 = desk.duplicate()
source2 = desk2.timeperiodexceptionsource_set.get()
exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
assert TimePeriodException.objects.count() == 2
# refresh the source
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('manage exceptions', index=0)
resp = resp.click(href='/manage/time-period-exceptions-source/%d/refresh' % source.pk)
assert TimePeriodException.objects.count() == 2
new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
assert exceptions[0].pk != new_exceptions[0].pk
new_exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
assert exceptions2[0].pk == new_exceptions2[0].pk
@mock.patch('chrono.agendas.models.requests.get')
def test_meetings_agenda_refresh_time_period_exception_source_desk_simple_management(
mocked_get, app, admin_user
):
mocked_response = mock.Mock()
mocked_response.text = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
mocked_get.return_value = mocked_response
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
desk2 = desk.duplicate()
source2 = desk2.timeperiodexceptionsource_set.get()
assert TimePeriodExceptionSource.objects.count() == 2
assert TimePeriodException.objects.count() == 0 # not imported yet
assert agenda.is_available_for_simple_management() is True
login(app)
app.get('/manage/time-period-exceptions-source/%d/refresh' % source.pk)
assert TimePeriodExceptionSource.objects.count() == 2
assert TimePeriodException.objects.count() == 2
assert source.timeperiodexception_set.count() == 1
assert source2.timeperiodexception_set.count() == 1
assert agenda.is_available_for_simple_management() is True
# should not happen: corresponding source does not exist
source2.delete()
app.get('/manage/time-period-exceptions-source/%d/refresh' % source.pk)
assert TimePeriodExceptionSource.objects.count() == 1
assert TimePeriodException.objects.count() == 1
assert source.timeperiodexception_set.count() == 1
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_meetings_agenda_time_period_exception_source_from_settings_toggle(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
desk.import_timeperiod_exceptions_from_settings(enable=True)
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
desk2.import_timeperiod_exceptions_from_settings(enable=True)
assert desk.timeperiodexception_set.exists()
assert desk2.timeperiodexception_set.exists()
login(app)
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
assert 'Holidays' in resp.text
assert 'disabled' not in resp.text
assert 'refresh' not in resp.text
resp = resp.click('disable').follow()
assert not desk.timeperiodexception_set.exists()
assert desk2.timeperiodexception_set.exists()
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
assert 'Holidays' in resp.text
assert 'disabled' in resp.text
resp = resp.click('enable').follow()
assert desk.timeperiodexception_set.exists()
assert desk2.timeperiodexception_set.exists()
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
assert 'disabled' not in resp.text
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_meetings_agenda_time_period_exception_source_from_settings_toggle_desk_simple_management(
app, admin_user
):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
desk = Desk.objects.create(agenda=agenda, label='Desk A')
desk.import_timeperiod_exceptions_from_settings(enable=True)
source = desk.timeperiodexceptionsource_set.get()
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
desk2.import_timeperiod_exceptions_from_settings(enable=True)
source2 = desk2.timeperiodexceptionsource_set.get()
assert desk.timeperiodexception_set.exists()
assert desk2.timeperiodexception_set.exists()
assert agenda.is_available_for_simple_management() is True
login(app)
app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
source.refresh_from_db()
source2.refresh_from_db()
assert not source.enabled
assert not source2.enabled
assert not desk.timeperiodexception_set.exists()
assert not desk2.timeperiodexception_set.exists()
assert agenda.is_available_for_simple_management() is True
app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
source.refresh_from_db()
source2.refresh_from_db()
assert source.enabled
assert source2.enabled
assert desk.timeperiodexception_set.exists()
assert desk2.timeperiodexception_set.exists()
assert agenda.is_available_for_simple_management() is True
# should not happen: corresponding source does not exist
source2.delete()
app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
source.refresh_from_db()
assert not source.enabled
def test_meetings_agenda_time_period_exception_source_try_disable_ics(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
MeetingType(agenda=agenda, label='Blah').save()
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
login(app)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow().follow()
resp = resp.click('Settings')
resp = resp.click('manage exceptions')
assert 'test.ics' in resp.text
assert app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk, status=404)
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_meetings_agenda_time_period_exception_source_from_settings(app, admin_user, freezer):
freezer.move_to('2020-01-01')
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
desk.import_timeperiod_exceptions_from_settings(enable=True)
new_year = desk.timeperiodexception_set.filter(label='New year').first()
remove_url = reverse('chrono-manager-time-period-exception-delete', kwargs={'pk': new_year.pk})
edit_url = reverse('chrono-manager-time-period-exception-edit', kwargs={'pk': new_year.pk})
login(app)
resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
assert 'New year' in resp.text
assert remove_url not in resp.text and edit_url not in resp.text
resp = resp.click('see all')
assert 'New year' in resp.text
assert remove_url not in resp.text and edit_url not in resp.text
app.get(remove_url, status=404)
def test_agenda_day_view(app, admin_user, manager_user, api_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
desk.save()
today = datetime.date.today()
meetingtype = MeetingType(agenda=agenda, label='Bar', duration=30)
meetingtype.save()
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' in resp.text # no time pediod
timeperiod = TimePeriod(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
timeperiod.save()
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' not in resp.text
assert 'div class="booking' not in resp.text
assert resp.text.count('<tr') == 9 # 10->18 (not included)
timeperiod.end_time = datetime.time(18, 30) # end during an hour
timeperiod.save()
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 10 # 10->18 (included)
# check opening hours cells
assert '<div class="opening-hours"' in resp.text
assert 'style="height: 850%; top: 0%;"' in resp.text
# book some slots
app.reset()
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
booking_url = resp.json['data'][0]['api']['fillslot_url']
booking_url2 = resp.json['data'][2]['api']['fillslot_url']
resp = app.post(booking_url)
resp = app.post_json(booking_url2, params={'label': 'foo', 'user': 'bar', 'url': 'http://baz/'})
app.reset()
login(app)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 2
assert 'hourspan-2' in resp.text # table CSS class
assert 'height: 50%; top: 0%;' in resp.text # booking cells
# create a shorter meeting type, this will change the table CSS class
# (and visually this will give more room for events)
meetingtype = MeetingType(agenda=agenda, label='Baz', duration=15)
meetingtype.save()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 2
assert 'hourspan-4' in resp.text # table CSS class
# cancel a booking
app.reset()
app.authorization = ('Basic', ('john.doe', 'password'))
booking = Booking.objects.all()[0]
resp = app.post('/api/booking/%s/cancel/' % booking.id)
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 1
app.reset()
login(app)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 1
# not enough permissions
app.reset()
app = login(app, username='manager', password='manager')
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=403)
# just enough permissions
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200)
# display exception
unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar')
TimePeriodException.objects.create(
label='Calendar exception',
unavailability_calendar=unavailability_calendar,
start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 13, 0)),
end_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 15, 0)),
)
unavailability_calendar.desks.add(desk)
TimePeriodException.objects.create(
label='Exception for the afternoon',
desk=desk,
start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 16, 0)),
end_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 23, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get(
'/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200
)
assert len(ctx.captured_queries) == 14
# day is displaying rows from 10am to 6pm,
# opening hours, 10am to 1pm gives top: 300%
# calendar exception, 1pm to 3pm gives heigh: 200%
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height: 200%; top: 300%;',
}
assert resp.pyquery.find('.exception-hours span')[0].text == 'Calendar exception'
# rest of the day, opened from 3pm to 4pm, since we left off at 500% it gives top: 600%
# then exception from 4pm to 6pm included, gives height: 300%
assert resp.pyquery.find('.exception-hours')[1].attrib == {
'class': 'exception-hours',
'style': 'height: 300%; top: 600%;',
}
assert resp.pyquery.find('.exception-hours span')[1].text == 'Exception for the afternoon'
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_day_view_late_meeting(app, admin_user, kind):
today = datetime.date.today()
if kind == 'meetings':
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
MeetingType.objects.create(agenda=real_agenda, label='Bar', duration=30)
TimePeriod.objects.create(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(23, 30)
)
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 15
assert '<th class="hour">11 p.m.</th>' in resp.text
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_invalid_day_view(app, admin_user, kind):
if kind == 'meetings':
agenda = Agenda.objects.create(label='New Example', kind='meetings')
Desk.objects.create(agenda=agenda, label='New Desk')
MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
Desk.objects.create(agenda=real_agenda, label='New Desk')
MeetingType.objects.create(agenda=real_agenda, label='Bar', duration=30)
login(app)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, 2018, 11, 31), status=302)
assert resp.location.endswith('2018/11/30/')
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_day_view_event_outside_timeperiod(app, admin_user, kind):
if kind == 'meetings':
today = datetime.date.today()
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
else:
today = datetime.date.today()
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=real_agenda, label='Bar', duration=30)
login(app)
# no time period - no events
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' in resp.text
assert 'div class="booking' not in resp.text
# book some slots
for hour, minute in [(9, 0), (17, 0)]:
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now()).replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
# no time period - events are displayed
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('div class="booking') == 2
# bookings are cancelled
Booking.objects.update(cancellation_datetime=now())
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' in resp.text
assert resp.text.count('div class="booking') == 0
# events outside time period
Booking.objects.update(cancellation_datetime=None) # reset
TimePeriod.objects.create(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(16, 0)
)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('div class="booking') == 2
assert '<div class="opening-hours"' in resp.text
assert 'style="height: 600%; top: 100%;"' in resp.text
@freezegun.freeze_time('2020-10-01')
def test_agenda_events_day_view(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
today = datetime.date.today()
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'Month view' in resp.text
assert "This day doesn't have any event configured." in resp.text
# event
Event.objects.create(
label='xyz', start_datetime=localtime().replace(day=11, month=11, year=2020), places=10, agenda=agenda
)
event = Event.objects.create(
label='abc',
start_datetime=localtime().replace(day=4, month=11, year=2020),
places=10,
agenda=agenda,
repeat='weekly',
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/2020/11/11/' % agenda.pk)
assert len(ctx.captured_queries) == 6
assert len(resp.pyquery.find('.event-info')) == 2
assert 'abc' in resp.pyquery.find('.event-info')[0].text
assert 'xyz' in resp.pyquery.find('.event-info')[1].text
# trying to access event recurrence creates it
event_count = Event.objects.count()
time = localtime(event.start_datetime).strftime('%H%M')
resp = resp.click(href='abc:2020-11-11-%s' % time)
assert Event.objects.count() == event_count + 1
resp = app.get('/manage/agendas/%s/2020/11/11/' % agenda.pk)
assert len(resp.pyquery.find('.event-info')) == 2
# create another event with recurrence, the same day/time
start_datetime = localtime().replace(day=4, month=11, year=2020)
event = Event.objects.create(
label='def', start_datetime=start_datetime, places=10, agenda=agenda, repeat='weekly'
)
resp = app.get('/manage/agendas/%s/2020/11/11/' % agenda.pk)
# the event occurence in DB does not hide recurrence of the second recurrent event
assert len(resp.pyquery.find('.event-info')) == 3
def test_agenda_events_day_view_midnight(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events', default_view='day')
midnight = localtime(now().replace(day=11, month=11, year=2020)).replace(hour=0, minute=0)
Event.objects.create(label='xyz', start_datetime=midnight, places=10, agenda=agenda)
login(app)
resp = app.get('/manage/agendas/%s/day/' % agenda.pk)
assert resp.location.endswith('/2020/11/11/')
resp = resp.follow()
assert len(resp.pyquery.find('.event-info')) == 1
@freezegun.freeze_time('2020-10-01')
def test_agenda_events_month_view(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
today = datetime.date.today()
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
assert 'Day view' in resp.text
assert "This month doesn't have any event configured." in resp.text
# add event in a future month, a wednesday
Event.objects.create(
label='xyz', start_datetime=localtime().replace(day=11, month=11, year=2020), places=10, agenda=agenda
)
# current month still doesn't have events
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert "This month doesn't have any event configured." in resp.text
# add recurring event on every Wednesday
start_datetime = localtime().replace(day=4, month=11, year=2020)
event = Event.objects.create(
label='abc', start_datetime=start_datetime, places=10, agenda=agenda, repeat='weekly'
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 11))
assert len(ctx.captured_queries) == 9
assert len(resp.pyquery.find('.event-info')) == 5
assert 'abc' in resp.pyquery.find('.event-info')[0].text
assert 'abc' in resp.pyquery.find('.event-info')[1].text
assert 'xyz' in resp.pyquery.find('.event-info')[2].text
assert 'abc' in resp.pyquery.find('.event-info')[3].text
assert 'abc' in resp.pyquery.find('.event-info')[4].text
time_period_exception = TimePeriodException.objects.create(
desk=agenda.desk_set.get(),
start_datetime=start_datetime + datetime.timedelta(days=6),
end_datetime=start_datetime + datetime.timedelta(days=8),
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 11))
assert len(resp.pyquery.find('.event-info')) == 4
assert 'abc' in resp.pyquery.find('li')[4].text_content()
assert 'Exception: 11/10/2020' in resp.pyquery.find('li')[5].text_content()
assert 'xyz' in resp.pyquery.find('li')[6].text_content()
# 12/2020 has 5 Wednesday
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
assert len(resp.pyquery.find('.event-info')) == 5
# trying to access event recurrence creates it
event_count = Event.objects.count()
time = localtime(event.start_datetime).strftime('%H%M')
resp = resp.click(href='abc:2020-12-02-%s' % time).follow()
assert Event.objects.count() == event_count + 1
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
assert len(resp.pyquery.find('.event-info')) == 5
# create another event with recurrence, the same day/time
start_datetime = localtime().replace(day=4, month=11, year=2020)
event = Event.objects.create(
label='def', start_datetime=start_datetime, places=10, agenda=agenda, repeat='weekly'
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
# the event occurence in DB does not hide recurrence of the second recurrent event
assert len(resp.pyquery.find('.event-info')) == 10
resp = resp.click(href='abc:2020-12-09-%s' % time).follow()
resp = resp.click('Options')
resp.form['start_datetime_1'] = '13:12'
resp = resp.form.submit(status=302).follow()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
assert len(resp.pyquery.find('.event-info')) == 10
assert '1:12 p.m.' in resp.text
Event.objects.get(slug='abc--2020-12-02-%s' % time).cancel()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
assert 'Cancelled' in resp.text
bad_event_url = '/manage/agendas/%s/create_event_recurrence/abc:2020-10-8-1130/' % agenda.id
resp = app.get(bad_event_url, status=404)
def test_agenda_events_month_view_midnight(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events', default_view='day')
midnight = localtime(now().replace(day=1, month=11, year=2020)).replace(hour=0, minute=0)
Event.objects.create(label='xyz', start_datetime=midnight, places=10, agenda=agenda)
login(app)
resp = app.get('/manage/agendas/%s/month/' % agenda.pk)
assert resp.location.endswith('/2020/11/')
resp = resp.follow()
assert len(resp.pyquery.find('.event-info')) == 1
def test_agenda_open_events_view(app, admin_user, manager_user):
agenda = Agenda.objects.create(
label='Events', kind='events', minimal_booking_delay=2, maximal_booking_delay=5
)
today = datetime.date.today()
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, today.year, today.month))
assert 'Open events' in resp.text
resp = app.get('/manage/agendas/%s/events/open/' % agenda.pk)
assert 'Month view' in resp.text
assert "This agenda doesn't have any open event configured." in resp.text
# create some events
# past event
Event.objects.create(
agenda=agenda, label='event A', start_datetime=now() - datetime.timedelta(days=1), places=42
)
# too late
Event.objects.create(
agenda=agenda, label='event B', start_datetime=now() + datetime.timedelta(days=1), places=42
)
# too soon
Event.objects.create(
agenda=agenda, label='event C', start_datetime=now() + datetime.timedelta(days=6), places=42
)
# in range
Event.objects.create(
agenda=agenda, label='event D', start_datetime=now() + datetime.timedelta(days=3), places=42
)
# publication date in future
Event.objects.create(
agenda=agenda,
label='event E',
start_datetime=now() + datetime.timedelta(days=3),
publication_date=today + 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_date=today - datetime.timedelta(days=1),
places=42,
)
# weekly recurring event, first recurrence is in the past but second is in range
event = Event.objects.create(
label='event G',
start_datetime=now() - datetime.timedelta(days=3),
places=10,
agenda=agenda,
repeat='weekly',
)
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_date=today - 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_date=today - datetime.timedelta(days=1),
places=42,
)
resp = app.get('/manage/agendas/%s/events/open/' % agenda.pk)
assert 'event H' in resp.text
assert '<h4>February %s</h4>' % (today.year + 2) in resp.text
# not enough permissions
app.reset()
app = login(app, username='manager', password='manager')
app.get('/manage/agendas/%s/events/open/' % agenda.pk, status=403)
# just enough permissions
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
resp = app.get('/manage/agendas/%s/events/open/' % agenda.pk)
assert 'Month view' in resp.text
# wrong kind
agenda.kind = 'meetings'
agenda.save()
app.get('/manage/agendas/%s/events/open/' % agenda.pk, status=404)
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/agendas/%s/events/open/' % agenda.pk, status=404)
def test_agenda_month_view(app, admin_user, manager_user, api_user):
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
today = datetime.date.today()
meetingtype = MeetingType(agenda=agenda, label='passeport', duration=20)
meetingtype.save()
login(app)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'Month view' in resp.text
resp = resp.click('Month view')
assert resp.request.url.endswith('%s/%s/' % (today.year, today.month))
assert 'Day view' in resp.text # date view link should be present
assert 'No opening hours this month.' in resp.text
today = datetime.date(2018, 11, 10) # fixed day
timeperiod_weekday = today.weekday()
timeperiod = TimePeriod(
desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
timeperiod.save()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert 'No opening hours this month.' not in resp.text
assert '<div class="booking' not in resp.text
first_month_day = today.replace(day=1)
last_month_day = today.replace(day=1, month=today.month + 1) - datetime.timedelta(days=1)
start_week_number = first_month_day.isocalendar()[1]
end_week_number = last_month_day.isocalendar()[1]
weeks_number = end_week_number - start_week_number + 1
assert resp.text.count('<tr') == 9 * weeks_number
# check opening hours cells
assert '<div class="opening-hours" style="height:800.0%;top:0.0%;width:97.0%;left:1.0%' in resp.text
# book some slots
app.reset()
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
booking_url = resp.json['data'][0]['api']['fillslot_url']
booking_url2 = resp.json['data'][2]['api']['fillslot_url']
booking = app.post(booking_url)
booking_2 = app.post_json(booking_url2, params={'label': 'foo book', 'user': 'bar', 'url': 'http://baz/'})
app.reset()
login(app)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
assert resp.text.count('<div class="booking" style="left:1.0%;height:33.0%;') == 2 # booking cells
assert len(resp.pyquery.find('span.desk')) == 0
desk = Desk.objects.create(agenda=agenda, label='Desk B')
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
assert len(resp.pyquery.find('span.desk')) == 2
timeperiod = TimePeriod(
desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
timeperiod.save()
app.reset()
booking_3 = app.post(booking_url)
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
# count occurences of timeperiod weekday in current month
d = first_month_day
weekdays = 0
while d <= last_month_day:
if d.weekday() == timeperiod_weekday:
weekdays += 1
d += datetime.timedelta(days=1)
assert resp.text.count('<div class="opening-hours"') == 2 * weekdays
current_month = today.strftime('%Y-%m')
if current_month in booking_url or current_month in booking_url2:
assert resp.text.count('<div class="booking"') == 3
# cancel bookings
app.reset()
app.post(booking.json['api']['cancel_url'])
app.post(booking_2.json['api']['cancel_url'])
app.post(booking_3.json['api']['cancel_url'])
# make sure the are not
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert resp.text.count('<div class="booking"') == 0
# check December is correctly displayed
today = datetime.date(2018, 12, 10)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert 'No opening hours this month.' not in resp.text
# display exception
unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar')
TimePeriodException.objects.create(
label='Calendar exception',
unavailability_calendar=unavailability_calendar,
start_datetime=make_aware(datetime.datetime(2018, 12, 15, 5, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 15, 14, 0)),
)
unavailability_calendar.desks.add(desk)
TimePeriodException.objects.create(
label='Exception for a December day',
desk=desk,
start_datetime=make_aware(datetime.datetime(2018, 12, 15, 14, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 15, 23, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert len(ctx.captured_queries) == 11
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height:400.0%;top:0.0%;width:48.0%;left:50.0%;',
'title': 'Calendar exception',
}
assert resp.pyquery.find('.exception-hours')[1].attrib == {
'class': 'exception-hours',
'style': 'height:400.0%;top:400.0%;width:48.0%;left:50.0%;',
'title': 'Exception for a December day',
}
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_weekend(app, admin_user, kind):
month, year = 1, 2019
monday = 0
if kind == 'meetings':
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
TimePeriod.objects.create(
desk=desk, weekday=monday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert 'Sunday' not in resp.text
assert 'Saturday' not in resp.text
# No Monday on first row since month starts a Tuesday
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 1
# When weekend is hidden, do not display an empty first week
month, year = 12, 2019 # month starts a Sunday
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
month, year = 6, 2019 # month starts a Saturday
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
saturday = 5
timeperiod_sat = TimePeriod.objects.create(
desk=desk, weekday=saturday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 5
sunday = 6
TimePeriod.objects.create(
desk=desk, weekday=sunday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
timeperiod_sat.delete()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, year, month))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_dst_change(app, admin_user, kind):
if kind == 'meetings':
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
meetingtype = MeetingType.objects.create(agenda=agenda, label='passeport', duration=20)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=real_agenda, label='passeport', duration=20)
for weekday in range(0, 7): # open all mornings
TimePeriod.objects.create(
desk=desk, weekday=weekday, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
)
login(app)
for date in ('2019-10-01', '2019-10-31'):
with freezegun.freeze_time(date):
resp = app.get('/manage/agendas/%s/2019/10/' % agenda.id)
# check all days are correctly aligned
assert resp.text.count('height:300.0%;top:0.0%') == 31
# book some slots
for date_tuple in [(2019, 10, 2, 10, 0), (2019, 10, 29, 10, 0)]:
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(make_aware(datetime.datetime(*date_tuple))),
)
Booking.objects.create(event=event)
# check booked slots are similarly aligned
login(app)
resp = app.get('/manage/agendas/%s/2019/10/' % agenda.id)
assert resp.text.count('height:33.0%;top:100.0%;') == 2
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_januaries(app, admin_user, kind):
if kind == 'meetings':
agenda = Agenda.objects.create(label='Passports', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
MeetingType.objects.create(agenda=agenda, label='passport', duration=20)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
MeetingType.objects.create(agenda=real_agenda, label='passport', duration=20)
TimePeriod(desk=desk, weekday=2, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)).save()
for year in range(2020, 2030):
date = datetime.date(year, 1, 1)
with freezegun.freeze_time(date):
login(app)
resp = app.get('/manage/agendas/%s/%s/1/' % (agenda.id, date.year))
assert resp.text.count('<th></th>') in (4, 5)
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
def test_agenda_month_view_event_outside_timeperiod(app, admin_user, kind):
today = datetime.date.today()
if kind == 'meetings':
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
else:
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=real_agenda, label='passport', duration=20)
login(app)
# no time period - no events
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert 'No opening hours this month.' in resp.text
assert 'div class="booking' not in resp.text
# book some slots
middle_day = now().replace(day=15)
for hour, minute in [(9, 0), (17, 0)]:
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now().replace(day=middle_day.day - middle_day.weekday() + 2)).replace(
hour=hour, minute=minute
),
)
Booking.objects.create(event=event)
# no time period - events are displayed
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 2
# bookings are cancelled
Booking.objects.update(cancellation_datetime=now())
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert 'No opening hours this month.' in resp.text
assert resp.text.count('div class="booking') == 0
# events outside time period
Booking.objects.update(cancellation_datetime=None) # reset
TimePeriod.objects.create(
desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0)
)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 2
assert '<div class="opening-hours" style="height:600.0%;top:100.0%;width:97.0%;left:1.0%' in resp.text
assert 'Sunday' not in resp.text
assert 'Saturday' not in resp.text
# create an event on saturday
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now()).replace(
day=middle_day.day - middle_day.weekday() + 5, hour=10, minute=0
),
)
Booking.objects.create(event=event)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 3
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
# bookings are cancelled
Booking.objects.update(cancellation_datetime=now())
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 0
# and a timeperiod
Booking.objects.update(cancellation_datetime=None) # reset
TimePeriod.objects.create(
desk=desk, weekday=5, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0)
)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 3
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
# create an event on sunday
middle_day = now().replace(day=15)
event = Event.objects.create(
agenda=desk.agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=localtime(now()).replace(
day=middle_day.day - middle_day.weekday() + 6, hour=10, minute=0
),
)
Booking.objects.create(event=event)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 4
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
# bookings are cancelled
Booking.objects.update(cancellation_datetime=now())
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 0
# and a timeperiod
Booking.objects.update(cancellation_datetime=None) # reset
TimePeriod.objects.create(
desk=desk, weekday=6, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0)
)
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.pk, today.year, today.month))
assert resp.text.count('div class="booking') == 4
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
def test_agenda_view_event(app, manager_user):
agenda = Agenda(label=u'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_name = 'Foo Bar User'
if i == 6:
booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 16, 0))
booking.user_name = 'Foo Bar User 2'
booking.label = 'Foo Bar Label 2'
if i == 7:
booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 17, 0))
booking.label = 'Foo Bar Label 3'
booking.save()
Booking.objects.create(event=event, cancellation_datetime=now())
app = login(app, username='manager', password='manager')
resp = app.get('/manage/agendas/%s/2019/12/' % agenda.id, status=200)
resp = resp.click('xyz')
assert 'Bookings (8/10): 2 remaining places' in resp.text
assert 'Waiting' not in resp.text
assert 'This event is overbooked.' not in resp.text
assert 'This event is full.' not in resp.text
event.waiting_list_places = 5
event.save()
resp = app.get(resp.request.url)
assert 'Waiting List (0/5): 5 remaining places' in resp.text
assert 'Unknown, Dec. 21, 2019, 2 p.m.' in resp.text
assert 'Unknown, Dec. 21, 2019, 2:01 p.m.' in resp.text
assert 'Unknown, Dec. 21, 2019, 2:02 p.m.' in resp.text
assert 'Unknown, Dec. 21, 2019, 2:03 p.m.' in resp.text
assert 'Unknown, Dec. 21, 2019, 2:04 p.m.' in resp.text
assert 'Foo Bar User, Dec. 21, 2019, 3 p.m.' in resp.text
assert 'Foo Bar User 2, Dec. 21, 2019, 4 p.m.' in resp.text
assert 'Foo Bar Label 3, Dec. 21, 2019, 5 p.m.' in resp.text
booking = Booking.objects.order_by('pk')[0]
booking.in_waiting_list = True
booking.save()
booking = Booking.objects.order_by('pk')[1]
booking.in_waiting_list = True
booking.save()
resp = app.get(resp.request.url)
assert 'Waiting List (2/5): 3 remaining places' in resp.text
assert 'Bookings (6/10): 4 remaining places' in resp.text
assert list(resp.context['booked']) == list(Booking.objects.order_by('creation_datetime')[2:8])
assert list(resp.context['waiting']) == list(Booking.objects.order_by('creation_datetime')[0:2])
event.places = 5
event.save()
resp = app.get(resp.request.url)
assert 'This event is overbooked.' in resp.text
assert 'This event is full.' not in resp.text
event.places = 6
event.save()
resp = app.get(resp.request.url)
assert 'This event is overbooked.' not in resp.text
assert 'This event is full.' in resp.text
def test_agenda_view_edit_event(app, manager_user):
test_agenda_view_event(app, manager_user)
agenda = Agenda.objects.first()
resp = app.get('/manage/agendas/%s/2019/12/' % agenda.id, status=200)
resp = resp.click('xyz')
assert 'Options' not in resp.text
assert 'Delete' not in resp.text
agenda.edit_role = manager_user.groups.all()[0]
agenda.save()
event_url = resp.request.url
resp = app.get(event_url)
assert 'Options' in resp.text
resp = resp.click('Options')
resp.form['start_datetime_0'] = agenda.event_set.first().start_datetime.strftime('%Y-%m-%d')
resp.form['start_datetime_1'] = agenda.event_set.first().start_datetime.strftime('%H:%M')
resp = resp.form.submit(status=302).follow()
assert event_url == resp.request.url
resp = resp.click('Delete')
resp = resp.form.submit()
assert Event.objects.count() == 0
def test_import_agenda_as_manager(app, manager_user):
# open /manage/ access to manager_user, and check agenda import 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/', status=200)
app.get('/manage/agendas/import/', status=403)
def test_import_agenda(app, admin_user):
agenda = Agenda(label=u'Foo bar')
agenda.save()
app = login(app)
with freezegun.freeze_time('2020-06-15'):
resp = app.get('/manage/agendas/%s/export' % agenda.id)
assert resp.headers['content-type'] == 'application/json'
assert resp.headers['content-disposition'] == 'attachment; filename="export_agenda_foo-bar_20200615.json"'
agenda_export = resp.text
# invalid json
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', b'garbage', 'application/json')
resp = resp.form.submit()
assert 'File is not in the expected JSON format.' in resp.text
# empty json
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', b'{}', 'application/json')
resp = resp.form.submit().follow()
assert 'No data found.' in resp.text
# existing agenda
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', agenda_export.encode('utf-8'), 'application/json')
resp = resp.form.submit().follow()
assert 'No agenda created. An agenda has been updated.' in resp.text
assert Agenda.objects.count() == 1
# new agenda
Agenda.objects.all().delete()
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', agenda_export.encode('utf-8'), 'application/json')
resp = resp.form.submit().follow()
assert 'An agenda has been created. No agenda updated.' in resp.text
assert Agenda.objects.count() == 1
# multiple agendas
agendas = json.loads(agenda_export)
agendas['agendas'].append(copy.copy(agendas['agendas'][0]))
agendas['agendas'].append(copy.copy(agendas['agendas'][0]))
agendas['agendas'][1]['label'] = 'Foo bar 2'
agendas['agendas'][1]['slug'] = 'foo-bar-2'
agendas['agendas'][2]['label'] = 'Foo bar 3'
agendas['agendas'][2]['slug'] = 'foo-bar-3'
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', json.dumps(agendas).encode('utf-8'), 'application/json')
resp = resp.form.submit().follow()
assert '2 agendas have been created. An agenda has been updated.' in resp.text
assert Agenda.objects.count() == 3
Agenda.objects.all().delete()
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', json.dumps(agendas).encode('utf-8'), 'application/json')
resp = resp.form.submit().follow()
assert '3 agendas have been created. No agenda updated.' in resp.text
assert Agenda.objects.count() == 3
# reference to unknown group
agenda_export_dict = json.loads(force_text(agenda_export))
agenda_export_dict['agendas'][0]['permissions']['view'] = u'gé1'
agenda_export = json.dumps(agenda_export_dict).encode('utf-8')
Agenda.objects.all().delete()
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', agenda_export, 'application/json')
resp = resp.form.submit()
assert u'Missing roles: &quot;gé1&quot;' in resp.text
del agenda_export_dict['agendas'][0]['permissions']['view']
# missing field
del agenda_export_dict['agendas'][0]['kind']
agenda_export = json.dumps(agenda_export_dict).encode('utf-8')
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', agenda_export, 'application/json')
resp = resp.form.submit()
assert resp.context['form'].errors['agendas_json'] == ['Key "kind" is missing.']
def test_import_unavailability_calendar(app, admin_user):
calendar = UnavailabilityCalendar.objects.create(label=u'Foo bar')
app = login(app)
with freezegun.freeze_time('2020-06-15'):
resp = app.get('/manage/unavailability-calendar/%s/export' % calendar.id)
assert resp.headers['content-type'] == 'application/json'
assert (
resp.headers['content-disposition']
== 'attachment; filename="export_unavailability-calendar_foo-bar_20200615.json"'
)
calendar_export = resp.text
# empty json
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', b'{}', 'application/json')
resp = resp.form.submit().follow()
assert 'No data found.' in resp.text
# existing unavailability calendar
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', calendar_export.encode('utf-8'), 'application/json')
resp = resp.form.submit().follow()
assert 'No unavailability calendar created. An unavailability calendar has been updated.' in resp.text
assert UnavailabilityCalendar.objects.count() == 1
# new unavailability calendar
UnavailabilityCalendar.objects.all().delete()
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', calendar_export.encode('utf-8'), 'application/json')
resp = resp.form.submit().follow()
assert 'An unavailability calendar has been created. No unavailability calendar updated.' in resp.text
assert UnavailabilityCalendar.objects.count() == 1
# multiple unavailability calendars
calendars = json.loads(calendar_export)
calendars['unavailability_calendars'].append(copy.copy(calendars['unavailability_calendars'][0]))
calendars['unavailability_calendars'].append(copy.copy(calendars['unavailability_calendars'][0]))
calendars['unavailability_calendars'][1]['label'] = 'Foo bar 2'
calendars['unavailability_calendars'][1]['slug'] = 'foo-bar-2'
calendars['unavailability_calendars'][2]['label'] = 'Foo bar 3'
calendars['unavailability_calendars'][2]['slug'] = 'foo-bar-3'
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload(
'export.json', json.dumps(calendars).encode('utf-8'), 'application/json'
)
resp = resp.form.submit().follow()
assert (
'2 unavailability calendars have been created. An unavailability calendar has been updated.'
in resp.text
)
assert UnavailabilityCalendar.objects.count() == 3
UnavailabilityCalendar.objects.all().delete()
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload(
'export.json', json.dumps(calendars).encode('utf-8'), 'application/json'
)
resp = resp.form.submit().follow()
assert '3 unavailability calendars have been created. No unavailability calendar updated.' in resp.text
assert UnavailabilityCalendar.objects.count() == 3
# reference to unknown group
calendar_export_dict = json.loads(force_text(calendar_export))
calendar_export_dict['unavailability_calendars'][0]['permissions']['view'] = u'gé1'
calendar_export = json.dumps(calendar_export_dict).encode('utf-8')
UnavailabilityCalendar.objects.all().delete()
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', calendar_export, 'application/json')
resp = resp.form.submit()
assert u'Missing roles: &quot;gé1&quot;' in resp.text
del calendar_export_dict['unavailability_calendars'][0]['permissions']['view']
def test_import_does_not_delete_bookings(app, admin_user):
agenda = Agenda.objects.create(label='Foo', kind='meetings')
meeting_type = MeetingType.objects.create(agenda=agenda, label='Meeting Type', duration=30)
desk = Desk.objects.create(agenda=agenda, label='Desk', slug='desk')
event = Event(start_datetime=now(), places=10, meeting_type=meeting_type, desk=desk, agenda=agenda)
event.save()
booking = Booking(event=event)
booking.save()
assert Booking.objects.count() == 1
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Export')
assert resp.headers['content-type'] == 'application/json'
agenda_export = resp.text
# existing agenda
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', agenda_export.encode('utf-8'), 'application/json')
resp = resp.form.submit().follow()
assert 'No agenda created. An agenda has been updated.' in resp.text
assert Agenda.objects.count() == 1
assert Booking.objects.count() == 1
def test_virtual_agenda_add(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp.form['label'] = 'Virtual agenda'
resp.form['kind'] = 'virtual'
resp = resp.form.submit()
agenda = Agenda.objects.get(label='Virtual agenda')
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
assert agenda.minimal_booking_delay is None
assert agenda.maximal_booking_delay is None
def test_virtual_agenda_day_view(app, admin_user, manager_user):
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda_1 = Agenda.objects.create(label='Real 1', kind='meetings')
real_agenda_2 = Agenda.objects.create(label='Real 2', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_1)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_2)
desk1 = Desk.objects.create(agenda=real_agenda_1, label='New Desk')
desk2 = Desk.objects.create(agenda=real_agenda_2, label='New Desk')
today = datetime.date.today()
meetingtype1 = MeetingType.objects.create(agenda=real_agenda_1, label='Bar', duration=30)
meetingtype2 = MeetingType.objects.create(agenda=real_agenda_2, label='Bar', duration=30)
login(app)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' in resp.text # no time pediod
timeperiod = TimePeriod.objects.create(
desk=desk1, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert 'No opening hours this day.' not in resp.text
assert 'div class="booking' not in resp.text
assert resp.text.count('<tr') == 9 # 10->18 (not included)
timeperiod.end_time = datetime.time(18, 30) # end during an hour
timeperiod.save()
resp = app.get('/manage/agendas/%s/%s/%s/%s/' % (agenda.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 10 # 10->18 (included)
# check opening hours cells
assert '<div class="opening-hours"' in resp.text
assert 'style="height: 850%; top: 0%;"' in resp.text
# book some slots
for hour, minute in [(10, 30), (14, 0)]:
event = Event.objects.create(
agenda=real_agenda_1,
places=1,
desk=desk1,
meeting_type=meetingtype1,
start_datetime=now().replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
event = Event.objects.create(
agenda=real_agenda_2,
places=1,
desk=desk2,
meeting_type=meetingtype2,
start_datetime=now().replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 4
assert 'hourspan-2' in resp.text # table CSS class
assert 'height: 50%; top: 0%;' in resp.text # booking cells
# create a shorter meeting type, this will change the table CSS class
# (and visually this will give more room for events)
MeetingType.objects.create(agenda=real_agenda_1, label='Baz', duration=15)
MeetingType.objects.create(agenda=real_agenda_2, label='Baz', duration=15)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 4
assert 'hourspan-4' in resp.text # table CSS class
# cancel a booking
booking = Booking.objects.first()
booking.cancel()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
assert resp.text.count('div class="booking') == 3
# not enough permissions
app.reset()
app = login(app, username='manager', password='manager')
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=403)
# just enough permissions
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200)
# display exception
TimePeriodException.objects.create(
label='Exception for the afternoon',
desk=desk1,
start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 13, 0)),
end_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 23, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get(
'/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200
)
assert len(ctx.captured_queries) == 15
# day is displaying rows from 10am to 6pm,
# opening hours, 10am to 1pm gives top: 300%
# rest of the day, 1pm to 6(+1)pm gives 600%
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height: 600%; top: 300%;',
}
assert resp.pyquery.find('.exception-hours span')[0].text == 'Exception for the afternoon'
def test_virtual_agenda_month_view(app, admin_user):
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
real_agenda_1 = Agenda.objects.create(label='Real 1', kind='meetings')
real_agenda_2 = Agenda.objects.create(label='Real 2', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_1)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_2)
desk1 = Desk.objects.create(agenda=real_agenda_1, label='New Desk')
desk2 = Desk.objects.create(agenda=real_agenda_2, label='New Desk')
today = datetime.date.today()
meetingtype1 = MeetingType.objects.create(agenda=real_agenda_1, label='Bar', duration=30)
meetingtype2 = MeetingType.objects.create(agenda=real_agenda_2, label='Bar', duration=30)
login(app)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
assert 'Month view' in resp.text
resp = resp.click('Month view')
assert resp.request.url.endswith('%s/%s/' % (today.year, today.month))
assert 'Day view' in resp.text # date view link should be present
assert 'No opening hours this month.' in resp.text
today = datetime.date(2018, 11, 10) # fixed day
timeperiod_weekday = today.weekday()
TimePeriod.objects.create(
desk=desk1, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert 'No opening hours this month.' not in resp.text
assert '<div class="booking' not in resp.text
first_month_day = today.replace(day=1)
last_month_day = today.replace(day=1, month=today.month + 1) - datetime.timedelta(days=1)
start_week_number = first_month_day.isocalendar()[1]
end_week_number = last_month_day.isocalendar()[1]
weeks_number = end_week_number - start_week_number + 1
assert resp.text.count('<tr') == 9 * weeks_number
# check opening hours cells
assert '<div class="opening-hours" style="height:800.0%;top:0.0%;width:48.0%;left:1.0%' in resp.text
# book some slots
for hour, minute in [(10, 30), (14, 0)]:
event = Event.objects.create(
agenda=real_agenda_1,
places=1,
desk=desk1,
meeting_type=meetingtype1,
start_datetime=now().replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
event = Event.objects.create(
agenda=real_agenda_2,
places=1,
desk=desk2,
meeting_type=meetingtype2,
start_datetime=now().replace(hour=hour, minute=minute),
)
Booking.objects.create(event=event)
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
assert resp.text.count('<div class="booking" style="left:1.0%;height:50.0%;') == 2 # booking cells
assert (
resp.text.count('<div class="booking" style="left:50.0%;height:50.0%;min-height:50.0%;') == 2
) # booking cells
# cancel a booking
booking = Booking.objects.first()
booking.cancel()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, date.year, date.month))
assert resp.text.count('<div class="booking"') == 3
# check December is correctly displayed
today = datetime.date(2018, 12, 10)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert 'No opening hours this month.' not in resp.text
# display exception
TimePeriodException.objects.create(
label='Exception for a December day',
desk=desk1,
start_datetime=make_aware(datetime.datetime(2018, 12, 15, 5, 0)),
end_datetime=make_aware(datetime.datetime(2018, 12, 15, 23, 0)),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
assert len(ctx.captured_queries) == 12
assert resp.pyquery.find('.exception-hours')[0].attrib == {
'class': 'exception-hours',
'style': 'height:800.0%;top:0.0%;width:48.0%;left:1.0%;',
'title': 'Exception for a December day',
}
def test_virtual_agenda_settings_empty(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert 'Include Agenda' in resp.text
assert 'Options' in resp.text
assert 'Export' in resp.text
assert 'Delete' in resp.text
assert 'Included Agendas' in resp.text
assert 'Add Excluded Period' in resp.text
assert "This virtual agenda doesn't include any agenda yet" in resp.text
# No meeting types yet
assert 'Meeting Types' not in resp.text
# No absence yet
assert 'Excluded Periods' not in resp.text
def test_virtual_agenda_settings(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
TimePeriod.objects.create(
agenda=agenda, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert "This virtual agenda doesn't include any agenda yet" not in resp.text
for real_agenda in [meeting_agenda_1, meeting_agenda_2]:
assert real_agenda.label in resp.text
assert '/manage/agendas/%s/settings' % real_agenda.pk in resp.text
assert 'Meeting Types' in resp.text
assert 'MT' in resp.text
assert 'mt' in resp.text
assert '10' in resp.text
assert 'Excluded Periods' in resp.text
assert 'Monday' in resp.text
# Error message when incompatible meeting types
mt2.delete()
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
assert "This virtual agenda doesn't have any meeting type." in resp.text
def test_virtual_agenda_settings_include(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
Agenda.objects.create(label='Event agenda', kind='events')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
MeetingType.objects.create(label='MT', duration=30, agenda=meeting_agenda_1)
Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Include Agenda')
# Only meetings agenda are proposed (2) + 1 empty choice = 3
assert len(resp.form['real_agenda'].options) == 3
# Include a real agenda
resp.form['real_agenda'].value = meeting_agenda_1.pk
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
assert VirtualMember.objects.get(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
resp = resp.follow()
resp = resp.click('Include Agenda')
# The previously include agenda is not proposed any more
assert len(resp.form['real_agenda'].options) == 2
def test_virtual_agenda_settings_add_excluded_period(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
resp = resp.click('Add Excluded Period')
resp.form.get('weekdays', index=0).checked = True
resp.form['start_time'] = '10:00'
resp.form['end_time'] = '17:00'
resp = resp.form.submit()
tp = TimePeriod.objects.get(agenda=agenda)
assert tp.weekday == 0
assert tp.start_time.hour == 10
assert tp.start_time.minute == 0
assert tp.end_time.hour == 17
assert tp.end_time.minute == 0
resp = resp.follow()
assert u'Monday / 10 a.m. → 5 p.m.' in resp.text
def test_virtual_agenda_settings_edit_excluded_period(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
tp = TimePeriod.objects.create(
agenda=agenda, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
url = '/manage/timeperiods/%s/edit' % tp.pk
resp = resp.click(href=url)
resp.form['start_time'] = '11:00'
resp = resp.form.submit()
tp = TimePeriod.objects.get(agenda=agenda)
assert tp.weekday == 0
assert tp.start_time.hour == 11
assert tp.start_time.minute == 0
assert tp.end_time.hour == 18
assert tp.end_time.minute == 0
def test_virtual_agenda_settings_delete_excluded_period(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
tp = TimePeriod.objects.create(
agenda=agenda, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
url = '/manage/timeperiods/%s/delete' % tp.pk
resp = resp.click(href=url)
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
assert TimePeriod.objects.count() == 0
def test_virtual_agenda_settings_include_incompatible_agenda(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
app = login(app)
# refused because different slug
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mtt', duration=10)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;MT&quot; (10 minutes) (identifier: mt) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
# refused because different duration
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=15)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;MT&quot; (10 minutes) (identifier: mt) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
# refused because different label
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MTT', slug='mt', duration=10)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;MT&quot; (10 minutes) (identifier: mt) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
# refused because has one more meeting type
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='AA', slug='aa', duration=30)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Extra meeting type, &quot;AA&quot;.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
# ok because mt2 is marked as deleted
mt2.deleted = True
mt2.save()
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert meeting_agenda_2.virtual_agendas.count() == 1
VirtualMember.objects.filter(real_agenda=meeting_agenda_2).delete()
mt.delete()
mt2.delete()
# refused because has one less meeting type
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
mt2 = MeetingType.objects.create(agenda=meeting_agenda_1, label='AA', slug='aa', duration=30)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;AA&quot; (30 minutes) (identifier: aa) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
mt2.delete()
# deleted meeting type is not checked
mt = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10, deleted=True)
resp = app.get('/manage/agendas/%s/add-virtual-member' % agenda.pk)
resp.form['real_agenda'].value = meeting_agenda_2.pk
resp = resp.form.submit()
assert 'This agenda does not have the same meeting types provided by the virtual agenda.' in resp.text
assert 'Meeting type &quot;MT&quot; (10 minutes) (identifier: mt) does no exist.' in resp.text
assert meeting_agenda_2.virtual_agendas.count() == 0
mt.delete()
def test_cant_delete_meetingtype_used_by_virtual_agenda(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
mt1 = MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
# ok because there is only one agenda in the virtual agenda
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % meeting_agenda_1.pk)
resp = resp.click('MT')
resp = resp.click('Delete')
resp = resp.form.submit()
assert not meeting_agenda_1.iter_meetingtypes()
mt1.deleted = False
mt1.save()
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
resp = app.get('/manage/agendas/%s/settings' % meeting_agenda_2.pk)
resp = resp.click('MT')
resp = resp.click('Delete')
assert 'This cannot be removed as it used by a virtual agenda' in resp.text
assert 'disabled' in resp.text
resp = app.post('/manage/meetingtypes/%s/delete' % mt2.pk, status=403)
def test_cant_modify_meetingtype_used_by_virtual_agenda(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
mt1 = MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
app = login(app)
# ok because there is only one agenda in the virtual agenda
resp = app.get('/manage/meetingtypes/%s/edit' % mt1.pk)
resp.form['label'].value = 'MTT'
resp = resp.form.submit()
assert MeetingType.objects.get(agenda=meeting_agenda_1, label='MTT', slug='mt', duration=10)
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='MTT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
app = login(app)
resp = app.get('/manage/meetingtypes/%s/edit' % mt2.pk)
resp.form['label'].value = 'Oho'
resp = resp.form.submit()
assert 'This meetingtype is used by a virtual agenda' in resp.text
mt = MeetingType.objects.get(pk=mt2.pk)
assert mt.label == 'MTT'
def test_cant_add_meetingtype_if_virtual_agenda(app, admin_user):
agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual')
meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')
MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10)
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_1)
app = login(app)
# ok because there is only one agenda in the virtual agenda
resp = app.get('/manage/agendas/%s/add-meeting-type' % meeting_agenda_1.pk)
resp.form['duration'].value = '12'
resp.form['label'].value = 'Oho'
resp = resp.form.submit()
assert MeetingType.objects.filter(agenda=meeting_agenda_1).count() == 2
MeetingType.objects.get(agenda=meeting_agenda_1, label='Oho').delete()
meeting_agenda_2 = Agenda.objects.create(label='Meeting agenda 2', kind='meetings')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2)
MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10)
app = login(app)
resp = app.get('/manage/agendas/%s/add-meeting-type' % meeting_agenda_1.pk)
resp.form['duration'].value = '12'
resp.form['label'].value = 'Oho'
resp = resp.form.submit()
assert 'Can&#39;t add a meetingtype to an agenda that is included in a virtual agenda.' in resp.text
assert MeetingType.objects.filter(agenda=meeting_agenda_1).count() == 1
def test_duplicate_agenda(app, admin_user):
agenda = Agenda.objects.create(label=u'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, api_user):
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
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
app.reset()
login(app)
booking = Booking.objects.get(pk=booking_json['booking_id'])
date = booking.event.start_datetime
month_view_url = '/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month)
resp = app.get(month_view_url)
assert len(resp.pyquery.find('div.booking a.cancel')) == 1 # cancel button is shown
resp = resp.click('Cancel')
# no callback url was provided at booking, warn user cancellation is forbidden
assert 'no callback url' in resp.text
assert not 'Proceed with cancellation' in resp.text
booking.delete()
# provide callback url this time
booking_url2 = bookings_resp.json['data'][1]['api']['fillslot_url']
booking_json2 = app.post_json(
booking_url2, params={'cancel_callback_url': 'http://example.org/jump/trigger/'}
).json
resp = app.get(month_view_url)
resp = resp.click('Cancel')
assert not 'no callback url' in resp.text
# a signed request is sent to callback_url
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
mock_response = mock.Mock(status_code=200)
mock_send.return_value = mock_response
resp = resp.form.submit()
url = mock_send.call_args[0][0].url
assert check_query(url.split('?', 1)[-1], 'chrono')
booking2 = Booking.objects.get(pk=booking_json2['booking_id'])
resp = resp.follow()
assert not resp.pyquery.find('div.booking')
assert booking2.cancellation_datetime
# request fails
booking_url3 = bookings_resp.json['data'][2]['api']['fillslot_url']
booking_json3 = app.post_json(
booking_url3, params={'cancel_callback_url': 'http://example.org/jump/trigger/'}
).json
booking3 = Booking.objects.get(pk=booking_json3['booking_id'])
def mocked_requests_connection_error(*args, **kwargs):
raise requests.exceptions.ConnectionError('unreachable')
resp = app.get(month_view_url)
resp = resp.click('Cancel')
assert resp.form['disable_trigger'].attrs['type'] == 'hidden'
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
mock_response = mock.Mock(status_code=200)
mock_send.return_value = mock_response
mock_send.side_effect = mocked_requests_connection_error
resp = resp.form.submit()
assert 'error' in resp.text
booking3.refresh_from_db()
assert not booking3.cancellation_datetime
# there is an option to force cancellation
resp.form['disable_trigger'] = True
resp = resp.form.submit()
booking3.refresh_from_db()
assert booking3.cancellation_datetime
# test day view
day_view_url = '/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day)
booking_url4 = bookings_resp.json['data'][3]['api']['fillslot_url']
booking_json4 = app.post(booking_url4).json
resp = app.get(day_view_url)
resp = resp.click('Cancel')
resp = resp.form.submit()
assert resp.location.endswith(day_view_url)
booking4 = Booking.objects.get(pk=booking_json4['booking_id'])
assert booking4.cancellation_datetime
# again
app.get('/manage/agendas/%s/bookings/%s/cancel' % (agenda.pk, booking4.pk), status=404)
def test_agenda_notifications(app, admin_user, managers_group):
agenda = Agenda.objects.create(label='Events', kind='events')
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')
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')
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Booking reminders' in resp.text
assert 'Reminders are disabled' in resp.text
resp = resp.click('Configure', href='reminder')
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Reminders are disabled' in resp.text
resp = resp.click('Configure', href='reminder')
assert not 'SMS' in resp.text
resp.form['days'] = 3
resp.form['send_email'] = True
resp.form['email_extra_info'] = 'test'
resp = resp.form.submit().follow()
assert 'Users will be reminded of their booking by email, 3 days in advance.' in resp.text
with override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO'):
resp = resp.click('Configure', href='reminder')
resp.form['send_sms'] = True
resp = resp.form.submit().follow()
assert 'Users will be reminded of their booking both by email and by SMS, 3 days in advance.' in resp.text
agenda = Agenda.objects.create(label='Meetings', kind='meetings')
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Booking reminders' in resp.text
agenda = Agenda.objects.create(label='Virtual', kind='virtual')
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert not 'Booking reminders' in resp.text
def test_manager_reminders_preview(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
AgendaReminderSettings.objects.create(
agenda=agenda,
days=1,
send_email=True,
email_extra_info='An ID will be required in order to process your form.',
send_sms=True,
sms_extra_info='Take ID card.',
)
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert (
'Users will be reminded of their booking both by email and by SMS, one day in advance.' in resp.text
)
resp = resp.click('Preview email')
assert 'Users will receive the following email:' in resp.text
assert len(resp.pyquery.find('p.email-subject')) == 1
assert '<strong>Subject:</strong> Reminder for your booking tomorrow at 2:30 p.m.' in resp.text
assert (
'You have a booking for event "Lorem Ipsum <small>(event label)</small>", on Tuesday 2 June at 2:30 p.m..'
in resp.text
)
assert 'An ID will be required' in resp.text
assert ' ea commodo consequat. <small>(event description, if present)</small>' in resp.text
assert 'Pricing: ... <small>(event pricing, if present)</small>' in resp.text
assert '<a href="#">More information<br>(link to event url, if present)</a>' in resp.text
resp = resp.click('Return to settings')
resp = resp.click('Preview SMS')
assert 'Users will receive the following SMS:' in resp.text
assert (
'Reminder: you have a booking for event "Lorem Ipsum <small>(event label)</small>", on 02/06 at 2:30 p.m.. Take ID card.'
in resp.text
)
def test_export_site(app, admin_user):
login(app)
resp = app.get('/manage/')
resp = resp.click('Export')
with freezegun.freeze_time('2020-06-15'):
resp = resp.form.submit()
assert resp.headers['content-type'] == 'application/json'
assert resp.headers['content-disposition'] == 'attachment; filename="export_agendas_20200615.json"'
site_json = json.loads(resp.text)
assert site_json == {'unavailability_calendars': [], 'agendas': []}
agenda = Agenda.objects.create(label=u'Foo Bar', kind='events')
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
resp = app.get('/manage/agendas/export/')
resp = resp.form.submit()
site_json = json.loads(resp.text)
assert len(site_json['agendas']) == 1
assert len(site_json['unavailability_calendars']) == 1
resp = app.get('/manage/agendas/export/')
resp.form['agendas'] = False
resp = resp.form.submit()
site_json = json.loads(resp.text)
assert 'agendas' not in site_json
assert 'unavailability_calendars' in site_json
def test_manager_agenda_roles(app, admin_user, manager_user):
agenda = Agenda.objects.create(label='Events', kind='events')
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Configure', href='roles')
role = manager_user.groups.all()[0]
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')
login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Configure', href='delays')
resp.form['maximal_booking_delay'] = 42
resp = resp.form.submit().follow()
assert '42 days' in resp.text
agenda.refresh_from_db()
assert agenda.maximal_booking_delay == 42
@pytest.mark.parametrize(
'view',
(
'/manage/agendas/%(agenda)s/%(year)d/%(month)d/%(day)d/',
'/manage/agendas/%(agenda)s/%(year)d/%(month)d/',
),
)
def test_agenda_booking_colors(app, admin_user, api_user, view):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
today = datetime.date.today()
timeperiod = 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
@override_settings(
EXCEPTIONS_SOURCES={
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
}
)
def test_recurring_events_manage_exceptions(settings, app, admin_user, freezer):
freezer.move_to('2021-07-01 12:10')
app = login(app)
resp = app.get('/manage/')
resp = resp.click('New')
resp.form['label'] = 'Foo bar'
resp.form['kind'] = 'events'
resp = resp.form.submit().follow()
agenda = Agenda.objects.get(label='Foo bar')
assert agenda.desk_set.count() == 1
desk = agenda.desk_set.get(slug='_exceptions_holder')
event = Event.objects.create(start_datetime=now(), places=10, agenda=agenda)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert not 'Recurrence exceptions' in resp.text
event.repeat = 'daily'
event.save()
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
assert len(resp.pyquery.find('.event-info')) == 31
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'Recurrence exceptions' in resp.text
resp = resp.click('Add a time period exception')
resp.form['start_datetime_0'] = now().strftime('%Y-%m-%d')
resp.form['start_datetime_1'] = now().strftime('%H:%M')
resp.form['end_datetime_0'] = (now() + datetime.timedelta(days=7)).strftime('%Y-%m-%d')
resp.form['end_datetime_1'] = (now() + datetime.timedelta(days=7)).strftime('%H:%M')
resp = resp.form.submit().follow()
assert desk.timeperiodexception_set.count() == 1
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
assert len(resp.pyquery.find('.event-info')) == 24
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Configure', href='exceptions')
resp = resp.click('enable').follow()
assert TimePeriodException.objects.count() > 1
assert 'Bastille Day' in resp.text
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
assert len(resp.pyquery.find('.event-info')) == 23
# add recurrence end date, which lead to recurrences creation
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
resp.form['recurrence_end_date'] = (now() + datetime.timedelta(days=31)).strftime('%Y-%m-%d')
resp = resp.form.submit()
# recurrences corresponding to exceptions have not been created
assert Event.objects.count() == 24
def test_recurring_events_exceptions_report(settings, app, admin_user, freezer):
freezer.move_to('2021-07-01 12:10')
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
start_datetime=now(),
places=10,
repeat='daily',
recurrence_end_date=now() + datetime.timedelta(days=30),
agenda=agenda,
)
event.create_all_recurrences()
app = login(app)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
assert len(resp.pyquery.find('.event-info')) == 30
time_period_exception = TimePeriodException.objects.create(
desk=agenda.desk_set.get(),
start_datetime=datetime.date(year=2021, month=7, day=5),
end_datetime=datetime.date(year=2021, month=7, day=10),
)
call_command('update_event_recurrences')
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
assert len(resp.pyquery.find('.event-info')) == 25
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert not 'warningnotice' in resp.text
event = Event.objects.get(start_datetime__day=11)
booking = Booking.objects.create(event=event)
time_period_exception.end_datetime = datetime.date(year=2021, month=7, day=12)
time_period_exception.save()
call_command('update_event_recurrences')
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
assert len(resp.pyquery.find('.event-info')) == 24
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'warningnotice' in resp.text
assert 'July 11, 2021, 2:10 p.m.' in resp.text
booking.cancel()
call_command('update_event_recurrences')
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
assert len(resp.pyquery.find('.event-info')) == 23
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert not 'warningnotice' in resp.text