# -*- coding: utf-8 -*- from __future__ import unicode_literals import codecs import copy import datetime import json import mock import os from django.contrib.auth.models import User, Group from django.db import connection from django.test.utils import CaptureQueriesContext from django.utils.encoding import force_text from django.utils.timezone import make_aware, now, localtime import freezegun import pytest import requests from webtest import Upload from chrono.agendas.models import ( Agenda, Booking, Desk, Event, MeetingType, Resource, TimePeriod, TimePeriodException, TimePeriodExceptionSource, VirtualMember, ) from chrono.manager.forms import TimePeriodExceptionForm 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 manager_user(): try: user = User.objects.get(username='manager') except User.DoesNotExist: user = User.objects.create_user('manager', password='manager') group, created = Group.objects.get_or_create(name='Managers') if created: group.save() user.groups.set([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') assert app.get('/manage/', status=403) 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 = manager_user.groups.all()[0] agenda.view_role = None 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 '

Agendas

' 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_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' 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) # check it gives a 404 on unknown agendas resp = app.get('/manage/agendas/%s/settings' % '9999', status=404) def test_add_resource(app, admin_user): app = login(app) resp = app.get('/manage/', status=200) resp = resp.click('Resources') resp = resp.click('New') resp.form['label'] = 'Foo bar' resp = resp.form.submit() resource = Resource.objects.latest('pk') assert resp.location.endswith('/manage/resource/%s/' % resource.pk) assert resource.label == 'Foo bar' assert resource.slug == 'foo-bar' def test_view_resource(app, admin_user): agenda = Agenda.objects.create(label=u'Foo Bar', kind='meetings') resource = Resource.objects.create(label='Resource 1') app = login(app) resp = app.get('/manage/resource/%s/' % resource.pk, status=200) assert '/manage/agendas/%s/settings' % agenda.pk not in resp.text agenda.resources.add(resource) resp = app.get('/manage/resource/%s/' % resource.pk, status=200) assert '/manage/agendas/%s/settings' % agenda.pk in resp.text def test_resource_day_view(app, admin_user): today = datetime.date.today() resource = Resource.objects.create(label='Foo bar') agenda = Agenda.objects.create(label='Agenda', kind='meetings') desk = Desk.objects.create(agenda=agenda, label='Desk') meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30) timeperiod = TimePeriod.objects.create( desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0) ) login(app) resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert 'div class="booking' not in resp.text assert 'No bookings this day.' in resp.text agenda.resources.add(resource) resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert 'div class="booking' not in resp.text assert resp.text.count('18 (not included) timeperiod.end_time = datetime.time(18, 30) # end during an hour timeperiod.save() resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert resp.text.count('18 (included) # book some slots for hour, minute in [(10, 30), (14, 0)]: event = Event.objects.create( agenda=agenda, places=1, desk=desk, meeting_type=meetingtype, start_datetime=now().replace(hour=hour, minute=minute), ) event.resources.add(resource) Booking.objects.create(event=event) with CaptureQueriesContext(connection) as ctx: resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert len(ctx.captured_queries) == 7 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 assert 'height: 50%; top: 50%;' 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.objects.create(agenda=agenda, label='Baz', duration=15) resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert resp.text.count('div class="booking') == 2 assert 'hourspan-4' in resp.text # table CSS class # cancel a booking booking = Booking.objects.first() booking.cancel() resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert resp.text.count('div class="booking') == 1 def test_resource_day_view_late_meeting(app, admin_user): today = datetime.date.today() resource = Resource.objects.create(label='Foo bar') agenda = Agenda.objects.create(label='Agenda', kind='meetings') agenda.resources.add(resource) desk = Desk.objects.create(agenda=agenda, label='Desk') MeetingType.objects.create(agenda=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/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert resp.text.count('midnight' not in resp.text assert '11 p.m.' in resp.text def test_resource_invalid_day_view(app, admin_user): resource = Resource.objects.create(label='Foo bar') login(app) resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, 2018, 11, 31), status=302) assert resp.location.endswith('2018/11/30/') def test_resource_day_view_event_outside_timeperiod(app, admin_user): today = datetime.date.today() resource = Resource.objects.create(label='Foo bar') agenda = Agenda.objects.create(label='Agenda', kind='meetings') agenda.resources.add(resource) desk = Desk.objects.create(agenda=agenda, label='New Desk') meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30) login(app) # no time period - no events resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert 'No bookings 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=agenda, places=1, desk=desk, meeting_type=meetingtype, start_datetime=localtime(now()).replace(hour=hour, minute=minute), ) event.resources.add(resource) Booking.objects.create(event=event) # no time period - events are displayed resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.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/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert 'No bookings 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/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day)) assert resp.text.count('div class="booking') == 2 @freezegun.freeze_time('2020-06-15') def test_resource_month_view(app, admin_user): resource = Resource.objects.create(label='Foo bar') agenda = Agenda.objects.create(label='Agenda', kind='meetings') agenda.resources.add(resource) desk = Desk.objects.create(agenda=agenda, label='Desk') meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=20) TimePeriod.objects.create( desk=desk, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0) ) login(app) today = datetime.date(2018, 11, 10) # fixed day resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, today.year, today.month)) assert '
') in (4, 5) def test_resource_month_view_event_outside_timeperiod(app, admin_user): today = datetime.date.today() resource = Resource.objects.create(label='Foo bar') agenda = Agenda.objects.create(label='Agenda', kind='meetings') agenda.resources.add(resource) desk = Desk.objects.create(agenda=agenda, label='Desk') meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30) login(app) # no time period - no events resp = app.get('/manage/resource/%s/%d/%d/' % (resource.pk, today.year, today.month)) assert 'No bookings 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=agenda, places=1, desk=desk, meeting_type=meetingtype, start_datetime=localtime(now()).replace( day=middle_day.day - middle_day.weekday() + 2, hour=9, minute=0 ), ) event.resources.add(resource) Booking.objects.create(event=event) # no time period - events are displayed resp = app.get('/manage/resource/%s/%d/%d/' % (resource.pk, today.year, today.month)) assert resp.text.count('div class="booking') == 2 # events outside time period TimePeriod.objects.create( desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0) ) resp = app.get('/manage/resource/%s/%d/%d/' % (resource.pk, today.year, today.month)) assert resp.text.count('div class="booking') == 2 assert 'Sunday' not in resp.text assert 'Saturday' not in resp.text # bookings are cancelled Booking.objects.update(cancellation_datetime=now()) resp = app.get('/manage/resource/%s/%d/%d/' % (resource.pk, today.year, today.month)) assert resp.text.count('div class="booking') == 0 # create an event on saturday Booking.objects.update(cancellation_datetime=None) # reset event = Event.objects.create( agenda=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 ), ) event.resources.add(resource) Booking.objects.create(event=event) resp = app.get('/manage/resource/%s/%d/%d/' % (resource.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/resource/%s/%d/%d/' % (resource.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/resource/%s/%d/%d/' % (resource.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=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 ), ) event.resources.add(resource) Booking.objects.create(event=event) resp = app.get('/manage/resource/%s/%d/%d/' % (resource.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/resource/%s/%d/%d/' % (resource.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/resource/%s/%d/%d/' % (resource.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_edit_resource(app, admin_user): resource = Resource.objects.create(label='Foo bar') app = login(app) resp = app.get('/manage/resource/%s/' % resource.pk, status=200) resp = resp.click('Edit') resp.form['label'] = 'Foo bar baz' resp.form['slug'] = 'baz' resp = resp.form.submit() assert resp.location.endswith('/manage/resource/%s/' % resource.pk) resource.refresh_from_db() assert resource.label == 'Foo bar baz' assert resource.slug == 'baz' def test_delete_resource(app, admin_user): resource = Resource.objects.create(label='Foo bar') app = login(app) resp = app.get('/manage/resource/%s/' % resource.pk, status=200) resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('/manage/resources/') assert Resource.objects.exists() is False 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 '

