1590 lines
66 KiB
Python
1590 lines
66 KiB
Python
import datetime
|
|
import os
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
import requests
|
|
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.timezone import localtime, make_aware, now
|
|
from webtest import Upload
|
|
|
|
from chrono.agendas.models import (
|
|
Agenda,
|
|
Booking,
|
|
Desk,
|
|
Event,
|
|
MeetingType,
|
|
TimePeriod,
|
|
TimePeriodException,
|
|
TimePeriodExceptionSource,
|
|
UnavailabilityCalendar,
|
|
)
|
|
from chrono.manager.forms import TimePeriodExceptionForm
|
|
from tests.utils import login
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
@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_meetings_agenda_add_time_period_exception(app, admin_user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
|
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
|
desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
|
|
|
|
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=0)
|
|
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='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='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 "New Year's Eve" 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'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
|
|
|
|
|
|
@override_settings(TEMPLATE_VARS={'passerelle_url': 'https://passerelle.publik.love/'})
|
|
@mock.patch('chrono.agendas.models.requests.get')
|
|
def test_agenda_import_time_period_exception_with_remote_ics_templated_url(mocked_get, app, admin_user):
|
|
agenda = Agenda.objects.create(label='New Example', kind='meetings')
|
|
desk = Desk.objects.create(agenda=agenda, label='New Desk')
|
|
login(app)
|
|
|
|
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
|
|
resp.form['ics_url'] = '{{ passerelle_url }}holidays/test/holidays.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
|
|
assert mocked_get.call_args.args[0] == 'https://passerelle.publik.love/holidays/test/holidays.ics'
|
|
|
|
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
|
|
resp.form['ics_url'] = '{{ unknown }}holidays/test/holidays.ics'
|
|
resp = resp.form.submit(status=200)
|
|
assert 'Enter a valid URL.' in resp.text
|
|
|
|
resp.form['ics_url'] = '{{ { }}holidays/test/holidays.ics'
|
|
resp = resp.form.submit(status=200)
|
|
assert 'syntax error' in resp.text
|
|
|
|
|
|
@mock.patch('chrono.agendas.models.requests.get')
|
|
def test_agenda_import_time_period_exception_url_desk_all_desks(mocked_get, app, admin_user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
|
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
|
|
|
# only one desk: no option to apply to all desks
|
|
app = login(app)
|
|
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
|
|
assert 'all_desks' not in resp.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 exceptions are not created for this one
|
|
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
|
|
resp.form['all_desks'] = True
|
|
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 TimePeriodException.objects.filter(desk__slug='desk-a').exists()
|
|
assert TimePeriodException.objects.filter(desk__slug='desk-b').exists()
|
|
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
|
|
|
|
|
|
def test_agenda_import_time_period_exception_file_desk_all_desks(app, admin_user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
|
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
|
|
|
# only one desk: no option to apply to all desks
|
|
app = login(app)
|
|
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
|
|
assert 'all_desks' not in resp.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 exceptions are not created for this one
|
|
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
|
|
resp.form['all_desks'] = True
|
|
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 TimePeriodException.objects.filter(desk__slug='desk-a').exists()
|
|
assert TimePeriodException.objects.filter(desk__slug='desk-b').exists()
|
|
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
|
|
|
|
|
|
@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)
|
|
assert 'all_desks' not 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.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)
|
|
assert 'all_desks' not in resp.form.fields
|
|
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)
|
|
|
|
|
|
@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() + datetime.timedelta(hours=1), places=10, agenda=agenda)
|
|
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
|
|
assert 'Recurrence exceptions' not in resp.text
|
|
|
|
event.recurrence_days = list(range(7))
|
|
event.recurrence_end_date = now() + datetime.timedelta(days=31)
|
|
event.save()
|
|
event.create_all_recurrences()
|
|
|
|
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
|
|
agenda.update_event_recurrences()
|
|
|
|
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
|
|
agenda.update_event_recurrences()
|
|
|
|
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
|
|
assert len(resp.pyquery.find('.event-info')) == 23
|
|
|
|
|
|
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')
|
|
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
|
event = Event.objects.create(
|
|
start_datetime=now(),
|
|
places=10,
|
|
recurrence_days=list(range(7)),
|
|
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 'warningnotice' not 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 'warningnotice' not in resp.text
|
|
|
|
|
|
def test_unavailability_calendar_import_time_period_exception_from_ics(app, admin_user):
|
|
calendar = UnavailabilityCalendar.objects.create(label='Example')
|
|
login(app)
|
|
resp = app.get('/manage/unavailability-calendar/%d/settings' % calendar.pk)
|
|
assert 'Manage unavailabilities from ICS' in resp.text
|
|
resp = resp.click('Manage unavailabilities')
|
|
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(unavailability_calendar=calendar).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(unavailability_calendar=calendar).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/unavailability-calendar/%s/import-unavailabilities/' % calendar.pk)
|
|
resp.form['ics_file'] = Upload('exceptions.ics', ics_with_exceptions, 'text/calendar')
|
|
resp = resp.form.submit(status=302)
|
|
assert TimePeriodException.objects.filter(unavailability_calendar=calendar).count() == 1
|
|
assert TimePeriodExceptionSource.objects.filter(unavailability_calendar=calendar).count() == 1
|
|
source = calendar.timeperiodexceptionsource_set.get()
|
|
exception = calendar.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
|
|
|
|
|
|
@mock.patch('chrono.agendas.models.requests.get')
|
|
def test_unavailability_calendar_import_time_period_exception_with_remote_ics(mocked_get, app, admin_user):
|
|
calendar = UnavailabilityCalendar.objects.create(label='Example')
|
|
login(app)
|
|
resp = app.get('/manage/unavailability-calendar/%s/import-unavailabilities/' % calendar.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(unavailability_calendar=calendar).count() == 1
|
|
assert TimePeriodExceptionSource.objects.filter(unavailability_calendar=calendar).count() == 1
|
|
source = calendar.timeperiodexceptionsource_set.get()
|
|
exception = calendar.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'
|
|
|
|
|
|
def test_unavailability_calendar_delete_time_period_exception_source(app, admin_user):
|
|
calendar = UnavailabilityCalendar.objects.create(label='Example')
|
|
source1 = TimePeriodExceptionSource.objects.create(
|
|
unavailability_calendar=calendar, ics_url='https://example.com/test.ics'
|
|
)
|
|
TimePeriodException.objects.create(
|
|
unavailability_calendar=calendar,
|
|
source=source1,
|
|
start_datetime=now() - datetime.timedelta(days=1),
|
|
end_datetime=now() + datetime.timedelta(days=1),
|
|
)
|
|
source2 = TimePeriodExceptionSource.objects.create(
|
|
unavailability_calendar=calendar, ics_url='https://example.com/test.ics'
|
|
)
|
|
TimePeriodException.objects.create(
|
|
unavailability_calendar=calendar,
|
|
source=source2,
|
|
start_datetime=now() - datetime.timedelta(days=1),
|
|
end_datetime=now() + datetime.timedelta(days=1),
|
|
)
|
|
|
|
login(app)
|
|
resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source2.pk)
|
|
resp = resp.form.submit()
|
|
assert TimePeriodException.objects.count() == 1
|
|
assert TimePeriodExceptionSource.objects.count() == 1
|
|
assert source1.timeperiodexception_set.count() == 1
|
|
assert TimePeriodExceptionSource.objects.filter(pk=source2.pk).exists() is False
|
|
|
|
|
|
def test_unavailability_calendar_replace_time_period_exception_source(app, admin_user, freezer):
|
|
freezer.move_to('2019-12-01')
|
|
calendar = UnavailabilityCalendar.objects.create(label='Example')
|
|
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/unavailability-calendar/%s/import-unavailabilities/' % calendar.pk)
|
|
resp.form['ics_file'] = Upload('exceptions.ics', ics_file_content, 'text/calendar')
|
|
resp = resp.form.submit(status=302).follow()
|
|
assert TimePeriodException.objects.filter(unavailability_calendar=calendar).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/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() == 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_unavailability_calendar_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
|
|
|
|
calendar = UnavailabilityCalendar.objects.create(label='Example')
|
|
|
|
login(app)
|
|
# import a source from an url
|
|
resp = app.get('/manage/unavailability-calendar/%d/settings' % calendar.pk)
|
|
resp = resp.click('Manage unavailabilities')
|
|
resp.form['ics_url'] = 'http://example.com/foo.ics'
|
|
resp = resp.form.submit(status=302).follow()
|
|
assert TimePeriodException.objects.filter(unavailability_calendar=calendar).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/unavailability-calendar/%d/settings' % calendar.pk)
|
|
resp = resp.click('Manage unavailabilities')
|
|
resp = resp.click(href='/manage/time-period-exceptions-source/%d/refresh' % source.pk)
|
|
assert TimePeriodException.objects.count() == 1
|
|
new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
|
|
assert exceptions[0].pk != new_exceptions[0].pk
|