Settings' in resp.text assert agenda.minimal_booking_delay == 1 assert agenda.maximal_booking_delay == 56 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') resp = app.get('/manage/', status=200) resp = app.get('/manage/agendas/add/', status=403) def test_options_agenda(app, admin_user): agenda = Agenda(label=u'Foo bar') agenda.save() app = login(app) resp = app.get('/manage/', status=200) resp = resp.click('Foo bar').follow() resp = resp.click('Settings') 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 'has_resources' not in resp.context assert 'Foo baz' in resp.text assert '

Settings' in resp.text 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/edit' % 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/edit' % 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') assert not 'Settings' 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/', status=200) resp = resp.click('Foo bar').follow() resp = resp.click('Settings') 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 '

Settings' in resp.text def test_options_meetings_agenda_num_queries(app, admin_user): agenda = Agenda.objects.create(label='Agenda', kind='meetings') for i in range(0, 10): MeetingType.objects.create(agenda=agenda, label='MT %s' % i) desk = Desk.objects.create(agenda=agenda, label='Desk %s' % i) for weekday in (0, 6): TimePeriod.objects.create( weekday=weekday, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0) ) # exception starts and ends in the past TimePeriodException.objects.create( desk=desk, start_datetime=now() - datetime.timedelta(days=2), end_datetime=now() - datetime.timedelta(days=1), ) if i % 2: # exception starts in the past but ends in the futur TimePeriodException.objects.create( desk=desk, start_datetime=now() - datetime.timedelta(days=1), end_datetime=now() + datetime.timedelta(days=1), ) else: # exception in more than 2 weeks TimePeriodException.objects.create( desk=desk, start_datetime=now() + datetime.timedelta(days=20), end_datetime=now() + datetime.timedelta(days=21), ) app = login(app) with CaptureQueriesContext(connection) as ctx: app.get('/manage/agendas/%s/settings' % agenda.pk) assert len(ctx.captured_queries) == 8 def test_agenda_resources(app, admin_user): agenda = Agenda.objects.create(label=u'Foo bar', kind='events') resource = Resource.objects.create(label='Resource 1') app = login(app) # not for events agenda app.get('/manage/agendas/%s/add-resource/' % agenda.pk, status=404) app.get('/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk), status=404) def test_meetings_agenda_resources(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 'has_resources' in resp.context assert resp.context['has_resources'] is False assert 'Add resource' not in resp.text resource = Resource.objects.create(label='Resource 1') resp = app.get('/manage/agendas/%s/settings' % agenda.pk) assert 'has_resources' in resp.context assert resp.context['has_resources'] is True assert '/manage/resource/%s/' % resource.pk not in resp.text assert '/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk) not in resp.text resp = resp.click('Add resource') assert list(resp.context['form'].fields['resource'].queryset) == [resource] resp.form['resource'] = resource.pk resp = resp.form.submit() assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk) assert list(agenda.resources.all()) == [resource] resp = resp.follow() assert '/manage/resource/%s/' % resource.pk in resp.text assert '/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk) in resp.text resp = resp.click('Add resource') assert list(resp.context['form'].fields['resource'].queryset) == [] resp = app.get('/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk)) resp = resp.form.submit() assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk) assert list(agenda.resources.all()) == [] resp = resp.follow() assert '/manage/resource/%s/' % resource.pk not in resp.text assert '/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk) not in resp.text 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 = resp.click('Foo bar').follow() resp = resp.click('Settings') resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('/manage/') resp = resp.follow() assert not 'Foo bar' 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/', status=200) resp = resp.click('Foo bar').follow() resp = resp.click('Settings') 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/', status=200) resp = resp.click('Foo bar').follow() resp = resp.click('Settings') resp = resp.click('Delete') assert 'This cannot be removed' in resp.text booking.cancellation_datetime = now() booking.save() resp = app.get('/manage/', status=200) resp = resp.click('Foo bar').follow() resp = resp.click('Settings') 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/', status=200) resp = resp.click('Foo bar').follow() resp = resp.click('Settings') 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 = resp.click('Foo bar').follow() resp = resp.click('Settings') 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_event(app, admin_user): agenda = Agenda(label=u'Foo bar') agenda.maximal_booking_delay = 0 agenda.save() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert "This agenda doesn't have any event yet." in resp.text year = now().year + 1 resp = resp.click('New Event') resp.form['start_datetime$date'] = '%s-02-15' % year resp.form['start_datetime$time'] = '17:00' resp.form['places'] = 10 resp = resp.form.submit() resp = resp.follow() event = Event.objects.get(places=10) assert event.publication_date is None assert "This agenda doesn't have any event yet." not in resp.text assert '/manage/agendas/%s/events/%s/' % (agenda.id, event.id) in resp.text assert ('Feb. 15, %s, 5 p.m.' % year) in resp.text assert '10 places' in resp.text resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id) assert resp_datetimes.json['data'][0]['text'] == 'Feb. 15, %s, 5 p.m.' % year assert resp_datetimes.json['data'][0]['datetime'] == '%s-02-15 17:00:00' % year # add with a description resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('New Event') resp.form['start_datetime$date'] = '%s-02-15' % year resp.form['start_datetime$time'] = '18:00' resp.form['publication_date'] = '2020-05-11' resp.form['places'] = 11 resp.form['description'] = 'A description' resp = resp.form.submit() resp = resp.follow() event = Event.objects.get(places=11) assert event.description == 'A description' assert event.publication_date == datetime.date(2020, 5, 11) # add with errors in datetime parts for parts in ( ('', ''), ('invalid', ''), ('', 'invalid'), ('2019-02-24', 'invalid'), ('invalid', '17:00'), ): resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('New Event') resp.form['start_datetime$date'] = parts[0] resp.form['start_datetime$time'] = parts[1] resp.form['places'] = 10 resp = resp.form.submit() assert resp.text.count('This field is required.') == 1 def test_add_event_on_missing_agenda(app, admin_user): app = login(app) app.get('/manage/agendas/%s/add-event' % '999', status=404) def test_add_event_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/agendas/%s/' % agenda.id, status=302) app.get('/manage/agendas/%s/add-event' % agenda.id, status=403) agenda.edit_role = manager_user.groups.all()[0] agenda.save() resp = app.get('/manage/agendas/%s/' % agenda.id).follow() resp = resp.click('Settings') assert '

Settings' in resp.text resp = resp.click('New Event') resp.form['start_datetime$date'] = '2016-02-15' resp.form['start_datetime$time'] = '17:00' resp.form['places'] = 10 resp = resp.form.submit() resp = resp.follow() event = Event.objects.get(places=10) assert "This agenda doesn't have any event yet." not in resp.text assert '/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id) in resp.text assert 'Feb. 15, 2016, 5 p.m.' in resp.text assert '10 places' in resp.text assert event.duration is None assert event.end_datetime is None resp = resp.click('New Event') resp.form['start_datetime$date'] = '2016-02-15' resp.form['start_datetime$time'] = '17:00' resp.form['duration'] = 45 resp.form['places'] = 12 resp = resp.form.submit() resp = resp.follow() event = Event.objects.get(places=12) assert event.duration == 45 assert event.end_datetime == event.start_datetime + datetime.timedelta(minutes=45) def test_edit_event(app, admin_user): agenda = Agenda(label=u'Foo bar') agenda.save() event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=20, agenda=agenda) event.save() assert event.duration is None assert event.end_datetime is None app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('Feb. 15, 2016, 5 p.m.') assert resp.form['start_datetime$date'].value == '2016-02-15' assert resp.form['start_datetime$time'].value == '17:00' assert resp.form['publication_date'].value == '' assert resp.form['duration'].value == '' resp.form['start_datetime$date'] = '2016-02-16' resp.form['start_datetime$time'] = '17:00' resp.form['publication_date'] = '2020-05-11' resp.form['duration'].value = 45 resp.form['places'] = 20 resp = resp.form.submit() resp = resp.follow() assert '/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id) in resp.text assert 'Feb. 16, 2016, 5 p.m.' in resp.text assert '20 places' in resp.text event.refresh_from_db() assert event.publication_date == datetime.date(2020, 5, 11) assert event.duration == 45 assert event.end_datetime == event.start_datetime + datetime.timedelta(minutes=45) def test_edit_missing_event(app, admin_user): app = login(app) app.get('/manage/agendas/999/', status=404) def test_edit_event_as_manager(app, manager_user): agenda = Agenda(label=u'Foo bar') agenda.view_role = manager_user.groups.all()[0] agenda.save() event = Event.objects.create( start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=20, agenda=agenda, publication_date=datetime.date(2020, 5, 11), ) app = login(app, username='manager', password='manager') resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id), status=403) agenda.edit_role = manager_user.groups.all()[0] agenda.save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('Feb. 15, 2016, 5 p.m.') assert resp.form['start_datetime$date'].value == '2016-02-15' assert resp.form['start_datetime$time'].value == '17:00' assert resp.form['publication_date'].value == '2020-05-11' resp.form['start_datetime$date'] = '2016-02-16' resp.form['start_datetime$time'] = '17:00' resp.form['publication_date'] = '' resp.form['places'] = 20 resp = resp.form.submit() resp = resp.follow() assert '/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id) in resp.text assert 'Feb. 16, 2016, 5 p.m.' in resp.text assert '20 places' in resp.text event.refresh_from_db() assert event.publication_date is None def test_booked_places(app, admin_user): agenda = Agenda(label=u'Foo bar') agenda.save() event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda) event.save() Booking(event=event).save() Booking(event=event).save() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert '10 places' in resp.text assert '2 booked places' in resp.text def test_event_classes(app, admin_user): agenda = Agenda(label=u'Foo bar') agenda.save() event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda) event.save() for i in range(2): Booking(event=event).save() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert not 'full' in resp.text assert not 'overbooking' in resp.text for i in range(8): Booking(event=event).save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert 'full' in resp.text assert not 'overbooking' in resp.text Booking(event=event).save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) assert 'full' in resp.text assert 'overbooking' in resp.text def test_delete_event(app, admin_user): agenda = Agenda(label=u'Foo bar') agenda.save() event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda) event.save() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id) assert Event.objects.count() == 0 def test_delete_busy_event(app, admin_user): agenda = Agenda(label=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, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'Are you sure you want to delete this event?' in resp.text booking = Booking(event=event) booking.save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'This cannot be removed' in resp.text booking.cancellation_datetime = now() booking.save() resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') assert 'Are you sure you want to delete this event?' in resp.text # suddenly the booking is no longer cancelled, but the admin clicks on the # delete button. booking.cancellation_datetime = None booking.save() resp = resp.form.submit(status=403) def test_delete_event_as_manager(app, manager_user): agenda = Agenda(label=u'Foo bar') agenda.edit_role = manager_user.groups.all()[0] agenda.save() event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda) event.save() app = login(app, username='manager', password='manager') resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click(href='/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id)) resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id) assert Event.objects.count() == 0 def test_import_events(app, admin_user): agenda = Agenda(label=u'Foo bar') agenda.save() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('Import Events') sample_csv_resp = resp.click('Download sample file') assert sample_csv_resp.content_type == 'text/csv' assert sample_csv_resp.text.startswith('date,time') resp.form['events_csv_file'] = Upload('t.csv', sample_csv_resp.content, 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'xx', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format.' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'xxxx\0\0xxxx', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format.' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-14-16,18:00', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format.' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-14-16,18:00,10', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format. (date/time format' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,blah', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format. (number of places,' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,blah', 'text/csv') resp = resp.form.submit(status=200) assert 'Invalid file format. (number of places in waiting list,' in resp.text resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,' + b'x' * 151, 'text/csv') resp = resp.form.submit(status=200) assert 'Ensure this value has at most 150' in resp.text resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10', 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5', 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', u'2016-09-16,18:00,10,5,éléphant'.encode('utf-8'), 'text/csv' ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 assert Event.objects.all()[0].label == u'éléphant' Event.objects.all().delete() # BOM resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', codecs.BOM_UTF8 + u'2016-09-16,18:00,10,5,éléphant'.encode('utf-8'), 'text/csv' ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 assert Event.objects.all()[0].label == u'éléphant' Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', u'2016-09-16,18:00,10,5,éléphant'.encode('iso-8859-15'), 'text/csv' ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 assert Event.objects.all()[0].label == u'éléphant' Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', u'2016-09-16,18:00,10,5,éléphant'.encode('eucjp'), 'text/csv' ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert Event.objects.all()[0].places == 10 assert Event.objects.all()[0].waiting_list_places == 5 assert Event.objects.all()[0].label == u'\x8f«±l\x8f«±phant' # eucjp interpreted as iso-8859-15 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'date,time,etc.\n' b'2016-09-16,18:00,10,5,bla bla bla\n' b'\n' b'2016-09-19,18:00,10', 'text/csv', ) resp = resp.form.submit(status=302) assert Event.objects.count() == 2 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', '"date"\t"time"\t"etc."\n' '"2016-09-16"\t"18:00"\t"10"\t"5"\t"éléphant"\n' '"2016-09-19"\t"18:00"\t"10"'.encode('iso-8859-15'), 'text/csv', ) resp = resp.form.submit(status=302) assert Event.objects.count() == 2 Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,slug', 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 event = Event.objects.latest('pk') assert event.start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0)) assert event.places == 10 assert event.waiting_list_places == 5 assert event.label == 'label' assert event.slug == 'slug' resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,slug', 'text/csv') resp = resp.form.submit(status=200) assert 'Event with this Agenda and Identifier already exists.' in resp.text assert '__all__' not in resp.text # additional optional attributes Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,slug,,,,', 'text/csv') resp = resp.form.submit(status=302) assert Event.objects.count() == 1 event = Event.objects.get() assert event.description == '' assert event.pricing == '' assert event.url == '' assert event.publication_date is None Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,slug,"description\nfoobar",pricing,url,2016-10-16', 'text/csv' ) resp = resp.form.submit(status=302) assert Event.objects.count() == 1 event = Event.objects.get() assert event.description == 'description\nfoobar' assert event.pricing == 'pricing' assert event.url == 'url' assert event.publication_date == datetime.date(2016, 10, 16) # publication date bad format resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,slug,description,pricing,url,foobar', 'text/csv' ) resp = resp.form.submit(status=200) assert 'Invalid file format. (date format' in resp.text # import events with empty slugs Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,,,pricing,\n' b'2016-09-17,18:00,10,5,label,,,pricing,\n' b'2016-09-18,18:00,10,5,label,,,pricing,\n', 'text/csv', ) resp = resp.form.submit(status=302) assert Event.objects.count() == 3 # forbidden numerical slug Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,1234', 'text/csv') resp = resp.form.submit(status=200) assert 'value cannot be a number' in resp.text assert 'Identifier:' in resp.text # verbose_name is shown, not field name ('slug:') # handle duplicated slug Event.objects.all().delete() resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) resp.form['events_csv_file'] = Upload( 't.csv', b'2016-09-16,18:00,10,5,label,slug\n' b'2016-09-16,18:00,10,5,label,slug\n', 'text/csv', ) resp = resp.form.submit(status=200) assert 'duplicated event identifiers' in resp.text 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 '

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(label=u'Foo bar', kind='meetings') agenda.save() app = login(app) resp = app.get('/manage/agendas/%s/' % agenda.id).follow() resp = resp.click('Settings') 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'] = '60' assert 'deleted' not in resp.form.fields resp = resp.form.submit() meeeting_type = MeetingType.objects.get(agenda=agenda) assert meeeting_type.label == 'Blah' assert meeeting_type.duration == 60 assert meeeting_type.deleted is False resp = resp.follow() assert 'Blah' in resp.text # and edit resp = resp.click('Blah') resp.form['duration'] = '30' assert 'deleted' not in resp.form.fields resp = resp.form.submit() meeeting_type = MeetingType.objects.get(agenda=agenda) assert meeeting_type.duration == 30 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/' % agenda.id).follow() resp = resp.click('Settings') 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/', status=200) resp = resp.click('Foo').follow() resp = resp.click('Settings') 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') 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$') resp.form['weekdays-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 resp = resp.follow() # add a second time period resp = resp.click('Add a time period', index=0) resp.form['weekdays-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['weekdays-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 edit resp = resp.click(u'Wednesday / 10 a.m. → 5 p.m.') assert 'Edit Time Period' in resp.text resp.form['start_time'] = '9:00' resp = resp.form.submit() resp = resp.follow() assert TimePeriod.objects.get(desk=desk, weekday=2).start_time.hour == 9 # and edit with inverted start/end resp2 = resp.click(u'Wednesday / 9 a.m. → 5 p.m.') resp2.form['start_time'] = '18: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['weekdays-4'].checked = True resp.form['weekdays-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_delete_time_period(app, admin_user): agenda = Agenda(label=u'Foo bar', kind='meetings') agenda.save() MeetingType(agenda=agenda, label='Blah').save() desk = Desk.objects.create(agenda=agenda, label='Desk A') time_period = TimePeriod( desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0) ) time_period.save() app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) resp = resp.click('Wednesday') resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id) assert TimePeriod.objects.count() == 0 def test_meetings_agenda_add_time_period_on_missing_desk(app, admin_user): app = login(app) agenda = Agenda(label=u'Foo bar', kind='meetings') agenda.save() MeetingType(agenda=agenda, label='Blah').save() app.get('/manage/agendas/1/desk/777/add-time-period', 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/%d/' % agenda.id).follow() resp = resp.click('Settings') 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_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') MeetingType(agenda=agenda, label='Blah').save() resp = app.get('/manage/agendas/%s/' % agenda.id).follow() resp = resp.click('Settings') 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 resp = resp.click('Desk A', index=1) resp.form['label'] = 'Desk B' resp = resp.form.submit().follow() assert 'Desk A' in resp.text assert 'Desk B' in resp.text 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 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 def test_meetings_agenda_delete_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 agenda = Agenda.objects.get(slug='foo-bar') MeetingType(agenda=agenda, label='Blah').save() resp = app.get('/manage/agendas/%s/' % agenda.id).follow() resp = resp.click('Settings') resp = resp.click('New Desk') resp.form['label'] = 'Desk A' resp = resp.form.submit().follow() assert Desk.objects.count() == 2 resp = resp.click('Desk A', index=0) resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id) assert Desk.objects.count() == 1 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') 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 exception') 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$date'] = tomorrow.strftime('%Y-%m-%d') resp.form['start_datetime$time'] = '08:00' resp.form['end_datetime$date'] = tomorrow.strftime('%Y-%m-%d') resp.form['end_datetime$time'] = '16:00' resp = resp.form.submit().follow() assert TimePeriodException.objects.count() == 1 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$date'] = future.strftime('%Y-%m-%d') resp.form['start_datetime$time'] = '00:00' resp.form['end_datetime$date'] = future.strftime('%Y-%m-%d') resp.form['end_datetime$time'] = '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_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/%d/' % agenda.pk).follow() resp = resp.click('Settings') 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$date'] = '2017-05-22' resp.form['start_datetime$time'] = '08:00' resp.form['end_datetime$date'] = '2017-05-26' resp.form['end_datetime$time'] = '17:30' resp = resp.form.submit().follow() assert 'Exception added. Note: 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/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('Add a time period exception') resp.form['start_datetime$date'] = '2017-05-22' resp.form['start_datetime$time'] = '08:00' resp.form['end_datetime$date'] = '2017-05-26' resp.form['end_datetime$time'] = '17:30' resp = resp.form.submit().follow() assert 'Exception added. Note: one or several bookings exists within this time slot.' not in resp.text assert TimePeriodException.objects.count() == 1 def test_meetings_agenda_add_invalid_time_period_exception(): form = TimePeriodExceptionForm( data={ 'start_datetime$date': '2017-05-26', 'start_datetime$time': '17:30', 'end_datetime$date': '2017-05-22', 'end_datetime$time': '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$date': '2017-05-26', 'start_datetime$time': 'foo', 'end_datetime$date': '2017-05-22', 'end_datetime$time': '08:00', } ) assert form.is_valid() is False # end_datetime is invalid form = TimePeriodExceptionForm( data={ 'start_datetime$date': '2017-05-26', 'start_datetime$time': '17:30', 'end_datetime$date': 'bar', 'end_datetime$time': '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') 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) ) login(app) resp = app.get('/manage/agendas/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('Add a time period exception') today = datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) tomorrow = make_aware(today + datetime.timedelta(days=15)) dt_format = '%Y-%m-%d %H:%M' resp.form['label'] = 'Exception 1' resp.form['start_datetime$date'] = tomorrow.strftime('%Y-%m-%d') resp.form['start_datetime$time'] = '08:00' resp.form['end_datetime$date'] = tomorrow.strftime('%Y-%m-%d') resp.form['end_datetime$time'] = '16:00' resp = resp.form.submit().follow() assert TimePeriodException.objects.count() == 1 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) resp = resp.click(href='/manage/time-period-exceptions/%d/edit' % time_period_exception.id) resp = resp.click('Delete') resp = resp.form.submit().follow() assert TimePeriodException.objects.count() == 0 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_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 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') MeetingType.objects.create(agenda=agenda, label='Foo') login(app) resp = app.get('/manage/agendas/%d/settings' % agenda.pk) assert 'Import exceptions from .ics' in resp.text resp = resp.click('upload') 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 resp = app.get('/manage/agendas/%d/settings' % agenda.pk) resp = resp.click('upload') 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 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 "New Year's Eve" has no start date.' in resp.text 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't contain any events." in resp.text 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/%d/settings' % agenda.pk) resp = resp.click('upload') 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 = TimePeriodExceptionSource.objects.latest('pk') exception = TimePeriodException.objects.latest('pk') 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 'An exception has been imported.' 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/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('upload') 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') MeetingType.objects.create(agenda=agenda, label='Bar') login(app) resp = app.get('/manage/agendas/%d/settings' % agenda.pk) resp = resp.click('upload') 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 exception = TimePeriodException.objects.get(desk=desk) assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1 source = TimePeriodExceptionSource.objects.latest('pk') exception = TimePeriodException.objects.latest('pk') 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('upload') 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 = 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('upload') 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 = 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('upload') 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 = 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('upload') 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 def test_meetings_agenda_delete_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') 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) ) login(app) # import a source resp = app.get('/manage/agendas/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('upload') 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 source1 = TimePeriodExceptionSource.objects.latest('pk') assert source1.timeperiodexception_set.count() == 2 # import another one resp = app.get('/manage/agendas/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('upload') 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() == 4 source2 = TimePeriodExceptionSource.objects.latest('pk') assert source2.timeperiodexception_set.count() == 2 # delete the second one resp = app.get('/manage/agendas/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('upload') resp = resp.click(href='/manage/time-period-exceptions-source/%d/delete' % source2.pk) resp = resp.form.submit().follow() assert TimePeriodException.objects.count() == 2 assert source1.timeperiodexception_set.count() == 2 assert TimePeriodExceptionSource.objects.filter(pk=source2.pk).exists() is False # delete the first one resp = app.get('/manage/agendas/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('upload') resp = resp.click(href='/manage/time-period-exceptions-source/%d/delete' % source1.pk) resp = resp.form.submit().follow() assert TimePeriodException.objects.count() == 0 assert TimePeriodExceptionSource.objects.filter(pk=source1.pk).exists() is False 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') 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) ) 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/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('upload') 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 # replace the source resp = app.get('/manage/agendas/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('upload') resp = resp.click(href='/manage/time-period-exceptions-source/%d/replace' % source.pk) resp.form['ics_newfile'] = Upload('exceptions.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 os.path.exists(old_ics_file_path) is False assert TimePeriodException.objects.count() == 2 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 @mock.patch('chrono.agendas.models.requests.get') def test_meetings_agenda_refresh_time_period_exception_source(mocked_get, 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) ) ics_url_content = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//foo.bar//EN BEGIN:VEVENT DTSTART:20180101 DTEND:20180101 SUMMARY:New Year's Eve END:VEVENT END:VCALENDAR""" login(app) # import a source from an url resp = app.get('/manage/agendas/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('upload') resp.form['ics_url'] = 'http://example.com/foo.ics' mocked_response = mock.Mock() mocked_response.text = ics_url_content mocked_get.return_value = mocked_response 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')) # refresh the source resp = app.get('/manage/agendas/%d/' % agenda.pk).follow() resp = resp.click('Settings') resp = resp.click('upload') mocked_response = mock.Mock() mocked_response.text = ics_url_content mocked_get.return_value = mocked_response resp = resp.click(href='/manage/time-period-exceptions-source/%d/refresh' % source.pk) assert TimePeriodException.objects.count() == 1 assert source.timeperiodexception_set.count() == 1 new_exceptions = list(source.timeperiodexception_set.order_by('pk')) assert exceptions[0].pk != new_exceptions[0].pk 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() meetingtype = MeetingType(agenda=agenda, label='Bar', duration=30) meetingtype.save() login(app) resp = app.get('/manage/agendas/%s/' % agenda.id, status=302) today = datetime.date.today() assert resp.location.endswith('%s/%s/%s/' % (today.year, today.month, today.day)) resp = resp.follow() 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/' % agenda.id, status=302).follow() assert 'No opening hours this day.' not in resp.text assert 'div class="booking' not in resp.text assert resp.text.count('18 (not included) timeperiod.end_time = datetime.time(18, 30) # end during an hour timeperiod.save() resp = app.get('/manage/agendas/%s/' % agenda.id, status=302).follow() assert resp.text.count('18 (included) # check opening hours cells assert '
11 p.m.' in resp.text def test_agenda_invalid_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() meetingtype = MeetingType(agenda=agenda, label='Bar', duration=30) meetingtype.save() 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/') def test_agenda_day_view_event_outside_timeperiod(app, admin_user): 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) 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=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 '
') in (4, 5) def test_agenda_month_view_event_outside_timeperiod(app, admin_user): 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) 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=agenda, places=1, desk=desk, meeting_type=meetingtype, start_datetime=localtime(now()).replace( day=middle_day.day - middle_day.weekday() + 2, hour=9, minute=0 ), ) 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 '