2016-10-03 14:24:47 +02:00
|
|
|
|
import datetime
|
2020-09-15 14:05:38 +02:00
|
|
|
|
import json
|
|
|
|
|
import smtplib
|
2020-02-20 11:46:23 +01:00
|
|
|
|
from unittest import mock
|
2017-10-03 23:15:00 +02:00
|
|
|
|
|
2016-02-13 11:57:57 +01:00
|
|
|
|
import pytest
|
2020-11-19 17:21:00 +01:00
|
|
|
|
import requests
|
2020-07-16 15:12:47 +02:00
|
|
|
|
from django.contrib.auth.models import Group, User
|
2019-12-10 15:28:39 +01:00
|
|
|
|
from django.core.files.base import ContentFile
|
2017-10-03 23:15:00 +02:00
|
|
|
|
from django.core.management import call_command
|
2020-07-16 15:12:47 +02:00
|
|
|
|
from django.db.models import Q
|
2020-02-20 11:46:23 +01:00
|
|
|
|
from django.test import override_settings
|
2019-12-10 15:28:39 +01:00
|
|
|
|
from django.utils.timezone import localtime, make_aware, now
|
2016-03-30 00:51:34 +02:00
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
from chrono.agendas.models import (
|
|
|
|
|
Agenda,
|
2020-07-16 15:12:47 +02:00
|
|
|
|
AgendaNotificationsSettings,
|
2020-09-15 14:05:38 +02:00
|
|
|
|
AgendaReminderSettings,
|
2017-09-03 13:28:50 +02:00
|
|
|
|
Booking,
|
2020-07-24 15:59:49 +02:00
|
|
|
|
Category,
|
2017-11-19 22:10:44 +01:00
|
|
|
|
Desk,
|
2019-12-10 15:28:39 +01:00
|
|
|
|
Event,
|
2020-07-09 12:46:13 +02:00
|
|
|
|
EventCancellationReport,
|
2017-11-19 22:10:44 +01:00
|
|
|
|
ICSError,
|
2019-12-10 15:28:39 +01:00
|
|
|
|
MeetingType,
|
2022-02-22 15:59:27 +01:00
|
|
|
|
Person,
|
2020-05-15 14:32:37 +02:00
|
|
|
|
Resource,
|
2022-02-22 15:59:27 +01:00
|
|
|
|
SharedCustodyAgenda,
|
2022-03-24 17:05:17 +01:00
|
|
|
|
SharedCustodyHolidayRule,
|
2022-02-22 15:59:27 +01:00
|
|
|
|
SharedCustodyPeriod,
|
|
|
|
|
SharedCustodyRule,
|
2020-02-26 18:18:15 +01:00
|
|
|
|
TimePeriod,
|
2019-12-10 15:28:39 +01:00
|
|
|
|
TimePeriodException,
|
2022-03-23 16:46:56 +01:00
|
|
|
|
TimePeriodExceptionGroup,
|
2019-12-10 15:28:39 +01:00
|
|
|
|
TimePeriodExceptionSource,
|
2020-11-04 14:16:32 +01:00
|
|
|
|
UnavailabilityCalendar,
|
2020-02-06 17:49:38 +01:00
|
|
|
|
VirtualMember,
|
2017-11-19 22:10:44 +01:00
|
|
|
|
)
|
2016-02-13 11:57:57 +01:00
|
|
|
|
|
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
ICS_SAMPLE = """BEGIN:VCALENDAR
|
|
|
|
|
VERSION:2.0
|
|
|
|
|
PRODID:-//foo.bar//EN
|
|
|
|
|
BEGIN:VEVENT
|
|
|
|
|
DTSTAMP:20170824T082855Z
|
|
|
|
|
DTSTART:20170831T170800Z
|
|
|
|
|
DTEND:20170831T203400Z
|
|
|
|
|
SEQUENCE:1
|
2020-01-11 12:10:19 +01:00
|
|
|
|
SUMMARY:Événement 1
|
2017-09-03 13:28:50 +02:00
|
|
|
|
END:VEVENT
|
|
|
|
|
BEGIN:VEVENT
|
|
|
|
|
DTSTAMP:20170824T092855Z
|
2017-10-03 23:15:00 +02:00
|
|
|
|
DTSTART:20170830T180800Z
|
|
|
|
|
DTEND:20170831T223400Z
|
2017-09-03 13:28:50 +02:00
|
|
|
|
SEQUENCE:2
|
|
|
|
|
END:VEVENT
|
|
|
|
|
END:VCALENDAR"""
|
|
|
|
|
|
2018-05-07 21:44:38 +02:00
|
|
|
|
ICS_SAMPLE_WITH_DURATION = """BEGIN:VCALENDAR
|
|
|
|
|
VERSION:2.0
|
|
|
|
|
PRODID:-//foo.bar//EN
|
|
|
|
|
BEGIN:VEVENT
|
|
|
|
|
DTSTAMP:20170824T082855Z
|
|
|
|
|
DTSTART:20170831T170800Z
|
|
|
|
|
DURATION:PT3H26M
|
|
|
|
|
SEQUENCE:1
|
|
|
|
|
SUMMARY:Event 1
|
|
|
|
|
END:VEVENT
|
|
|
|
|
BEGIN:VEVENT
|
|
|
|
|
DTSTAMP:20170824T092855Z
|
|
|
|
|
DTSTART:20170830T180800Z
|
|
|
|
|
DURATION:P1D4H26M
|
|
|
|
|
SEQUENCE:2
|
|
|
|
|
SUMMARY:Event 2
|
|
|
|
|
END:VEVENT
|
|
|
|
|
END:VCALENDAR"""
|
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
ICS_SAMPLE_WITH_RECURRENT_EVENT = """BEGIN:VCALENDAR
|
|
|
|
|
VERSION:2.0
|
|
|
|
|
PRODID:-//foo.bar//EN
|
|
|
|
|
BEGIN:VEVENT
|
|
|
|
|
DTSTAMP:20170720T145803Z
|
|
|
|
|
DESCRIPTION:Vacances d'ete
|
|
|
|
|
DTSTART;VALUE=DATE:20180101
|
|
|
|
|
DTEND;VALUE=DATE:20180101
|
|
|
|
|
SUMMARY:reccurent event
|
|
|
|
|
END:VEVENT
|
|
|
|
|
BEGIN:VEVENT
|
2018-04-26 13:27:49 +02:00
|
|
|
|
DTSTAMP:20180824T082855Z
|
2017-09-03 13:28:50 +02:00
|
|
|
|
DTSTART:20180101
|
|
|
|
|
DTEND:20180101
|
2017-10-03 23:15:00 +02:00
|
|
|
|
SUMMARY:New Year's Eve
|
2017-09-03 13:28:50 +02:00
|
|
|
|
RRULE:FREQ=YEARLY
|
|
|
|
|
END:VEVENT
|
|
|
|
|
END:VCALENDAR"""
|
|
|
|
|
|
2018-05-07 21:44:38 +02:00
|
|
|
|
ICS_SAMPLE_WITH_RECURRENT_EVENT_IN_THE_PAST = """BEGIN:VCALENDAR
|
|
|
|
|
VERSION:2.0
|
|
|
|
|
PRODID:-//foo.bar//EN
|
|
|
|
|
BEGIN:VEVENT
|
|
|
|
|
DTSTAMP:20180824T082855Z
|
|
|
|
|
DTSTART:20120101
|
|
|
|
|
DURATION:PT24H
|
|
|
|
|
SUMMARY:New Year's Eve
|
|
|
|
|
RRULE:FREQ=YEARLY
|
|
|
|
|
END:VEVENT
|
|
|
|
|
END:VCALENDAR"""
|
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
ICS_SAMPLE_WITH_NO_EVENTS = """BEGIN:VCALENDAR
|
|
|
|
|
VERSION:2.0
|
|
|
|
|
PRODID:-//foo.bar//EN
|
|
|
|
|
END:VCALENDAR"""
|
|
|
|
|
|
|
|
|
|
INVALID_ICS_SAMPLE = """content
|
|
|
|
|
"""
|
|
|
|
|
|
2019-12-10 15:28:39 +01:00
|
|
|
|
|
2018-10-12 11:06:42 +02:00
|
|
|
|
with open('tests/data/atreal.ics') as f:
|
|
|
|
|
ICS_ATREAL = f.read()
|
|
|
|
|
|
2016-02-13 11:57:57 +01:00
|
|
|
|
|
2022-03-23 16:46:56 +01:00
|
|
|
|
with open('tests/data/holidays.ics') as f:
|
|
|
|
|
ICS_HOLIDAYS = f.read()
|
|
|
|
|
|
|
|
|
|
|
2016-02-13 11:57:57 +01:00
|
|
|
|
def test_slug():
|
|
|
|
|
agenda = Agenda(label='Foo bar')
|
|
|
|
|
agenda.save()
|
|
|
|
|
assert agenda.slug == 'foo-bar'
|
|
|
|
|
|
2019-11-06 15:20:07 +01:00
|
|
|
|
|
2016-02-13 11:57:57 +01:00
|
|
|
|
def test_existing_slug():
|
|
|
|
|
agenda = Agenda(label='Foo bar', slug='bar')
|
|
|
|
|
agenda.save()
|
|
|
|
|
assert agenda.slug == 'bar'
|
|
|
|
|
|
2019-11-06 15:20:07 +01:00
|
|
|
|
|
2016-02-13 11:57:57 +01:00
|
|
|
|
def test_duplicate_slugs():
|
|
|
|
|
agenda = Agenda(label='Foo baz')
|
|
|
|
|
agenda.save()
|
|
|
|
|
assert agenda.slug == 'foo-baz'
|
|
|
|
|
agenda = Agenda(label='Foo baz')
|
|
|
|
|
agenda.save()
|
|
|
|
|
assert agenda.slug == 'foo-baz-1'
|
|
|
|
|
agenda = Agenda(label='Foo baz')
|
|
|
|
|
agenda.save()
|
|
|
|
|
assert agenda.slug == 'foo-baz-2'
|
2016-03-30 00:51:34 +02:00
|
|
|
|
|
2019-11-06 15:20:07 +01:00
|
|
|
|
|
2020-05-15 14:32:37 +02:00
|
|
|
|
def test_resource_slug():
|
|
|
|
|
resource = Resource.objects.create(label='Foo bar')
|
|
|
|
|
assert resource.slug == 'foo-bar'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resource_existing_slug():
|
|
|
|
|
resource = Resource.objects.create(label='Foo bar', slug='bar')
|
|
|
|
|
assert resource.slug == 'bar'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resource_duplicate_slugs():
|
|
|
|
|
resource = Resource.objects.create(label='Foo baz')
|
|
|
|
|
assert resource.slug == 'foo-baz'
|
|
|
|
|
resource = Resource.objects.create(label='Foo baz')
|
|
|
|
|
assert resource.slug == 'foo-baz-1'
|
|
|
|
|
resource = Resource.objects.create(label='Foo baz')
|
|
|
|
|
assert resource.slug == 'foo-baz-2'
|
|
|
|
|
|
|
|
|
|
|
2020-07-24 15:59:49 +02:00
|
|
|
|
def test_category_slug():
|
|
|
|
|
category = Category.objects.create(label='Foo bar')
|
|
|
|
|
assert category.slug == 'foo-bar'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_category_existing_slug():
|
|
|
|
|
category = Category.objects.create(label='Foo bar', slug='bar')
|
|
|
|
|
assert category.slug == 'bar'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_category_duplicate_slugs():
|
|
|
|
|
category = Category.objects.create(label='Foo baz')
|
|
|
|
|
assert category.slug == 'foo-baz'
|
|
|
|
|
category = Category.objects.create(label='Foo baz')
|
|
|
|
|
assert category.slug == 'foo-baz-1'
|
|
|
|
|
category = Category.objects.create(label='Foo baz')
|
|
|
|
|
assert category.slug == 'foo-baz-2'
|
|
|
|
|
|
|
|
|
|
|
2022-03-25 15:00:31 +01:00
|
|
|
|
def test_agenda_minimal_booking_delay(freezer):
|
|
|
|
|
freezer.move_to('2021-07-09')
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', minimal_booking_delay=4)
|
|
|
|
|
assert agenda.min_booking_datetime == make_aware(datetime.datetime(2021, 7, 13, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2022-03-18')
|
|
|
|
|
del agenda.min_booking_datetime
|
|
|
|
|
assert agenda.min_booking_datetime == make_aware(datetime.datetime(2022, 3, 22, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
# DST change on sunday
|
|
|
|
|
freezer.move_to('2022-03-25')
|
|
|
|
|
del agenda.min_booking_datetime
|
|
|
|
|
assert agenda.min_booking_datetime == make_aware(datetime.datetime(2022, 3, 29, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
# DST change on sunday
|
|
|
|
|
freezer.move_to('2021-10-29')
|
|
|
|
|
del agenda.min_booking_datetime
|
|
|
|
|
assert agenda.min_booking_datetime == make_aware(datetime.datetime(2021, 11, 2, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_agenda_minimal_booking_delay_in_working_days(settings, freezer):
|
|
|
|
|
freezer.move_to('2021-07-09')
|
2021-07-05 16:04:30 +02:00
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', minimal_booking_delay=1)
|
|
|
|
|
|
|
|
|
|
settings.WORKING_DAY_CALENDAR = None
|
|
|
|
|
agenda.minimal_booking_delay_in_working_days = True
|
|
|
|
|
agenda.save()
|
|
|
|
|
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 10)
|
|
|
|
|
agenda.minimal_booking_delay_in_working_days = False
|
|
|
|
|
agenda.save()
|
2021-10-29 15:22:04 +02:00
|
|
|
|
del agenda.min_booking_datetime
|
2021-07-05 16:04:30 +02:00
|
|
|
|
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 10)
|
|
|
|
|
|
|
|
|
|
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
|
|
|
|
|
agenda.minimal_booking_delay_in_working_days = True
|
|
|
|
|
agenda.save()
|
2021-10-29 15:22:04 +02:00
|
|
|
|
del agenda.min_booking_datetime
|
2021-07-05 16:04:30 +02:00
|
|
|
|
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 12)
|
|
|
|
|
agenda.minimal_booking_delay_in_working_days = False
|
|
|
|
|
agenda.save()
|
2021-10-29 15:22:04 +02:00
|
|
|
|
del agenda.min_booking_datetime
|
2021-07-05 16:04:30 +02:00
|
|
|
|
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 10)
|
|
|
|
|
|
2022-03-25 15:00:31 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', minimal_booking_delay=2)
|
|
|
|
|
agenda.minimal_booking_delay_in_working_days = True
|
|
|
|
|
agenda.save()
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2022-03-18')
|
|
|
|
|
assert agenda.min_booking_datetime == make_aware(datetime.datetime(2022, 3, 22, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
# DST change on sunday
|
|
|
|
|
freezer.move_to('2022-03-25')
|
|
|
|
|
del agenda.min_booking_datetime
|
|
|
|
|
assert agenda.min_booking_datetime == make_aware(datetime.datetime(2022, 3, 29, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
# DST change on sunday, and first of november is off
|
|
|
|
|
freezer.move_to('2021-10-29')
|
|
|
|
|
del agenda.min_booking_datetime
|
|
|
|
|
assert agenda.min_booking_datetime == make_aware(datetime.datetime(2021, 11, 3, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_agenda_maximal_booking_delay(freezer):
|
|
|
|
|
freezer.move_to('2021-07-09')
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', maximal_booking_delay=4)
|
|
|
|
|
assert agenda.max_booking_datetime == make_aware(datetime.datetime(2021, 7, 13, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2022-03-18')
|
|
|
|
|
del agenda.max_booking_datetime
|
|
|
|
|
assert agenda.max_booking_datetime == make_aware(datetime.datetime(2022, 3, 22, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
# DST change on sunday
|
|
|
|
|
freezer.move_to('2022-03-25')
|
|
|
|
|
del agenda.max_booking_datetime
|
|
|
|
|
assert agenda.max_booking_datetime == make_aware(datetime.datetime(2022, 3, 29, 0, 0, 0))
|
|
|
|
|
|
|
|
|
|
# DST change on sunday
|
|
|
|
|
freezer.move_to('2021-10-29')
|
|
|
|
|
del agenda.max_booking_datetime
|
|
|
|
|
assert agenda.max_booking_datetime == make_aware(datetime.datetime(2021, 11, 2, 0, 0, 0))
|
|
|
|
|
|
2021-07-05 16:04:30 +02:00
|
|
|
|
|
2021-02-02 11:54:34 +01:00
|
|
|
|
@pytest.mark.parametrize('with_prefetch', [True, False])
|
|
|
|
|
def test_agenda_is_available_for_simple_management(settings, with_prefetch):
|
2021-01-26 09:58:28 +01:00
|
|
|
|
settings.EXCEPTIONS_SOURCES = {
|
|
|
|
|
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-02 11:54:34 +01:00
|
|
|
|
def check_is_available(result, use_prefetch=True):
|
|
|
|
|
agenda = Agenda.objects.get()
|
|
|
|
|
if with_prefetch and use_prefetch:
|
2022-10-24 17:53:22 +02:00
|
|
|
|
agenda.prefetch_desks_and_exceptions(with_sources=True, min_date=now())
|
2021-02-02 11:54:34 +01:00
|
|
|
|
assert agenda.is_available_for_simple_management() == result
|
|
|
|
|
|
2021-01-26 09:58:28 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
|
|
|
|
|
# no desks
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
|
|
|
|
|
# check kind
|
|
|
|
|
agenda.kind = 'events'
|
|
|
|
|
agenda.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False, use_prefetch=False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
agenda.kind = 'virtual'
|
|
|
|
|
agenda.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False, use_prefetch=False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
|
|
|
|
|
# only one desk
|
|
|
|
|
agenda.kind = 'meetings'
|
|
|
|
|
agenda.save()
|
|
|
|
|
desk = Desk.objects.create(label='Desk', agenda=agenda)
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
|
|
|
|
|
# create some related data for this desk
|
|
|
|
|
time_period = TimePeriod.objects.create(
|
|
|
|
|
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
|
|
|
|
)
|
|
|
|
|
desk.import_timeperiod_exceptions_from_settings(enable=True)
|
|
|
|
|
source1 = desk.timeperiodexceptionsource_set.get(settings_slug='holidays')
|
|
|
|
|
source2 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='http://example.com/sample.ics')
|
|
|
|
|
source3 = TimePeriodExceptionSource.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
ics_filename='sample.ics',
|
|
|
|
|
ics_file=ContentFile(ICS_SAMPLE_WITH_DURATION, name='sample.ics'),
|
|
|
|
|
)
|
|
|
|
|
date_now = now()
|
|
|
|
|
exception = TimePeriodException.objects.create(
|
|
|
|
|
label='Exception',
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=date_now + datetime.timedelta(days=1),
|
|
|
|
|
end_datetime=date_now + datetime.timedelta(days=2),
|
|
|
|
|
)
|
|
|
|
|
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
unavailability_calendar.desks.add(desk)
|
|
|
|
|
unavailability_calendar2 = UnavailabilityCalendar.objects.create(label='Calendar 2')
|
|
|
|
|
|
|
|
|
|
# still ok
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
|
|
|
|
|
# duplicate the desk twice
|
|
|
|
|
desk2 = desk.duplicate()
|
|
|
|
|
desk.duplicate()
|
|
|
|
|
|
|
|
|
|
# still ok
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
|
|
|
|
|
# changes on time periods
|
|
|
|
|
for _desk in [desk, desk2]:
|
|
|
|
|
time_period2 = TimePeriod.objects.create(
|
|
|
|
|
weekday=2, desk=_desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
|
|
|
|
)
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
time_period2.delete()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
time_period.weekday = 2
|
|
|
|
|
time_period.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
time_period.weekday = 1
|
|
|
|
|
time_period.start_time = datetime.time(10, 1)
|
|
|
|
|
time_period.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
time_period.start_time = datetime.time(10, 0)
|
|
|
|
|
time_period.end_time = datetime.time(12, 1)
|
|
|
|
|
time_period.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
time_period.end_time = datetime.time(12, 0)
|
|
|
|
|
time_period.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
|
|
|
|
|
# changes on exceptions
|
|
|
|
|
for _desk in [desk, desk2]:
|
|
|
|
|
exception2 = TimePeriodException.objects.create(
|
|
|
|
|
label='Exception',
|
|
|
|
|
desk=_desk,
|
|
|
|
|
start_datetime=date_now + datetime.timedelta(days=3),
|
|
|
|
|
end_datetime=date_now + datetime.timedelta(days=4),
|
|
|
|
|
)
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
exception2.delete()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
exception.label = 'Exception blah'
|
|
|
|
|
exception.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
exception.label = 'Exception'
|
|
|
|
|
exception.start_datetime = date_now + datetime.timedelta(days=3)
|
|
|
|
|
exception.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
exception.start_datetime = date_now + datetime.timedelta(days=1)
|
|
|
|
|
exception.end_datetime = date_now + datetime.timedelta(days=1)
|
|
|
|
|
exception.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
exception.end_datetime = date_now + datetime.timedelta(days=2)
|
|
|
|
|
exception.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
# exceptions from source are not checked
|
|
|
|
|
exception3 = TimePeriodException.objects.create(
|
|
|
|
|
label='Exception',
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=date_now + datetime.timedelta(days=3),
|
|
|
|
|
end_datetime=date_now + datetime.timedelta(days=4),
|
|
|
|
|
source=source2,
|
|
|
|
|
)
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
exception3.delete()
|
|
|
|
|
|
|
|
|
|
# changes on sources - from settings
|
|
|
|
|
for _desk in [desk, desk2]:
|
|
|
|
|
source = TimePeriodExceptionSource.objects.create(
|
|
|
|
|
desk=_desk, settings_slug='holidays-bis', enabled=True
|
|
|
|
|
)
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source.delete()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source1.enabled = False
|
|
|
|
|
source1.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source1.enabled = True
|
|
|
|
|
source1.settings_slug = 'holidays-bis'
|
|
|
|
|
source1.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source1.settings_slug = 'holidays'
|
|
|
|
|
source1.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
|
|
|
|
|
# changes on sources - from url
|
|
|
|
|
for _desk in [desk, desk2]:
|
|
|
|
|
source = TimePeriodExceptionSource.objects.create(
|
|
|
|
|
desk=_desk, ics_url='http://example.com/sample-bis.ics'
|
|
|
|
|
)
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source.delete()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source2.ics_url = 'http://example.com/sample-bis.ics'
|
|
|
|
|
source2.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source2.ics_url = 'http://example.com/sample.ics'
|
|
|
|
|
source2.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
|
|
|
|
|
# changes on sources - from file
|
|
|
|
|
for _desk in [desk, desk2]:
|
|
|
|
|
source = TimePeriodExceptionSource.objects.create(
|
|
|
|
|
desk=_desk,
|
|
|
|
|
ics_filename='sample-bis.ics',
|
|
|
|
|
ics_file=ContentFile(ICS_SAMPLE, name='sample-bis.ics'),
|
|
|
|
|
)
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source.delete()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source3.ics_filename = 'sample-bis.ics'
|
|
|
|
|
source3.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
source3.ics_filename = 'sample.ics'
|
|
|
|
|
source3.save()
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
# ics_file content is not checked
|
|
|
|
|
|
|
|
|
|
# changes on unavailability calendars
|
|
|
|
|
for _desk in [desk, desk2]:
|
|
|
|
|
unavailability_calendar2.desks.add(_desk)
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(False)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
unavailability_calendar2.desks.remove(_desk)
|
2021-02-02 11:54:34 +01:00
|
|
|
|
check_is_available(True)
|
2021-01-26 09:58:28 +01:00
|
|
|
|
|
|
|
|
|
|
2020-06-23 15:15:26 +02:00
|
|
|
|
def test_event_slug():
|
|
|
|
|
other_agenda = Agenda.objects.create(label='Foo bar')
|
|
|
|
|
Event.objects.create(agenda=other_agenda, places=42, start_datetime=now(), slug='foo-bar')
|
|
|
|
|
|
|
|
|
|
agenda = Agenda.objects.create(label='Foo baz')
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now(), label='Foo bar')
|
|
|
|
|
assert event.slug == 'foo-bar'
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now(), label='Foo bar')
|
|
|
|
|
assert event.slug == 'foo-bar-1'
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now(), label='Foo bar')
|
|
|
|
|
assert event.slug == 'foo-bar-2'
|
|
|
|
|
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now())
|
|
|
|
|
assert event.slug == 'foo-baz-event'
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now())
|
|
|
|
|
assert event.slug == 'foo-baz-event-1'
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now())
|
|
|
|
|
assert event.slug == 'foo-baz-event-2'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_event_existing_slug():
|
|
|
|
|
other_agenda = Agenda.objects.create(label='Foo bar')
|
|
|
|
|
Event.objects.create(agenda=other_agenda, places=42, start_datetime=now(), slug='bar')
|
|
|
|
|
|
|
|
|
|
agenda = Agenda.objects.create(label='Foo baz')
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now(), label='Foo bar', slug='bar')
|
|
|
|
|
assert event.slug == 'bar'
|
|
|
|
|
|
|
|
|
|
|
2016-03-30 00:51:34 +02:00
|
|
|
|
def test_event_manager():
|
|
|
|
|
agenda = Agenda(label='Foo baz')
|
|
|
|
|
agenda.save()
|
|
|
|
|
event = Event(start_datetime=now(), places=10, agenda=agenda)
|
|
|
|
|
event.save()
|
|
|
|
|
booking = Booking(event=event)
|
|
|
|
|
booking.save()
|
|
|
|
|
assert Event.objects.all()[0].booked_places == 1
|
|
|
|
|
booking.cancellation_datetime = now()
|
|
|
|
|
booking.save()
|
|
|
|
|
assert Event.objects.all()[0].booked_places == 0
|
2016-10-03 14:24:47 +02:00
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2020-05-12 16:57:11 +02:00
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
'start_days, start_minutes, min_delay, max_delay, pub_days, expected',
|
|
|
|
|
[
|
|
|
|
|
# no delay
|
|
|
|
|
(10, 0, 0, 0, None, True),
|
2021-10-08 15:48:14 +02:00
|
|
|
|
# test publication_datetime
|
2020-05-12 16:57:11 +02:00
|
|
|
|
(10, 0, 0, 0, 1, False),
|
|
|
|
|
(10, 0, 0, 0, 0, True),
|
|
|
|
|
# test min and max delays
|
|
|
|
|
(10, 0, 20, 0, None, False),
|
|
|
|
|
(10, 0, 1, 5, None, False),
|
|
|
|
|
(10, 0, 1, 20, None, True),
|
|
|
|
|
# special case for events that happens today
|
|
|
|
|
(0, 10, 0, 20, None, True),
|
|
|
|
|
(0, -10, 0, 20, None, False),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_event_bookable_period(start_days, start_minutes, min_delay, max_delay, pub_days, expected):
|
|
|
|
|
agenda = Agenda.objects.create(
|
|
|
|
|
label='Foo bar', minimal_booking_delay=min_delay, maximal_booking_delay=max_delay
|
|
|
|
|
)
|
|
|
|
|
event = Event.objects.create(
|
2020-11-12 15:05:07 +01:00
|
|
|
|
start_datetime=localtime() + datetime.timedelta(days=start_days, minutes=start_minutes),
|
2021-10-08 15:48:14 +02:00
|
|
|
|
publication_datetime=(localtime() + datetime.timedelta(days=pub_days)) if pub_days else None,
|
2020-05-12 16:57:11 +02:00
|
|
|
|
places=10,
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
)
|
|
|
|
|
assert event.in_bookable_period() == expected
|
2017-01-20 10:35:44 +01:00
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2016-10-28 19:06:51 +02:00
|
|
|
|
def test_meeting_type_slugs():
|
|
|
|
|
agenda1 = Agenda(label='Foo bar')
|
|
|
|
|
agenda1.save()
|
|
|
|
|
agenda2 = Agenda(label='Foo bar second')
|
|
|
|
|
agenda2.save()
|
|
|
|
|
|
|
|
|
|
meeting_type1 = MeetingType(agenda=agenda1, label='Baz')
|
|
|
|
|
meeting_type1.save()
|
|
|
|
|
assert meeting_type1.slug == 'baz'
|
|
|
|
|
|
|
|
|
|
meeting_type2 = MeetingType(agenda=agenda1, label='Baz')
|
|
|
|
|
meeting_type2.save()
|
|
|
|
|
assert meeting_type2.slug == 'baz-1'
|
|
|
|
|
|
|
|
|
|
meeting_type3 = MeetingType(agenda=agenda2, label='Baz')
|
|
|
|
|
meeting_type3.save()
|
|
|
|
|
assert meeting_type3.slug == 'baz'
|
2017-09-03 13:28:50 +02:00
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
def test_timeperiodexception_creation_from_ics():
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 1 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 1 desk', agenda=agenda)
|
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='sample.ics', ics_file=ContentFile(ICS_SAMPLE, name='sample.ics')
|
2019-12-10 15:28:39 +01:00
|
|
|
|
)
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
2017-09-03 13:28:50 +02:00
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
def test_timeperiodexception_creation_from_ics_without_startdt():
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 2 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 2 desk', agenda=agenda)
|
2017-09-03 13:28:50 +02:00
|
|
|
|
lines = []
|
|
|
|
|
# remove start datetimes from ics
|
|
|
|
|
for line in ICS_SAMPLE.splitlines():
|
|
|
|
|
if line.startswith('DTSTART:'):
|
|
|
|
|
continue
|
|
|
|
|
lines.append(line)
|
2019-12-10 15:28:39 +01:00
|
|
|
|
ics_sample = ContentFile("\n".join(lines), name='sample.ics')
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(ics_filename='sample.ics', ics_file=ics_sample)
|
2017-09-03 13:28:50 +02:00
|
|
|
|
with pytest.raises(ICSError) as e:
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source._check_ics_content()
|
2021-07-09 17:50:07 +02:00
|
|
|
|
assert str(e.value) == 'Event "Événement 1" has no start date.'
|
2017-09-03 13:28:50 +02:00
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
def test_timeperiodexception_creation_from_ics_without_enddt():
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 3 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 3 desk', agenda=agenda)
|
2017-09-03 13:28:50 +02:00
|
|
|
|
lines = []
|
|
|
|
|
# remove end datetimes from ics
|
|
|
|
|
for line in ICS_SAMPLE.splitlines():
|
|
|
|
|
if line.startswith('DTEND:'):
|
|
|
|
|
continue
|
|
|
|
|
lines.append(line)
|
2019-12-10 15:28:39 +01:00
|
|
|
|
ics_sample = ContentFile("\n".join(lines), name='sample.ics')
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(ics_filename='sample.ics', ics_file=ics_sample)
|
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
2017-09-03 13:28:50 +02:00
|
|
|
|
for exception in TimePeriodException.objects.filter(desk=desk):
|
|
|
|
|
end_time = localtime(exception.end_datetime).time()
|
|
|
|
|
assert end_time == datetime.time(23, 59, 59, 999999)
|
|
|
|
|
|
2018-04-26 13:27:49 +02:00
|
|
|
|
|
|
|
|
|
@pytest.mark.freeze_time('2017-12-01')
|
2017-09-03 13:28:50 +02:00
|
|
|
|
def test_timeperiodexception_creation_from_ics_with_recurrences():
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 4 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 4 desk', agenda=agenda)
|
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='sample.ics', ics_file=ContentFile(ICS_SAMPLE_WITH_RECURRENT_EVENT, name='sample.ics')
|
2018-04-26 13:27:49 +02:00
|
|
|
|
)
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
2019-12-10 15:28:39 +01:00
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk).count() == 3
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
|
|
|
|
|
def test_timeexception_creation_from_ics_with_dates():
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 5 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 5 desk', agenda=agenda)
|
2017-09-03 13:28:50 +02:00
|
|
|
|
lines = []
|
|
|
|
|
# remove end datetimes from ics
|
|
|
|
|
for line in ICS_SAMPLE_WITH_RECURRENT_EVENT.splitlines():
|
|
|
|
|
if line.startswith('RRULE:'):
|
|
|
|
|
continue
|
|
|
|
|
lines.append(line)
|
2019-12-10 15:28:39 +01:00
|
|
|
|
ics_sample = ContentFile("\n".join(lines), name='sample.ics')
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(ics_filename='sample.ics', ics_file=ics_sample)
|
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
2017-09-03 13:28:50 +02:00
|
|
|
|
for exception in TimePeriodException.objects.filter(desk=desk):
|
2018-03-25 11:26:47 +02:00
|
|
|
|
assert localtime(exception.start_datetime) == make_aware(datetime.datetime(2018, 1, 1, 0, 0))
|
|
|
|
|
assert localtime(exception.end_datetime) == make_aware(datetime.datetime(2018, 1, 1, 0, 0))
|
2017-09-03 13:28:50 +02:00
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
def test_timeexception_create_from_invalid_ics():
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 6 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 6 desk', agenda=agenda)
|
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='sample.ics', ics_file=ContentFile(INVALID_ICS_SAMPLE, name='sample.ics')
|
|
|
|
|
)
|
2017-09-03 13:28:50 +02:00
|
|
|
|
with pytest.raises(ICSError) as e:
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source._check_ics_content()
|
2017-09-03 13:28:50 +02:00
|
|
|
|
assert str(e.value) == 'File format is invalid.'
|
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-09-03 13:28:50 +02:00
|
|
|
|
def test_timeexception_create_from_ics_with_no_events():
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 7 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 7 desk', agenda=agenda)
|
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='sample.ics', ics_file=ContentFile(ICS_SAMPLE_WITH_NO_EVENTS, name='sample.ics')
|
|
|
|
|
)
|
2017-09-03 13:28:50 +02:00
|
|
|
|
with pytest.raises(ICSError) as e:
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source._check_ics_content()
|
2017-09-03 13:28:50 +02:00
|
|
|
|
assert str(e.value) == "The file doesn't contain any events."
|
2017-10-03 23:15:00 +02:00
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-10-03 23:15:00 +02:00
|
|
|
|
@mock.patch('chrono.agendas.models.requests.get')
|
|
|
|
|
def test_timeperiodexception_creation_from_remote_ics(mocked_get):
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 8 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 8 desk', agenda=agenda)
|
2017-10-03 23:15:00 +02:00
|
|
|
|
mocked_response = mock.Mock()
|
|
|
|
|
mocked_response.text = ICS_SAMPLE
|
|
|
|
|
mocked_get.return_value = mocked_response
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(ics_url='http://example.com/sample.ics')
|
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
2020-01-11 12:10:19 +01:00
|
|
|
|
assert 'Événement 1' in [x.label for x in desk.timeperiodexception_set.all()]
|
2017-10-03 23:15:00 +02:00
|
|
|
|
|
|
|
|
|
mocked_response.text = ICS_SAMPLE_WITH_NO_EVENTS
|
|
|
|
|
mocked_get.return_value = mocked_response
|
2019-12-10 15:28:39 +01:00
|
|
|
|
with pytest.raises(ICSError) as e:
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source._check_ics_content()
|
2019-12-10 15:28:39 +01:00
|
|
|
|
assert str(e.value) == "The file doesn't contain any events."
|
2017-10-03 23:15:00 +02:00
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2020-01-11 12:10:19 +01:00
|
|
|
|
@mock.patch('chrono.agendas.models.requests.get')
|
|
|
|
|
def test_timeperiodexception_remote_ics_encoding(mocked_get):
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 8 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 8 desk', agenda=agenda)
|
2020-01-11 12:10:19 +01:00
|
|
|
|
mocked_response = mock.Mock()
|
|
|
|
|
mocked_response.content = ICS_SAMPLE.encode('iso-8859-15')
|
|
|
|
|
mocked_response.text = ICS_SAMPLE
|
|
|
|
|
mocked_get.return_value = mocked_response
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(ics_url='http://example.com/sample.ics')
|
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
2020-01-11 12:10:19 +01:00
|
|
|
|
assert 'Événement 1' in [x.label for x in desk.timeperiodexception_set.all()]
|
|
|
|
|
|
|
|
|
|
|
2017-10-03 23:15:00 +02:00
|
|
|
|
@mock.patch('chrono.agendas.models.requests.get')
|
|
|
|
|
def test_timeperiodexception_creation_from_unreachable_remote_ics(mocked_get):
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 9 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 9 desk', agenda=agenda)
|
2017-10-03 23:15:00 +02:00
|
|
|
|
mocked_response = mock.Mock()
|
|
|
|
|
mocked_response.text = ICS_SAMPLE
|
|
|
|
|
mocked_get.return_value = mocked_response
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-10-03 23:15:00 +02:00
|
|
|
|
def mocked_requests_connection_error(*args, **kwargs):
|
|
|
|
|
raise requests.ConnectionError('unreachable')
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(ics_url='http://example.com/sample.ics')
|
2017-10-03 23:15:00 +02:00
|
|
|
|
mocked_get.side_effect = mocked_requests_connection_error
|
|
|
|
|
with pytest.raises(ICSError) as e:
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source._check_ics_content()
|
2019-05-18 08:20:22 +02:00
|
|
|
|
assert str(e.value) == "Failed to retrieve remote calendar (http://example.com/sample.ics, unreachable)."
|
2017-10-03 23:15:00 +02:00
|
|
|
|
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-10-03 23:15:00 +02:00
|
|
|
|
@mock.patch('chrono.agendas.models.requests.get')
|
|
|
|
|
def test_timeperiodexception_creation_from_forbidden_remote_ics(mocked_get):
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 10 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 10 desk', agenda=agenda)
|
2017-10-03 23:15:00 +02:00
|
|
|
|
mocked_response = mock.Mock()
|
|
|
|
|
mocked_response.status_code = 403
|
|
|
|
|
mocked_get.return_value = mocked_response
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-10-03 23:15:00 +02:00
|
|
|
|
def mocked_requests_http_forbidden_error(*args, **kwargs):
|
|
|
|
|
raise requests.HTTPError(response=mocked_response)
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(ics_url='http://example.com/sample.ics')
|
2017-10-03 23:15:00 +02:00
|
|
|
|
mocked_get.side_effect = mocked_requests_http_forbidden_error
|
|
|
|
|
with pytest.raises(ICSError) as e:
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source._check_ics_content()
|
2019-05-18 08:20:22 +02:00
|
|
|
|
assert (
|
|
|
|
|
str(e.value) == "Failed to retrieve remote calendar (http://example.com/sample.ics, HTTP error 403)."
|
2019-12-16 16:21:24 +01:00
|
|
|
|
)
|
|
|
|
|
|
2017-10-03 23:15:00 +02:00
|
|
|
|
|
|
|
|
|
@mock.patch('chrono.agendas.models.requests.get')
|
|
|
|
|
def test_sync_desks_timeperiod_exceptions_from_ics(mocked_get, capsys):
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 11 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 11 desk', agenda=agenda)
|
2020-01-28 15:08:24 +01:00
|
|
|
|
source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='http://example.com/sample.ics')
|
2017-10-03 23:15:00 +02:00
|
|
|
|
mocked_response = mock.Mock()
|
|
|
|
|
mocked_response.status_code = 403
|
|
|
|
|
mocked_get.return_value = mocked_response
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-10-03 23:15:00 +02:00
|
|
|
|
def mocked_requests_http_forbidden_error(*args, **kwargs):
|
|
|
|
|
raise requests.HTTPError(response=mocked_response)
|
2019-12-16 16:21:24 +01:00
|
|
|
|
|
2017-10-03 23:15:00 +02:00
|
|
|
|
mocked_get.side_effect = mocked_requests_http_forbidden_error
|
|
|
|
|
call_command('sync_desks_timeperiod_exceptions')
|
2021-07-09 15:41:40 +02:00
|
|
|
|
err = capsys.readouterr()[1]
|
2019-05-18 08:20:22 +02:00
|
|
|
|
assert (
|
|
|
|
|
err
|
|
|
|
|
== 'unable to create timeperiod exceptions for "Test 11 desk": Failed to retrieve remote calendar (http://example.com/sample.ics, HTTP error 403).\n'
|
2019-12-16 16:21:24 +01:00
|
|
|
|
)
|
|
|
|
|
|
2020-01-28 15:08:24 +01:00
|
|
|
|
assert source.ics_url is not None
|
|
|
|
|
assert source.ics_filename is None
|
|
|
|
|
assert source.ics_file.name is None
|
|
|
|
|
with mock.patch(
|
2021-02-09 11:35:38 +01:00
|
|
|
|
'chrono.agendas.models.TimePeriodExceptionSource.refresh_timeperiod_exceptions_from_ics'
|
|
|
|
|
) as refresh:
|
|
|
|
|
call_command('sync_desks_timeperiod_exceptions')
|
|
|
|
|
assert refresh.call_args_list == [mock.call()]
|
2020-01-28 15:08:24 +01:00
|
|
|
|
|
|
|
|
|
source.ics_url = None
|
|
|
|
|
source.ics_filename = 'sample.ics'
|
|
|
|
|
source.ics_file = ContentFile(ICS_SAMPLE_WITH_DURATION, name='sample.ics')
|
|
|
|
|
source.save()
|
|
|
|
|
with mock.patch(
|
2021-02-09 11:35:38 +01:00
|
|
|
|
'chrono.agendas.models.TimePeriodExceptionSource.refresh_timeperiod_exceptions_from_ics'
|
|
|
|
|
) as refresh:
|
|
|
|
|
call_command('sync_desks_timeperiod_exceptions')
|
|
|
|
|
assert refresh.call_args_list == [mock.call()]
|
2020-01-28 15:08:24 +01:00
|
|
|
|
|
2020-03-06 14:12:14 +01:00
|
|
|
|
TimePeriodExceptionSource.objects.update(ics_file='')
|
|
|
|
|
with mock.patch(
|
2021-02-09 11:35:38 +01:00
|
|
|
|
'chrono.agendas.models.TimePeriodExceptionSource.refresh_timeperiod_exceptions_from_ics'
|
|
|
|
|
) as refresh:
|
|
|
|
|
call_command('sync_desks_timeperiod_exceptions')
|
|
|
|
|
assert refresh.call_args_list == []
|
2020-03-06 14:12:14 +01:00
|
|
|
|
|
|
|
|
|
TimePeriodExceptionSource.objects.update(ics_file=None)
|
2020-01-28 15:08:24 +01:00
|
|
|
|
with mock.patch(
|
2021-02-09 11:35:38 +01:00
|
|
|
|
'chrono.agendas.models.TimePeriodExceptionSource.refresh_timeperiod_exceptions_from_ics'
|
|
|
|
|
) as refresh:
|
|
|
|
|
call_command('sync_desks_timeperiod_exceptions')
|
|
|
|
|
assert refresh.call_args_list == []
|
2020-01-28 15:08:24 +01:00
|
|
|
|
|
2017-10-03 23:15:00 +02:00
|
|
|
|
|
2020-02-20 11:46:23 +01:00
|
|
|
|
@override_settings(
|
|
|
|
|
EXCEPTIONS_SOURCES={
|
|
|
|
|
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
def test_timeperiodexception_from_settings():
|
|
|
|
|
agenda = Agenda(label='Test 1 agenda')
|
|
|
|
|
agenda.save()
|
|
|
|
|
desk = Desk(label='Test 1 desk', agenda=agenda)
|
|
|
|
|
desk.save()
|
2020-10-26 17:26:57 +01:00
|
|
|
|
desk.import_timeperiod_exceptions_from_settings(enable=True)
|
2020-02-20 11:46:23 +01:00
|
|
|
|
|
|
|
|
|
source = TimePeriodExceptionSource.objects.get(desk=desk)
|
|
|
|
|
assert source.settings_slug == 'holidays'
|
|
|
|
|
assert source.enabled
|
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk, source=source).exists()
|
|
|
|
|
|
|
|
|
|
exception = TimePeriodException.objects.first()
|
|
|
|
|
from workalendar.europe import France
|
|
|
|
|
|
|
|
|
|
date, label = France().holidays()[0]
|
|
|
|
|
exception = TimePeriodException.objects.filter(label=label).first()
|
|
|
|
|
assert exception.end_datetime - exception.start_datetime == datetime.timedelta(days=1)
|
|
|
|
|
assert localtime(exception.start_datetime).date() == date
|
|
|
|
|
|
|
|
|
|
source.disable()
|
|
|
|
|
assert not source.enabled
|
|
|
|
|
assert not TimePeriodException.objects.filter(desk=desk, source=source).exists()
|
|
|
|
|
|
|
|
|
|
source.enable()
|
|
|
|
|
assert source.enabled
|
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk, source=source).exists()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_timeperiodexception_from_settings_command():
|
|
|
|
|
setting = {
|
|
|
|
|
'EXCEPTIONS_SOURCES': {
|
|
|
|
|
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
agenda = Agenda(label='Test 1 agenda')
|
|
|
|
|
agenda.save()
|
2020-10-26 17:26:57 +01:00
|
|
|
|
|
2020-02-20 11:46:23 +01:00
|
|
|
|
desk1 = Desk(label='Test 1 desk', agenda=agenda)
|
|
|
|
|
desk1.save()
|
2020-10-26 17:26:57 +01:00
|
|
|
|
assert not TimePeriodExceptionSource.objects.filter(desk=desk1).exists()
|
|
|
|
|
|
2020-02-20 11:46:23 +01:00
|
|
|
|
with override_settings(**setting):
|
|
|
|
|
desk2 = Desk(label='Test 2 desk', agenda=agenda)
|
|
|
|
|
desk2.save()
|
2020-10-26 17:26:57 +01:00
|
|
|
|
desk2.import_timeperiod_exceptions_from_settings(enable=True)
|
2020-02-20 11:46:23 +01:00
|
|
|
|
desk3 = Desk(label='Test 3 desk', agenda=agenda)
|
|
|
|
|
desk3.save()
|
2020-10-26 17:26:57 +01:00
|
|
|
|
desk3.import_timeperiod_exceptions_from_settings(enable=False)
|
2020-02-20 11:46:23 +01:00
|
|
|
|
call_command('sync_desks_timeperiod_exceptions_from_settings')
|
|
|
|
|
assert not TimePeriodExceptionSource.objects.get(desk=desk1).enabled
|
|
|
|
|
source2 = TimePeriodExceptionSource.objects.get(desk=desk2)
|
|
|
|
|
assert source2.enabled
|
2020-10-26 17:26:57 +01:00
|
|
|
|
source3 = TimePeriodExceptionSource.objects.get(desk=desk3)
|
2020-02-20 11:46:23 +01:00
|
|
|
|
assert not source3.enabled
|
|
|
|
|
|
|
|
|
|
exceptions_count = source2.timeperiodexception_set.count()
|
|
|
|
|
# Alsace Moselle has more holidays
|
|
|
|
|
setting['EXCEPTIONS_SOURCES']['holidays']['class'] = 'workalendar.europe.FranceAlsaceMoselle'
|
|
|
|
|
with override_settings(**setting):
|
|
|
|
|
call_command('sync_desks_timeperiod_exceptions_from_settings')
|
|
|
|
|
source2.refresh_from_db()
|
|
|
|
|
assert exceptions_count < source2.timeperiodexception_set.count()
|
|
|
|
|
|
2020-10-29 17:11:39 +01:00
|
|
|
|
setting['LANGUAGE_CODE'] = 'fr-fr'
|
|
|
|
|
with override_settings(**setting):
|
|
|
|
|
call_command('sync_desks_timeperiod_exceptions_from_settings')
|
|
|
|
|
assert not TimePeriodException.objects.filter(label='All Saints Day').exists()
|
|
|
|
|
assert TimePeriodException.objects.filter(label='Toussaint').exists()
|
|
|
|
|
|
2020-02-20 11:46:23 +01:00
|
|
|
|
setting['EXCEPTIONS_SOURCES'] = {}
|
|
|
|
|
with override_settings(**setting):
|
|
|
|
|
call_command('sync_desks_timeperiod_exceptions_from_settings')
|
|
|
|
|
assert not TimePeriodExceptionSource.objects.exists()
|
|
|
|
|
|
|
|
|
|
|
2022-03-23 16:46:56 +01:00
|
|
|
|
def test_timeperiodexception_groups():
|
|
|
|
|
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
source = unavailability_calendar.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='holidays.ics', ics_file=ContentFile(ICS_HOLIDAYS, name='holidays.ics')
|
|
|
|
|
)
|
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
|
|
|
|
assert TimePeriodException.objects.count() == 11
|
|
|
|
|
group1, group2 = TimePeriodExceptionGroup.objects.all()
|
|
|
|
|
assert group1.label == 'Vacances de Noël'
|
|
|
|
|
assert group1.slug == 'christmas_holidays'
|
|
|
|
|
assert group2.label == 'Vacances d’Été'
|
|
|
|
|
assert group2.slug == 'summer_holidays'
|
|
|
|
|
|
|
|
|
|
assert group1.exceptions.count() == 6
|
|
|
|
|
assert group2.exceptions.count() == 5
|
|
|
|
|
|
|
|
|
|
unavailability_calendar.delete()
|
|
|
|
|
assert not TimePeriodException.objects.exists()
|
|
|
|
|
assert not TimePeriodExceptionGroup.objects.exists()
|
|
|
|
|
|
|
|
|
|
# check no groups are created for desks
|
|
|
|
|
agenda = Agenda.objects.create(label='Test 1 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 1 desk', agenda=agenda)
|
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='holidays.ics', ics_file=ContentFile(ICS_HOLIDAYS, name='holidays.ics')
|
|
|
|
|
)
|
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
|
|
|
|
assert TimePeriodException.objects.count() == 11
|
|
|
|
|
assert not TimePeriodExceptionGroup.objects.exists()
|
|
|
|
|
|
|
|
|
|
|
2017-11-19 22:10:44 +01:00
|
|
|
|
def test_base_meeting_duration():
|
|
|
|
|
agenda = Agenda(label='Meeting', kind='meetings')
|
|
|
|
|
agenda.save()
|
|
|
|
|
|
2020-12-23 10:56:49 +01:00
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
agenda.get_base_meeting_duration()
|
|
|
|
|
|
|
|
|
|
meeting_type = MeetingType(agenda=agenda, label='Foo', duration=0)
|
|
|
|
|
meeting_type.save()
|
|
|
|
|
|
2017-11-19 22:10:44 +01:00
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
agenda.get_base_meeting_duration()
|
|
|
|
|
|
|
|
|
|
meeting_type = MeetingType(agenda=agenda, label='Foo', duration=30)
|
|
|
|
|
meeting_type.save()
|
2021-04-21 11:44:32 +02:00
|
|
|
|
del agenda.__dict__['cached_meetingtypes']
|
2017-11-19 22:10:44 +01:00
|
|
|
|
assert agenda.get_base_meeting_duration() == 30
|
|
|
|
|
|
|
|
|
|
meeting_type = MeetingType(agenda=agenda, label='Bar', duration=60)
|
|
|
|
|
meeting_type.save()
|
2021-04-21 11:44:32 +02:00
|
|
|
|
del agenda.__dict__['cached_meetingtypes']
|
2017-11-19 22:10:44 +01:00
|
|
|
|
assert agenda.get_base_meeting_duration() == 30
|
|
|
|
|
|
|
|
|
|
meeting_type = MeetingType(agenda=agenda, label='Bar', duration=45)
|
|
|
|
|
meeting_type.save()
|
2021-04-21 11:44:32 +02:00
|
|
|
|
del agenda.__dict__['cached_meetingtypes']
|
2017-11-19 22:10:44 +01:00
|
|
|
|
assert agenda.get_base_meeting_duration() == 15
|
2018-05-07 21:44:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_timeperiodexception_creation_from_ics_with_duration():
|
|
|
|
|
# test that event defined using duration works and give the same start and
|
|
|
|
|
# end dates
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 1 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 1 desk', agenda=agenda)
|
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='sample.ics', ics_file=ContentFile(ICS_SAMPLE_WITH_DURATION, name='sample.ics')
|
2019-12-10 15:28:39 +01:00
|
|
|
|
)
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
2018-05-07 21:44:38 +02:00
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
|
|
|
|
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == {
|
|
|
|
|
make_aware(datetime.datetime(2017, 8, 31, 19, 8, 0)),
|
|
|
|
|
make_aware(datetime.datetime(2017, 8, 30, 20, 8, 0)),
|
2021-07-27 15:19:00 +02:00
|
|
|
|
}
|
2018-05-07 21:44:38 +02:00
|
|
|
|
assert set(TimePeriodException.objects.values_list('end_datetime', flat=True)) == {
|
|
|
|
|
make_aware(datetime.datetime(2017, 8, 31, 22, 34, 0)),
|
|
|
|
|
make_aware(datetime.datetime(2017, 9, 1, 0, 34, 0)),
|
2021-07-27 15:19:00 +02:00
|
|
|
|
}
|
2018-05-07 21:44:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.freeze_time('2017-12-01')
|
|
|
|
|
def test_timeperiodexception_creation_from_ics_with_recurrences_in_the_past():
|
|
|
|
|
# test that recurrent events before today are not created
|
|
|
|
|
# also test that duration + recurrent events works
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test 4 agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test 4 desk', agenda=agenda)
|
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='sample.ics',
|
|
|
|
|
ics_file=ContentFile(ICS_SAMPLE_WITH_RECURRENT_EVENT_IN_THE_PAST, name='sample.ics'),
|
2019-12-10 15:28:39 +01:00
|
|
|
|
)
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
2018-05-07 21:44:38 +02:00
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
|
|
|
|
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == {
|
|
|
|
|
make_aware(datetime.datetime(2018, 1, 1)),
|
|
|
|
|
make_aware(datetime.datetime(2019, 1, 1)),
|
2021-07-27 15:19:00 +02:00
|
|
|
|
}
|
2018-10-12 11:06:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_timeperiodexception_creation_from_ics_with_recurrences_atreal():
|
2021-02-09 11:35:38 +01:00
|
|
|
|
agenda = Agenda.objects.create(label='Test atreal agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Test atreal desk', agenda=agenda)
|
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='sample.ics', ics_file=ContentFile(ICS_ATREAL, name='sample.ics')
|
|
|
|
|
)
|
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk).exists()
|
2018-12-06 13:52:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_management_role_deletion():
|
|
|
|
|
group = Group(name='Group')
|
|
|
|
|
group.save()
|
|
|
|
|
agenda = Agenda(label='Test agenda', edit_role=group, view_role=group)
|
|
|
|
|
agenda.save()
|
|
|
|
|
|
|
|
|
|
Group.objects.all().delete()
|
|
|
|
|
|
2021-07-09 17:55:04 +02:00
|
|
|
|
assert Agenda.objects.get(id=agenda.id).view_role is None
|
|
|
|
|
assert Agenda.objects.get(id=agenda.id).edit_role is None
|
2019-12-24 10:14:55 +01:00
|
|
|
|
|
|
|
|
|
|
2020-02-06 17:49:38 +01:00
|
|
|
|
def test_virtual_agenda_init():
|
|
|
|
|
agenda1 = Agenda.objects.create(label='Agenda 1', kind='meetings')
|
|
|
|
|
agenda2 = Agenda.objects.create(label='Agenda 2', kind='meetings')
|
|
|
|
|
virt_agenda = Agenda.objects.create(label='Virtual agenda', kind='virtual')
|
|
|
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda1)
|
|
|
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda2)
|
|
|
|
|
virt_agenda.save()
|
|
|
|
|
|
|
|
|
|
assert virt_agenda.real_agendas.count() == 2
|
|
|
|
|
assert virt_agenda.real_agendas.get(pk=agenda1.pk)
|
|
|
|
|
assert virt_agenda.real_agendas.get(pk=agenda2.pk)
|
|
|
|
|
|
|
|
|
|
for agenda in (agenda1, agenda2):
|
|
|
|
|
assert agenda.virtual_agendas.count() == 1
|
|
|
|
|
assert agenda.virtual_agendas.get() == virt_agenda
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_virtual_agenda_base_meeting_duration():
|
|
|
|
|
virt_agenda = Agenda.objects.create(label='Virtual agenda', kind='virtual')
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
virt_agenda.get_base_meeting_duration()
|
|
|
|
|
|
|
|
|
|
agenda1 = Agenda.objects.create(label='Agenda 1', kind='meetings')
|
|
|
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda1)
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
virt_agenda.get_base_meeting_duration()
|
|
|
|
|
|
|
|
|
|
meeting_type = MeetingType(agenda=agenda1, label='Foo', duration=30)
|
|
|
|
|
meeting_type.save()
|
2021-04-21 11:44:32 +02:00
|
|
|
|
del virt_agenda.__dict__['cached_meetingtypes']
|
2020-02-06 17:49:38 +01:00
|
|
|
|
assert virt_agenda.get_base_meeting_duration() == 30
|
|
|
|
|
|
|
|
|
|
meeting_type = MeetingType(agenda=agenda1, label='Bar', duration=60)
|
|
|
|
|
meeting_type.save()
|
2021-04-21 11:44:32 +02:00
|
|
|
|
del virt_agenda.__dict__['cached_meetingtypes']
|
2020-02-06 17:49:38 +01:00
|
|
|
|
assert virt_agenda.get_base_meeting_duration() == 30
|
|
|
|
|
|
|
|
|
|
agenda2 = Agenda.objects.create(label='Agenda 2', kind='meetings')
|
|
|
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda2)
|
|
|
|
|
virt_agenda.save()
|
|
|
|
|
|
|
|
|
|
meeting_type = MeetingType(agenda=agenda2, label='Bar', duration=60)
|
|
|
|
|
meeting_type.save()
|
2021-04-21 11:44:32 +02:00
|
|
|
|
del virt_agenda.__dict__['cached_meetingtypes']
|
2020-02-06 17:49:38 +01:00
|
|
|
|
assert virt_agenda.get_base_meeting_duration() == 60
|
2020-02-26 18:18:15 +01:00
|
|
|
|
|
|
|
|
|
|
api: optimize get_all_slots() and around it (#42169)
Workflow in get_all_slots() is simplified :
* first we accumulate, for each desk, the set of time slots when a booking cannot
occur or is already booked,
* then we generate the list of possible time slots and match them to the
exclusion and already booked set.
Intervals is replaced by a simpler data-structure, IntervalSet, it does
not need to be a map, a simple set is enough.
Also :
* moved TimePeriod.get_effective_timeperiods() to the agenda level , it
deduplictes TimePeriod between desks and remove excluded TimePeriod for
virtual agendas.
* added a named-tuple WeekTime to represent a TimePeriod base unit, so
we can use them in IntervalSet easily (as they can be compared) to
compute the effective time periods,
* the fact that base_duration is unique for a given virtual agenda is
now accounted and stated everywhere,
* the fact that generated time slots must have time in the local
timezone for the API to work is now stated everywhere,
* In get_all_slots(), also :
* integrated the code of get_exceptions_by_desk() into get_all_slots()
to further reduce the number of SQL queries.
* used_min/max_datetime is reduced by the exclusion periods, and
effective time periods are grouped based on the used_min/max_datetime of
each agenda.
* pre-filter slots for uniqueness when generating available datetimes
(but for filling slot we still need exact availability information
for each desk)
2020-04-30 12:16:38 +02:00
|
|
|
|
def test_agenda_get_effective_time_periods(db):
|
|
|
|
|
real_agenda = Agenda.objects.create(label='Real Agenda', kind='meetings')
|
2021-07-09 15:41:40 +02:00
|
|
|
|
MeetingType.objects.create(agenda=real_agenda, label='MT1')
|
api: optimize get_all_slots() and around it (#42169)
Workflow in get_all_slots() is simplified :
* first we accumulate, for each desk, the set of time slots when a booking cannot
occur or is already booked,
* then we generate the list of possible time slots and match them to the
exclusion and already booked set.
Intervals is replaced by a simpler data-structure, IntervalSet, it does
not need to be a map, a simple set is enough.
Also :
* moved TimePeriod.get_effective_timeperiods() to the agenda level , it
deduplictes TimePeriod between desks and remove excluded TimePeriod for
virtual agendas.
* added a named-tuple WeekTime to represent a TimePeriod base unit, so
we can use them in IntervalSet easily (as they can be compared) to
compute the effective time periods,
* the fact that base_duration is unique for a given virtual agenda is
now accounted and stated everywhere,
* the fact that generated time slots must have time in the local
timezone for the API to work is now stated everywhere,
* In get_all_slots(), also :
* integrated the code of get_exceptions_by_desk() into get_all_slots()
to further reduce the number of SQL queries.
* used_min/max_datetime is reduced by the exclusion periods, and
effective time periods are grouped based on the used_min/max_datetime of
each agenda.
* pre-filter slots for uniqueness when generating available datetimes
(but for filling slot we still need exact availability information
for each desk)
2020-04-30 12:16:38 +02:00
|
|
|
|
desk = Desk.objects.create(label='Real Agenda Desk1', agenda=real_agenda)
|
|
|
|
|
time_period = TimePeriod.objects.create(
|
|
|
|
|
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0), desk=desk
|
|
|
|
|
)
|
|
|
|
|
virtual_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
|
|
|
|
|
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=real_agenda)
|
|
|
|
|
|
2020-02-26 18:18:15 +01:00
|
|
|
|
# empty exclusion set
|
api: optimize get_all_slots() and around it (#42169)
Workflow in get_all_slots() is simplified :
* first we accumulate, for each desk, the set of time slots when a booking cannot
occur or is already booked,
* then we generate the list of possible time slots and match them to the
exclusion and already booked set.
Intervals is replaced by a simpler data-structure, IntervalSet, it does
not need to be a map, a simple set is enough.
Also :
* moved TimePeriod.get_effective_timeperiods() to the agenda level , it
deduplictes TimePeriod between desks and remove excluded TimePeriod for
virtual agendas.
* added a named-tuple WeekTime to represent a TimePeriod base unit, so
we can use them in IntervalSet easily (as they can be compared) to
compute the effective time periods,
* the fact that base_duration is unique for a given virtual agenda is
now accounted and stated everywhere,
* the fact that generated time slots must have time in the local
timezone for the API to work is now stated everywhere,
* In get_all_slots(), also :
* integrated the code of get_exceptions_by_desk() into get_all_slots()
to further reduce the number of SQL queries.
* used_min/max_datetime is reduced by the exclusion periods, and
effective time periods are grouped based on the used_min/max_datetime of
each agenda.
* pre-filter slots for uniqueness when generating available datetimes
(but for filling slot we still need exact availability information
for each desk)
2020-04-30 12:16:38 +02:00
|
|
|
|
common_timeperiods = list(virtual_agenda.get_effective_time_periods())
|
|
|
|
|
assert len(common_timeperiods) == 1
|
|
|
|
|
common_timeperiod = common_timeperiods[0]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == time_period.start_time
|
|
|
|
|
assert common_timeperiod.end_time == time_period.end_time
|
2020-02-26 18:18:15 +01:00
|
|
|
|
|
|
|
|
|
# exclusions are on a different day
|
api: optimize get_all_slots() and around it (#42169)
Workflow in get_all_slots() is simplified :
* first we accumulate, for each desk, the set of time slots when a booking cannot
occur or is already booked,
* then we generate the list of possible time slots and match them to the
exclusion and already booked set.
Intervals is replaced by a simpler data-structure, IntervalSet, it does
not need to be a map, a simple set is enough.
Also :
* moved TimePeriod.get_effective_timeperiods() to the agenda level , it
deduplictes TimePeriod between desks and remove excluded TimePeriod for
virtual agendas.
* added a named-tuple WeekTime to represent a TimePeriod base unit, so
we can use them in IntervalSet easily (as they can be compared) to
compute the effective time periods,
* the fact that base_duration is unique for a given virtual agenda is
now accounted and stated everywhere,
* the fact that generated time slots must have time in the local
timezone for the API to work is now stated everywhere,
* In get_all_slots(), also :
* integrated the code of get_exceptions_by_desk() into get_all_slots()
to further reduce the number of SQL queries.
* used_min/max_datetime is reduced by the exclusion periods, and
effective time periods are grouped based on the used_min/max_datetime of
each agenda.
* pre-filter slots for uniqueness when generating available datetimes
(but for filling slot we still need exact availability information
for each desk)
2020-04-30 12:16:38 +02:00
|
|
|
|
def exclude_time_periods(time_periods):
|
|
|
|
|
virtual_agenda.excluded_timeperiods.clear()
|
|
|
|
|
for time_period in time_periods:
|
|
|
|
|
time_period.agenda = virtual_agenda
|
|
|
|
|
time_period.save()
|
|
|
|
|
|
|
|
|
|
exclude_time_periods(
|
|
|
|
|
[
|
|
|
|
|
TimePeriod(weekday=1, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
|
|
|
|
|
TimePeriod(weekday=2, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
common_timeperiods = list(virtual_agenda.get_effective_time_periods())
|
|
|
|
|
assert len(common_timeperiods) == 1
|
|
|
|
|
common_timeperiod = common_timeperiods[0]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == time_period.start_time
|
|
|
|
|
assert common_timeperiod.end_time == time_period.end_time
|
2020-02-26 18:18:15 +01:00
|
|
|
|
|
|
|
|
|
# one exclusion, end_time should be earlier
|
api: optimize get_all_slots() and around it (#42169)
Workflow in get_all_slots() is simplified :
* first we accumulate, for each desk, the set of time slots when a booking cannot
occur or is already booked,
* then we generate the list of possible time slots and match them to the
exclusion and already booked set.
Intervals is replaced by a simpler data-structure, IntervalSet, it does
not need to be a map, a simple set is enough.
Also :
* moved TimePeriod.get_effective_timeperiods() to the agenda level , it
deduplictes TimePeriod between desks and remove excluded TimePeriod for
virtual agendas.
* added a named-tuple WeekTime to represent a TimePeriod base unit, so
we can use them in IntervalSet easily (as they can be compared) to
compute the effective time periods,
* the fact that base_duration is unique for a given virtual agenda is
now accounted and stated everywhere,
* the fact that generated time slots must have time in the local
timezone for the API to work is now stated everywhere,
* In get_all_slots(), also :
* integrated the code of get_exceptions_by_desk() into get_all_slots()
to further reduce the number of SQL queries.
* used_min/max_datetime is reduced by the exclusion periods, and
effective time periods are grouped based on the used_min/max_datetime of
each agenda.
* pre-filter slots for uniqueness when generating available datetimes
(but for filling slot we still need exact availability information
for each desk)
2020-04-30 12:16:38 +02:00
|
|
|
|
exclude_time_periods(
|
|
|
|
|
[TimePeriod(weekday=0, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0))]
|
|
|
|
|
)
|
|
|
|
|
common_timeperiods = list(virtual_agenda.get_effective_time_periods())
|
|
|
|
|
assert len(common_timeperiods) == 1
|
|
|
|
|
common_timeperiod = common_timeperiods[0]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == datetime.time(10, 0)
|
|
|
|
|
assert common_timeperiod.end_time == datetime.time(17, 0)
|
2020-02-26 18:18:15 +01:00
|
|
|
|
|
|
|
|
|
# one exclusion, start_time should be later
|
api: optimize get_all_slots() and around it (#42169)
Workflow in get_all_slots() is simplified :
* first we accumulate, for each desk, the set of time slots when a booking cannot
occur or is already booked,
* then we generate the list of possible time slots and match them to the
exclusion and already booked set.
Intervals is replaced by a simpler data-structure, IntervalSet, it does
not need to be a map, a simple set is enough.
Also :
* moved TimePeriod.get_effective_timeperiods() to the agenda level , it
deduplictes TimePeriod between desks and remove excluded TimePeriod for
virtual agendas.
* added a named-tuple WeekTime to represent a TimePeriod base unit, so
we can use them in IntervalSet easily (as they can be compared) to
compute the effective time periods,
* the fact that base_duration is unique for a given virtual agenda is
now accounted and stated everywhere,
* the fact that generated time slots must have time in the local
timezone for the API to work is now stated everywhere,
* In get_all_slots(), also :
* integrated the code of get_exceptions_by_desk() into get_all_slots()
to further reduce the number of SQL queries.
* used_min/max_datetime is reduced by the exclusion periods, and
effective time periods are grouped based on the used_min/max_datetime of
each agenda.
* pre-filter slots for uniqueness when generating available datetimes
(but for filling slot we still need exact availability information
for each desk)
2020-04-30 12:16:38 +02:00
|
|
|
|
exclude_time_periods(
|
|
|
|
|
[TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0))]
|
|
|
|
|
)
|
|
|
|
|
common_timeperiods = list(virtual_agenda.get_effective_time_periods())
|
|
|
|
|
assert len(common_timeperiods) == 1
|
|
|
|
|
common_timeperiod = common_timeperiods[0]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == datetime.time(16, 0)
|
|
|
|
|
assert common_timeperiod.end_time == datetime.time(18, 0)
|
2020-02-26 18:18:15 +01:00
|
|
|
|
|
|
|
|
|
# one exclusion, splits effective timeperiod in two
|
api: optimize get_all_slots() and around it (#42169)
Workflow in get_all_slots() is simplified :
* first we accumulate, for each desk, the set of time slots when a booking cannot
occur or is already booked,
* then we generate the list of possible time slots and match them to the
exclusion and already booked set.
Intervals is replaced by a simpler data-structure, IntervalSet, it does
not need to be a map, a simple set is enough.
Also :
* moved TimePeriod.get_effective_timeperiods() to the agenda level , it
deduplictes TimePeriod between desks and remove excluded TimePeriod for
virtual agendas.
* added a named-tuple WeekTime to represent a TimePeriod base unit, so
we can use them in IntervalSet easily (as they can be compared) to
compute the effective time periods,
* the fact that base_duration is unique for a given virtual agenda is
now accounted and stated everywhere,
* the fact that generated time slots must have time in the local
timezone for the API to work is now stated everywhere,
* In get_all_slots(), also :
* integrated the code of get_exceptions_by_desk() into get_all_slots()
to further reduce the number of SQL queries.
* used_min/max_datetime is reduced by the exclusion periods, and
effective time periods are grouped based on the used_min/max_datetime of
each agenda.
* pre-filter slots for uniqueness when generating available datetimes
(but for filling slot we still need exact availability information
for each desk)
2020-04-30 12:16:38 +02:00
|
|
|
|
exclude_time_periods(
|
|
|
|
|
[TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(16, 0))]
|
|
|
|
|
)
|
|
|
|
|
common_timeperiods = list(virtual_agenda.get_effective_time_periods())
|
|
|
|
|
assert len(common_timeperiods) == 2
|
|
|
|
|
common_timeperiod = common_timeperiods[0]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == datetime.time(10, 0)
|
|
|
|
|
assert common_timeperiod.end_time == datetime.time(12, 0)
|
|
|
|
|
common_timeperiod = common_timeperiods[1]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == datetime.time(16, 0)
|
|
|
|
|
assert common_timeperiod.end_time == datetime.time(18, 0)
|
2020-02-26 18:18:15 +01:00
|
|
|
|
|
|
|
|
|
# several exclusion, splits effective timeperiod into pieces
|
api: optimize get_all_slots() and around it (#42169)
Workflow in get_all_slots() is simplified :
* first we accumulate, for each desk, the set of time slots when a booking cannot
occur or is already booked,
* then we generate the list of possible time slots and match them to the
exclusion and already booked set.
Intervals is replaced by a simpler data-structure, IntervalSet, it does
not need to be a map, a simple set is enough.
Also :
* moved TimePeriod.get_effective_timeperiods() to the agenda level , it
deduplictes TimePeriod between desks and remove excluded TimePeriod for
virtual agendas.
* added a named-tuple WeekTime to represent a TimePeriod base unit, so
we can use them in IntervalSet easily (as they can be compared) to
compute the effective time periods,
* the fact that base_duration is unique for a given virtual agenda is
now accounted and stated everywhere,
* the fact that generated time slots must have time in the local
timezone for the API to work is now stated everywhere,
* In get_all_slots(), also :
* integrated the code of get_exceptions_by_desk() into get_all_slots()
to further reduce the number of SQL queries.
* used_min/max_datetime is reduced by the exclusion periods, and
effective time periods are grouped based on the used_min/max_datetime of
each agenda.
* pre-filter slots for uniqueness when generating available datetimes
(but for filling slot we still need exact availability information
for each desk)
2020-04-30 12:16:38 +02:00
|
|
|
|
exclude_time_periods(
|
|
|
|
|
[
|
|
|
|
|
TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(13, 0)),
|
|
|
|
|
TimePeriod(weekday=0, start_time=datetime.time(10, 30), end_time=datetime.time(11, 30)),
|
|
|
|
|
TimePeriod(weekday=0, start_time=datetime.time(16, 30), end_time=datetime.time(17, 00)),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
common_timeperiods = list(virtual_agenda.get_effective_time_periods())
|
|
|
|
|
assert len(common_timeperiods) == 4
|
|
|
|
|
|
|
|
|
|
common_timeperiod = common_timeperiods[0]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == datetime.time(10, 0)
|
|
|
|
|
assert common_timeperiod.end_time == datetime.time(10, 30)
|
|
|
|
|
|
|
|
|
|
common_timeperiod = common_timeperiods[1]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == datetime.time(11, 30)
|
|
|
|
|
assert common_timeperiod.end_time == datetime.time(12, 0)
|
|
|
|
|
|
|
|
|
|
common_timeperiod = common_timeperiods[2]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == datetime.time(13, 0)
|
|
|
|
|
assert common_timeperiod.end_time == datetime.time(16, 30)
|
|
|
|
|
|
|
|
|
|
common_timeperiod = common_timeperiods[3]
|
|
|
|
|
assert common_timeperiod.weekday == time_period.weekday
|
|
|
|
|
assert common_timeperiod.start_time == datetime.time(17, 0)
|
|
|
|
|
assert common_timeperiod.end_time == datetime.time(18, 0)
|
2020-05-07 17:45:01 +02:00
|
|
|
|
|
|
|
|
|
|
2021-01-29 10:46:10 +01:00
|
|
|
|
def test_exception_read_only():
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
|
|
|
|
|
desk = Desk.objects.create(agenda=agenda, label='Desk')
|
|
|
|
|
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
|
|
|
|
|
source1 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='http://example.com/sample.ics')
|
|
|
|
|
source2 = TimePeriodExceptionSource.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
ics_filename='sample.ics',
|
|
|
|
|
ics_file=ContentFile(ICS_SAMPLE_WITH_DURATION, name='sample.ics'),
|
|
|
|
|
)
|
|
|
|
|
source3 = TimePeriodExceptionSource.objects.create(
|
|
|
|
|
desk=desk, settings_slug='slug', settings_label='Foo Bar', enabled=True
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
exception = TimePeriodException.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=now() - datetime.timedelta(days=2),
|
|
|
|
|
end_datetime=now() - datetime.timedelta(days=1),
|
|
|
|
|
)
|
|
|
|
|
assert exception.read_only is False
|
|
|
|
|
|
|
|
|
|
for source in [source1, source2, source3]:
|
|
|
|
|
exception.source = source
|
|
|
|
|
exception.save()
|
|
|
|
|
assert exception.read_only is True
|
|
|
|
|
|
|
|
|
|
exception.source = None
|
|
|
|
|
exception.desk = None
|
|
|
|
|
exception.unavailability_calendar = unavailability_calendar
|
|
|
|
|
exception.save()
|
|
|
|
|
assert exception.read_only is True
|
|
|
|
|
|
|
|
|
|
|
2020-06-02 09:44:53 +02:00
|
|
|
|
def test_desk_exceptions_within_two_weeks():
|
2020-11-19 17:21:00 +01:00
|
|
|
|
def set_prefetched_exceptions(desk):
|
|
|
|
|
desk.prefetched_exceptions = TimePeriodException.objects.filter(
|
|
|
|
|
Q(desk=desk) | Q(unavailability_calendar__desks=desk)
|
|
|
|
|
)
|
|
|
|
|
|
2020-06-02 09:44:53 +02:00
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
|
|
|
|
|
desk = Desk.objects.create(agenda=agenda, label='Desk')
|
|
|
|
|
|
|
|
|
|
# no exception
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-06-02 09:44:53 +02:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == []
|
|
|
|
|
|
|
|
|
|
# exception ends in the past
|
|
|
|
|
exception = TimePeriodException.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=now() - datetime.timedelta(days=2),
|
|
|
|
|
end_datetime=now() - datetime.timedelta(days=1),
|
|
|
|
|
)
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-06-02 09:44:53 +02:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == []
|
|
|
|
|
|
|
|
|
|
# exception ends in the future - 14 days
|
|
|
|
|
exception.end_datetime = now() + datetime.timedelta(days=10)
|
|
|
|
|
# but starts in the past
|
|
|
|
|
exception.start_datetime = now() - datetime.timedelta(days=2)
|
|
|
|
|
exception.save()
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-06-02 09:44:53 +02:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == [exception]
|
|
|
|
|
|
|
|
|
|
# exception ends in the future - 14 days
|
|
|
|
|
exception.end_datetime = now() + datetime.timedelta(days=10)
|
|
|
|
|
# but starts in the future
|
|
|
|
|
exception.start_datetime = now() + datetime.timedelta(days=2)
|
|
|
|
|
exception.save()
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-06-02 09:44:53 +02:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == [exception]
|
|
|
|
|
|
|
|
|
|
# exception ends in the future + 14 days
|
|
|
|
|
exception.end_datetime = now() + datetime.timedelta(days=20)
|
|
|
|
|
# but starts in the past
|
|
|
|
|
exception.start_datetime = now() - datetime.timedelta(days=2)
|
|
|
|
|
exception.save()
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-06-02 09:44:53 +02:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == [exception]
|
|
|
|
|
|
|
|
|
|
# create another one, very far way from now
|
|
|
|
|
exception2 = TimePeriodException.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=now() + datetime.timedelta(days=200),
|
|
|
|
|
end_datetime=now() + datetime.timedelta(days=201),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# exception ends in the future + 14 days
|
|
|
|
|
exception.end_datetime = now() + datetime.timedelta(days=20)
|
|
|
|
|
# but starts in the future - 14 days
|
|
|
|
|
exception.start_datetime = now() + datetime.timedelta(days=2)
|
|
|
|
|
exception.save()
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-06-02 09:44:53 +02:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == [exception]
|
|
|
|
|
|
|
|
|
|
# exception ends in the future + 14 days
|
|
|
|
|
exception.end_datetime = now() + datetime.timedelta(days=20)
|
|
|
|
|
# but starts in the future + 14 days
|
|
|
|
|
exception.start_datetime = now() + datetime.timedelta(days=21)
|
|
|
|
|
exception.save()
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-06-02 09:44:53 +02:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == [exception]
|
|
|
|
|
|
|
|
|
|
# exception in the past
|
|
|
|
|
exception.end_datetime = now() - datetime.timedelta(days=20)
|
|
|
|
|
exception.start_datetime = now() - datetime.timedelta(days=21)
|
|
|
|
|
exception.save()
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-06-02 09:44:53 +02:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == [exception2]
|
|
|
|
|
|
|
|
|
|
# check ordering of the queryset: exception is after exception2
|
|
|
|
|
exception.start_datetime = now() + datetime.timedelta(days=10)
|
|
|
|
|
exception.end_datetime = now() + datetime.timedelta(days=11)
|
|
|
|
|
exception.save()
|
|
|
|
|
exception2.start_datetime = now() + datetime.timedelta(days=5)
|
|
|
|
|
exception2.end_datetime = now() + datetime.timedelta(days=6)
|
|
|
|
|
exception2.save()
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-06-02 09:44:53 +02:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == [exception2, exception]
|
|
|
|
|
|
2020-11-04 14:16:32 +01:00
|
|
|
|
# add an exception from unavailability calendar
|
|
|
|
|
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
exception3 = TimePeriodException.objects.create(
|
|
|
|
|
unavailability_calendar=unavailability_calendar,
|
|
|
|
|
start_datetime=now() + datetime.timedelta(days=2),
|
|
|
|
|
end_datetime=now() + datetime.timedelta(days=3),
|
|
|
|
|
)
|
|
|
|
|
unavailability_calendar.desks.add(desk)
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-11-04 14:16:32 +01:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == [exception3, exception2, exception]
|
|
|
|
|
|
|
|
|
|
# change it
|
|
|
|
|
exception3.start_datetime = now() + datetime.timedelta(days=7)
|
|
|
|
|
exception3.end_datetime = now() + datetime.timedelta(days=8)
|
|
|
|
|
exception3.save()
|
2020-11-19 17:21:00 +01:00
|
|
|
|
set_prefetched_exceptions(desk)
|
2020-11-04 14:16:32 +01:00
|
|
|
|
assert list(desk.get_exceptions_within_two_weeks()) == [exception2, exception3, exception]
|
|
|
|
|
|
2020-06-02 09:44:53 +02:00
|
|
|
|
|
2020-05-07 17:45:01 +02:00
|
|
|
|
def test_desk_duplicate():
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Desk', agenda=agenda)
|
|
|
|
|
time_period = TimePeriod.objects.create(
|
|
|
|
|
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
|
|
|
|
)
|
|
|
|
|
TimePeriodExceptionSource.objects.create(desk=desk, ics_url='http://example.com/sample.ics')
|
|
|
|
|
source2 = TimePeriodExceptionSource.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
ics_filename='sample.ics',
|
|
|
|
|
ics_file=ContentFile(ICS_SAMPLE_WITH_DURATION, name='sample.ics'),
|
|
|
|
|
)
|
|
|
|
|
time_period_exception = TimePeriodException.objects.create(
|
|
|
|
|
label='Exception',
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=now() + datetime.timedelta(days=1),
|
|
|
|
|
end_datetime=now() + datetime.timedelta(days=2),
|
|
|
|
|
)
|
2021-01-26 09:46:32 +01:00
|
|
|
|
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
unavailability_calendar.desks.add(desk)
|
2020-05-07 17:45:01 +02:00
|
|
|
|
|
2020-07-23 14:25:26 +02:00
|
|
|
|
new_desk = desk.duplicate(label="New Desk")
|
2020-05-07 17:45:01 +02:00
|
|
|
|
assert new_desk.pk != desk.pk
|
2020-07-23 14:25:26 +02:00
|
|
|
|
assert new_desk.label == 'New Desk'
|
|
|
|
|
assert new_desk.slug == 'new-desk'
|
2020-05-07 17:45:01 +02:00
|
|
|
|
assert new_desk.timeperiod_set.count() == 1
|
|
|
|
|
new_time_period = TimePeriod.objects.get(desk=new_desk)
|
|
|
|
|
assert new_time_period.weekday == time_period.weekday
|
|
|
|
|
assert new_time_period.start_time == time_period.start_time
|
|
|
|
|
assert new_time_period.end_time == time_period.end_time
|
|
|
|
|
assert new_desk.timeperiodexception_set.count() == 1
|
|
|
|
|
new_time_period_exception = TimePeriodException.objects.get(desk=new_desk)
|
|
|
|
|
assert new_time_period_exception.label == time_period_exception.label
|
|
|
|
|
assert new_time_period_exception.start_datetime == time_period_exception.start_datetime
|
|
|
|
|
assert new_time_period_exception.end_datetime == time_period_exception.end_datetime
|
|
|
|
|
assert new_desk.timeperiodexceptionsource_set.count() == 2
|
|
|
|
|
assert TimePeriodExceptionSource.objects.filter(
|
|
|
|
|
desk=new_desk, ics_url='http://example.com/sample.ics'
|
|
|
|
|
).exists()
|
|
|
|
|
new_source2 = TimePeriodExceptionSource.objects.get(desk=new_desk, ics_filename='sample.ics')
|
|
|
|
|
assert new_source2.ics_file.path != source2.ics_file.path
|
2021-01-26 09:46:32 +01:00
|
|
|
|
assert new_desk.unavailability_calendars.count() == 1
|
|
|
|
|
assert new_desk.unavailability_calendars.get() == unavailability_calendar
|
2020-05-07 17:45:01 +02:00
|
|
|
|
|
|
|
|
|
# duplicate again !
|
2020-07-23 14:25:26 +02:00
|
|
|
|
new_desk = desk.duplicate(label="New Desk")
|
|
|
|
|
assert new_desk.slug == 'new-desk-1'
|
2020-06-17 10:52:25 +02:00
|
|
|
|
|
|
|
|
|
|
2020-10-22 11:58:55 +02:00
|
|
|
|
def test_desk_duplicate_exception_sources():
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Desk', agenda=agenda)
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source = desk.timeperiodexceptionsource_set.create(
|
|
|
|
|
ics_filename='sample.ics', ics_file=ContentFile(ICS_SAMPLE, name='sample.ics')
|
2020-10-22 11:58:55 +02:00
|
|
|
|
)
|
2021-02-09 11:35:38 +01:00
|
|
|
|
source.refresh_timeperiod_exceptions_from_ics()
|
2020-10-22 11:58:55 +02:00
|
|
|
|
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
|
|
|
|
|
|
|
|
|
new_desk = desk.duplicate(label="New Desk")
|
|
|
|
|
new_source = new_desk.timeperiodexceptionsource_set.get(ics_filename='sample.ics')
|
2021-02-09 11:35:38 +01:00
|
|
|
|
assert new_desk.timeperiodexception_set.count() == 2
|
2020-10-22 11:58:55 +02:00
|
|
|
|
|
|
|
|
|
source.delete()
|
2021-02-09 11:35:38 +01:00
|
|
|
|
assert new_desk.timeperiodexception_set.count() == 2
|
2020-10-22 11:58:55 +02:00
|
|
|
|
|
|
|
|
|
new_source.delete()
|
|
|
|
|
assert not new_desk.timeperiodexception_set.exists()
|
|
|
|
|
|
|
|
|
|
|
2020-10-22 11:59:31 +02:00
|
|
|
|
@override_settings(
|
|
|
|
|
EXCEPTIONS_SOURCES={
|
|
|
|
|
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
def test_desk_duplicate_exception_source_from_settings():
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda')
|
|
|
|
|
desk = Desk.objects.create(label='Desk', agenda=agenda)
|
2020-10-26 17:26:57 +01:00
|
|
|
|
desk.import_timeperiod_exceptions_from_settings(enable=True)
|
2020-10-22 11:59:31 +02:00
|
|
|
|
|
|
|
|
|
source = desk.timeperiodexceptionsource_set.get(settings_slug='holidays')
|
|
|
|
|
assert source.enabled
|
|
|
|
|
exceptions_count = desk.timeperiodexception_set.count()
|
|
|
|
|
|
|
|
|
|
new_desk = desk.duplicate(label="New Desk")
|
|
|
|
|
assert new_desk.timeperiodexceptionsource_set.filter(settings_slug='holidays').count() == 1
|
|
|
|
|
assert new_desk.timeperiodexceptionsource_set.get(settings_slug='holidays').enabled
|
|
|
|
|
assert new_desk.timeperiodexception_set.count() == exceptions_count
|
|
|
|
|
|
|
|
|
|
source.disable()
|
|
|
|
|
|
|
|
|
|
new_desk = desk.duplicate(label="New Desk")
|
|
|
|
|
assert not new_desk.timeperiodexceptionsource_set.get(settings_slug='holidays').enabled
|
|
|
|
|
assert not new_desk.timeperiodexception_set.exists()
|
|
|
|
|
|
|
|
|
|
|
2020-06-17 10:52:25 +02:00
|
|
|
|
def test_agenda_meetings_duplicate():
|
|
|
|
|
group = Group(name='Group')
|
|
|
|
|
group.save()
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='meetings', view_role=group)
|
|
|
|
|
desk = Desk.objects.create(label='Desk', agenda=agenda)
|
|
|
|
|
meeting_type = MeetingType.objects.create(agenda=agenda, label='meeting', duration=30)
|
|
|
|
|
time_period = TimePeriod.objects.create(
|
|
|
|
|
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
|
|
|
|
)
|
|
|
|
|
TimePeriodExceptionSource.objects.create(desk=desk, ics_url='http://example.com/sample.ics')
|
|
|
|
|
source2 = TimePeriodExceptionSource.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
ics_filename='sample.ics',
|
|
|
|
|
ics_file=ContentFile(ICS_SAMPLE_WITH_DURATION, name='sample.ics'),
|
|
|
|
|
)
|
|
|
|
|
time_period_exception = TimePeriodException.objects.create(
|
|
|
|
|
label='Exception',
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=now() + datetime.timedelta(days=1),
|
|
|
|
|
end_datetime=now() + datetime.timedelta(days=2),
|
|
|
|
|
)
|
|
|
|
|
resource = Resource.objects.create(label='Foo bar')
|
|
|
|
|
agenda.resources.add(resource)
|
|
|
|
|
|
|
|
|
|
new_agenda = agenda.duplicate()
|
|
|
|
|
assert new_agenda.pk != agenda.pk
|
|
|
|
|
assert new_agenda.label == 'Copy of Agenda'
|
|
|
|
|
assert new_agenda.slug == 'copy-of-agenda'
|
|
|
|
|
assert new_agenda.kind == 'meetings'
|
|
|
|
|
assert new_agenda.view_role == group
|
|
|
|
|
assert new_agenda.resources.first() == resource
|
|
|
|
|
|
|
|
|
|
new_meeting_type = new_agenda.meetingtype_set.first()
|
|
|
|
|
assert new_meeting_type.pk != meeting_type.pk
|
|
|
|
|
assert new_meeting_type.label == meeting_type.label
|
|
|
|
|
assert new_meeting_type.duration == meeting_type.duration
|
|
|
|
|
assert new_meeting_type.slug == meeting_type.slug
|
|
|
|
|
|
|
|
|
|
new_desk = new_agenda.desk_set.first()
|
|
|
|
|
assert new_desk.pk != desk.pk
|
2020-07-23 14:25:26 +02:00
|
|
|
|
assert new_desk.label == desk.label
|
2020-06-17 10:52:25 +02:00
|
|
|
|
assert new_desk.slug == desk.slug
|
|
|
|
|
assert new_desk.timeperiod_set.count() == 1
|
|
|
|
|
new_time_period = TimePeriod.objects.get(desk=new_desk)
|
|
|
|
|
assert new_time_period.weekday == time_period.weekday
|
|
|
|
|
assert new_time_period.start_time == time_period.start_time
|
|
|
|
|
assert new_time_period.end_time == time_period.end_time
|
|
|
|
|
assert new_desk.timeperiodexception_set.count() == 1
|
|
|
|
|
new_time_period_exception = TimePeriodException.objects.get(desk=new_desk)
|
|
|
|
|
assert new_time_period_exception.label == time_period_exception.label
|
|
|
|
|
assert new_time_period_exception.start_datetime == time_period_exception.start_datetime
|
|
|
|
|
assert new_time_period_exception.end_datetime == time_period_exception.end_datetime
|
|
|
|
|
assert new_desk.timeperiodexceptionsource_set.count() == 2
|
|
|
|
|
assert TimePeriodExceptionSource.objects.filter(
|
|
|
|
|
desk=new_desk, ics_url='http://example.com/sample.ics'
|
|
|
|
|
).exists()
|
|
|
|
|
new_source2 = TimePeriodExceptionSource.objects.get(desk=new_desk, ics_filename='sample.ics')
|
|
|
|
|
assert new_source2.ics_file.path != source2.ics_file.path
|
|
|
|
|
|
|
|
|
|
# duplicate again !
|
|
|
|
|
new_agenda = agenda.duplicate()
|
|
|
|
|
assert new_agenda.slug == 'copy-of-agenda-1'
|
|
|
|
|
|
|
|
|
|
|
2021-07-08 17:27:31 +02:00
|
|
|
|
@override_settings(
|
|
|
|
|
EXCEPTIONS_SOURCES={
|
|
|
|
|
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
|
|
|
|
|
}
|
|
|
|
|
)
|
2020-06-17 10:52:25 +02:00
|
|
|
|
def test_agenda_events_duplicate():
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now() + datetime.timedelta(days=1),
|
|
|
|
|
duration=10,
|
|
|
|
|
places=10,
|
|
|
|
|
label='event',
|
|
|
|
|
slug='event',
|
|
|
|
|
)
|
2021-07-08 17:27:31 +02:00
|
|
|
|
desk = Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
|
|
|
|
desk.import_timeperiod_exceptions_from_settings(enable=True)
|
2021-08-20 15:07:28 +02:00
|
|
|
|
TimePeriodException.objects.create(
|
2021-07-08 17:27:31 +02:00
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=now(),
|
|
|
|
|
end_datetime=now() + datetime.timedelta(minutes=30),
|
|
|
|
|
)
|
|
|
|
|
assert desk.timeperiodexception_set.count() == 34
|
2021-08-20 15:07:28 +02:00
|
|
|
|
AgendaNotificationsSettings.objects.create(
|
2021-07-08 16:49:30 +02:00
|
|
|
|
agenda=agenda,
|
|
|
|
|
full_event=AgendaNotificationsSettings.EMAIL_FIELD,
|
|
|
|
|
full_event_emails=['hop@entrouvert.com', 'top@entrouvert.com'],
|
|
|
|
|
)
|
2022-02-02 17:45:15 +01:00
|
|
|
|
AgendaReminderSettings.objects.create(agenda=agenda, days_before_email=1, email_extra_info='top')
|
2020-06-17 10:52:25 +02:00
|
|
|
|
|
|
|
|
|
new_agenda = agenda.duplicate()
|
|
|
|
|
assert new_agenda.pk != agenda.pk
|
|
|
|
|
assert new_agenda.kind == 'events'
|
2021-07-08 16:49:30 +02:00
|
|
|
|
assert new_agenda.notifications_settings.full_event == AgendaNotificationsSettings.EMAIL_FIELD
|
|
|
|
|
assert new_agenda.notifications_settings.full_event_emails == ['hop@entrouvert.com', 'top@entrouvert.com']
|
2022-02-02 17:45:15 +01:00
|
|
|
|
assert new_agenda.reminder_settings.days_before_email == 1
|
2021-07-08 16:49:30 +02:00
|
|
|
|
assert new_agenda.reminder_settings.email_extra_info == 'top'
|
2020-06-17 10:52:25 +02:00
|
|
|
|
|
2021-07-08 17:27:31 +02:00
|
|
|
|
new_desk = new_agenda.desk_set.get()
|
|
|
|
|
assert new_desk.slug == '_exceptions_holder'
|
|
|
|
|
assert new_desk.timeperiodexception_set.count() == 34
|
|
|
|
|
assert new_desk.timeperiodexception_set.filter(source__isnull=True).count() == 1
|
|
|
|
|
source = new_desk.timeperiodexceptionsource_set.get()
|
|
|
|
|
assert source.enabled is True
|
|
|
|
|
assert source.settings_slug == 'holidays'
|
|
|
|
|
|
2020-06-17 10:52:25 +02:00
|
|
|
|
new_event = new_agenda.event_set.first()
|
|
|
|
|
assert new_event.pk != event.pk
|
|
|
|
|
assert new_event.label == event.label
|
|
|
|
|
assert new_event.duration == event.duration
|
|
|
|
|
assert new_event.duration == event.duration
|
|
|
|
|
assert new_event.places == event.places
|
|
|
|
|
assert new_event.start_datetime == event.start_datetime
|
|
|
|
|
|
|
|
|
|
|
2021-07-01 15:52:27 +02:00
|
|
|
|
def test_agenda_events_recurrence_duplicate(freezer):
|
|
|
|
|
freezer.move_to('2021-01-06 12:00') # Wednesday
|
|
|
|
|
now_ = now()
|
|
|
|
|
end = now_ + datetime.timedelta(days=15)
|
|
|
|
|
orig_agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
Desk.objects.create(agenda=orig_agenda, slug='_exceptions_holder')
|
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=orig_agenda,
|
|
|
|
|
start_datetime=now_,
|
|
|
|
|
recurrence_days=[now_.weekday()],
|
|
|
|
|
label='Event',
|
|
|
|
|
places=10,
|
|
|
|
|
recurrence_end_date=end,
|
|
|
|
|
)
|
|
|
|
|
event.create_all_recurrences()
|
|
|
|
|
dup_agenda = orig_agenda.duplicate()
|
|
|
|
|
|
|
|
|
|
for agenda in (orig_agenda, dup_agenda):
|
|
|
|
|
assert agenda.event_set.count() == 4 # one recurring event, 3 real events
|
|
|
|
|
assert agenda.event_set.filter(recurrence_days__isnull=False).count() == 1
|
|
|
|
|
rec_event = agenda.event_set.filter(recurrence_days__isnull=False).first()
|
|
|
|
|
for event in agenda.event_set.filter(recurrence_days__isnull=True):
|
|
|
|
|
assert event.primary_event == rec_event
|
|
|
|
|
|
|
|
|
|
|
2020-06-17 10:52:25 +02:00
|
|
|
|
def test_agenda_virtual_duplicate():
|
|
|
|
|
agenda1 = Agenda.objects.create(label='Agenda 1', kind='meetings')
|
|
|
|
|
agenda2 = Agenda.objects.create(label='Agenda 2', kind='meetings')
|
|
|
|
|
virt_agenda = Agenda.objects.create(label='Virtual agenda', kind='virtual')
|
|
|
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda1)
|
|
|
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda2)
|
|
|
|
|
virt_agenda.save()
|
|
|
|
|
|
|
|
|
|
excluded_timeperiod = TimePeriod.objects.create(
|
|
|
|
|
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
new_agenda = virt_agenda.duplicate()
|
|
|
|
|
assert new_agenda.pk != virt_agenda.pk
|
|
|
|
|
assert new_agenda.kind == 'virtual'
|
|
|
|
|
|
|
|
|
|
new_time_period = new_agenda.excluded_timeperiods.first()
|
|
|
|
|
assert new_time_period.pk != excluded_timeperiod.pk
|
|
|
|
|
assert new_time_period.agenda == new_agenda
|
|
|
|
|
assert new_time_period.weekday == excluded_timeperiod.weekday
|
|
|
|
|
assert new_time_period.start_time == excluded_timeperiod.start_time
|
|
|
|
|
assert new_time_period.end_time == excluded_timeperiod.end_time
|
|
|
|
|
|
|
|
|
|
assert VirtualMember.objects.filter(virtual_agenda=new_agenda, real_agenda=agenda1).exists()
|
|
|
|
|
assert VirtualMember.objects.filter(virtual_agenda=new_agenda, real_agenda=agenda2).exists()
|
2020-07-09 12:46:13 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_agendas_cancel_events_command():
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10, label='Event')
|
|
|
|
|
|
2021-07-09 15:41:40 +02:00
|
|
|
|
for _ in range(5):
|
2020-07-09 12:46:13 +02:00
|
|
|
|
Booking.objects.create(event=event, cancel_callback_url='http://example.org/jump/trigger/')
|
|
|
|
|
|
|
|
|
|
event.cancellation_scheduled = True
|
|
|
|
|
event.save()
|
|
|
|
|
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
|
call_command('cancel_events')
|
|
|
|
|
assert mock_send.call_count == 5
|
|
|
|
|
|
|
|
|
|
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 5
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert not event.cancellation_scheduled
|
|
|
|
|
assert event.cancelled
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_agendas_cancel_events_command_network_error(freezer):
|
|
|
|
|
freezer.move_to('2020-01-01')
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10, label='Event')
|
|
|
|
|
|
|
|
|
|
def mocked_requests_connection_error(request, **kwargs):
|
|
|
|
|
if 'good' in request.url:
|
|
|
|
|
return mock.Mock(status_code=200)
|
|
|
|
|
raise requests.exceptions.ConnectionError('unreachable')
|
|
|
|
|
|
|
|
|
|
booking_good_url = Booking.objects.create(event=event, cancel_callback_url='http://good.org/')
|
2021-07-09 15:41:40 +02:00
|
|
|
|
for _ in range(5):
|
2020-07-09 12:46:13 +02:00
|
|
|
|
Booking.objects.create(event=event, cancel_callback_url='http://example.org/jump/trigger/')
|
|
|
|
|
|
|
|
|
|
event.cancellation_scheduled = True
|
|
|
|
|
event.save()
|
|
|
|
|
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
|
mock_send.side_effect = mocked_requests_connection_error
|
|
|
|
|
call_command('cancel_events')
|
|
|
|
|
assert mock_send.call_count == 6
|
|
|
|
|
|
|
|
|
|
booking_good_url.refresh_from_db()
|
|
|
|
|
assert booking_good_url.cancellation_datetime
|
|
|
|
|
assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 5
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert not event.cancellation_scheduled
|
|
|
|
|
assert not event.cancelled
|
|
|
|
|
|
|
|
|
|
report = EventCancellationReport.objects.get(event=event)
|
|
|
|
|
assert report.bookings.count() == 5
|
|
|
|
|
assert len(report.booking_errors) == 5
|
|
|
|
|
|
|
|
|
|
for booking in report.bookings.all():
|
|
|
|
|
assert report.booking_errors[str(booking.pk)] == 'unreachable'
|
|
|
|
|
|
|
|
|
|
# old reports are automatically removed
|
|
|
|
|
freezer.move_to('2020-03-01')
|
|
|
|
|
call_command('cancel_events')
|
|
|
|
|
assert not EventCancellationReport.objects.exists()
|
2020-07-16 15:12:47 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mock.patch('django.contrib.auth.models.Group.role', create=True)
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
'emails_to_members,emails',
|
|
|
|
|
[
|
|
|
|
|
(False, []),
|
|
|
|
|
(False, ['test@entrouvert.com']),
|
|
|
|
|
(True, []),
|
|
|
|
|
(True, ['test@entrouvert.com']),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_agenda_notifications_role_email(mocked_role, emails_to_members, emails, mailoutbox):
|
|
|
|
|
group = Group.objects.create(name='group')
|
|
|
|
|
user = User.objects.create(username='user', email='user@entrouvert.com')
|
|
|
|
|
user.groups.add(group)
|
|
|
|
|
mocked_role.emails_to_members = emails_to_members
|
|
|
|
|
mocked_role.emails = emails
|
|
|
|
|
expected_recipients = emails
|
|
|
|
|
if emails_to_members:
|
|
|
|
|
expected_recipients.append(user.email)
|
|
|
|
|
expected_email_count = 1 if emails else 0
|
|
|
|
|
|
|
|
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='event', edit_role=group)
|
|
|
|
|
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=10, start_datetime=now(), label='Hop')
|
|
|
|
|
settings = AgendaNotificationsSettings.objects.create(agenda=agenda)
|
|
|
|
|
settings.almost_full_event = AgendaNotificationsSettings.EDIT_ROLE
|
|
|
|
|
settings.save()
|
|
|
|
|
|
|
|
|
|
# book 9/10 places to reach almost full state
|
2021-07-09 15:41:40 +02:00
|
|
|
|
for _ in range(9):
|
2020-07-16 15:12:47 +02:00
|
|
|
|
Booking.objects.create(event=event)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.almost_full
|
|
|
|
|
|
|
|
|
|
call_command('send_email_notifications')
|
|
|
|
|
assert len(mailoutbox) == expected_email_count
|
|
|
|
|
if mailoutbox:
|
|
|
|
|
assert mailoutbox[0].recipients() == expected_recipients
|
|
|
|
|
assert mailoutbox[0].subject == 'Alert: event "Hop" is almost full (90%)'
|
2020-09-07 16:41:33 +02:00
|
|
|
|
assert 'manage/agendas/%s/events/%s/' % (agenda.id, event.id) in mailoutbox[0].body
|
|
|
|
|
assert 'manage/agendas/%s/events/%s/' % (agenda.id, event.id) in mailoutbox[0].alternatives[0][0]
|
2020-07-16 15:12:47 +02:00
|
|
|
|
|
|
|
|
|
# no new email on subsequent run
|
|
|
|
|
call_command('send_email_notifications')
|
|
|
|
|
assert len(mailoutbox) == expected_email_count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_agenda_notifications_email_list(mailoutbox):
|
|
|
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='event')
|
|
|
|
|
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=10, start_datetime=now(), label='Hop')
|
|
|
|
|
settings = AgendaNotificationsSettings.objects.create(agenda=agenda)
|
|
|
|
|
settings.full_event = AgendaNotificationsSettings.EMAIL_FIELD
|
|
|
|
|
settings.full_event_emails = recipients = ['hop@entrouvert.com', 'top@entrouvert.com']
|
|
|
|
|
settings.save()
|
|
|
|
|
|
2021-07-09 15:41:40 +02:00
|
|
|
|
for _ in range(10):
|
2020-07-16 15:12:47 +02:00
|
|
|
|
Booking.objects.create(event=event)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.full
|
|
|
|
|
|
|
|
|
|
call_command('send_email_notifications')
|
|
|
|
|
assert len(mailoutbox) == 1
|
|
|
|
|
assert mailoutbox[0].recipients() == recipients
|
|
|
|
|
assert mailoutbox[0].subject == 'Alert: event "Hop" is full'
|
|
|
|
|
assert (
|
|
|
|
|
'view it here: https://example.com/manage/agendas/%s/events/%s/'
|
|
|
|
|
% (
|
|
|
|
|
agenda.pk,
|
|
|
|
|
event.pk,
|
|
|
|
|
)
|
|
|
|
|
in mailoutbox[0].body
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# no new email on subsequent run
|
|
|
|
|
call_command('send_email_notifications')
|
|
|
|
|
assert len(mailoutbox) == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_agenda_notifications_cancelled(mailoutbox):
|
|
|
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='event')
|
|
|
|
|
|
|
|
|
|
event = Event.objects.create(agenda=agenda, places=10, start_datetime=now(), label='Hop')
|
|
|
|
|
settings = AgendaNotificationsSettings.objects.create(agenda=agenda)
|
|
|
|
|
settings.cancelled_event = AgendaNotificationsSettings.EMAIL_FIELD
|
|
|
|
|
settings.cancelled_event_emails = recipients = ['hop@entrouvert.com', 'top@entrouvert.com']
|
|
|
|
|
settings.save()
|
|
|
|
|
|
|
|
|
|
event.cancelled = True
|
|
|
|
|
event.save()
|
|
|
|
|
|
|
|
|
|
call_command('send_email_notifications')
|
|
|
|
|
assert len(mailoutbox) == 1
|
|
|
|
|
assert mailoutbox[0].recipients() == recipients
|
|
|
|
|
assert mailoutbox[0].subject == 'Alert: event "Hop" is cancelled'
|
|
|
|
|
|
|
|
|
|
# no new email on subsequent run
|
|
|
|
|
call_command('send_email_notifications')
|
|
|
|
|
assert len(mailoutbox) == 1
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_agenda_reminders(mailoutbox, freezer):
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
|
|
|
|
|
|
|
|
|
# add some old event with booking
|
|
|
|
|
freezer.move_to('2019-01-01')
|
|
|
|
|
old_event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10, label='Event')
|
|
|
|
|
Booking.objects.create(event=old_event, user_email='old@test.org')
|
|
|
|
|
|
|
|
|
|
# no reminder configured
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert len(mailoutbox) == 0
|
|
|
|
|
|
|
|
|
|
# move to present day
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
# configure reminder the day before
|
2022-02-02 17:45:15 +01:00
|
|
|
|
AgendaReminderSettings.objects.create(agenda=agenda, days_before_email=1)
|
2020-09-15 14:05:38 +02:00
|
|
|
|
# event starts in 2 days
|
|
|
|
|
start_datetime = now() + datetime.timedelta(days=2)
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=start_datetime, places=10, label='Event')
|
|
|
|
|
|
2021-07-09 15:41:40 +02:00
|
|
|
|
for _ in range(5):
|
|
|
|
|
Booking.objects.create(event=event, user_email='t@test.org')
|
2020-09-15 14:05:38 +02:00
|
|
|
|
# extra booking with no email, should be ignored
|
2021-07-09 15:41:40 +02:00
|
|
|
|
Booking.objects.create(event=event)
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-02 10:00')
|
|
|
|
|
# not time to send reminders yet
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert len(mailoutbox) == 0
|
|
|
|
|
|
|
|
|
|
# one of the booking is cancelled
|
|
|
|
|
Booking.objects.filter(user_email='t@test.org').first().cancel()
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert len(mailoutbox) == 4
|
|
|
|
|
mailoutbox.clear()
|
|
|
|
|
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert len(mailoutbox) == 0
|
|
|
|
|
|
|
|
|
|
# booking is placed the day of the event, notfication should no be sent
|
|
|
|
|
freezer.move_to('2020-01-03 08:00')
|
2021-07-09 15:41:40 +02:00
|
|
|
|
Booking.objects.create(event=event, user_email='t@test.org')
|
2020-09-15 14:05:38 +02:00
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert len(mailoutbox) == 0
|
|
|
|
|
|
|
|
|
|
|
2022-02-02 16:28:57 +01:00
|
|
|
|
@pytest.mark.freeze_time('2020-01-01 14:00')
|
|
|
|
|
def test_agenda_reminders_extra_emails(mailoutbox, freezer):
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
2022-02-02 17:45:15 +01:00
|
|
|
|
AgendaReminderSettings.objects.create(agenda=agenda, days_before_email=1)
|
2022-02-02 16:28:57 +01:00
|
|
|
|
start_datetime = now() + datetime.timedelta(days=2)
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=start_datetime, places=10, label='Event')
|
|
|
|
|
|
|
|
|
|
Booking.objects.create(
|
|
|
|
|
event=event,
|
|
|
|
|
user_email='t@test.org',
|
|
|
|
|
extra_emails=['t@test.org', 'u@test.org', 'v@test.org'],
|
|
|
|
|
)
|
|
|
|
|
Booking.objects.create(event=event, extra_emails=['w@test.org'])
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
|
|
|
|
|
assert len(mailoutbox) == 4
|
|
|
|
|
assert all(len(x.to) == 1 for x in mailoutbox)
|
|
|
|
|
assert {x.to[0] for x in mailoutbox} == {'t@test.org', 'u@test.org', 'v@test.org', 'w@test.org'}
|
|
|
|
|
|
|
|
|
|
|
2020-09-17 12:13:15 +02:00
|
|
|
|
@override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO')
|
2020-09-15 14:05:38 +02:00
|
|
|
|
def test_agenda_reminders_sms(freezer):
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
2022-02-02 17:45:15 +01:00
|
|
|
|
AgendaReminderSettings.objects.create(agenda=agenda, days_before_sms=1)
|
2020-09-15 14:05:38 +02:00
|
|
|
|
start_datetime = now() + datetime.timedelta(days=2)
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=start_datetime, places=10, label='Event')
|
|
|
|
|
|
2021-07-09 15:41:40 +02:00
|
|
|
|
for _ in range(5):
|
|
|
|
|
Booking.objects.create(event=event, user_phone_number='+336123456789')
|
|
|
|
|
Booking.objects.create(event=event)
|
2022-02-02 16:28:57 +01:00
|
|
|
|
Booking.objects.create(
|
|
|
|
|
event=event,
|
|
|
|
|
user_phone_number='+33111111111',
|
|
|
|
|
extra_phone_numbers=['+33111111111', '+33222222222', '+33333333333'],
|
|
|
|
|
)
|
|
|
|
|
Booking.objects.create(event=event, extra_phone_numbers=['+336123456789'])
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
|
2022-02-02 16:28:57 +01:00
|
|
|
|
assert mock_send.call_count == 7
|
|
|
|
|
body = json.loads(mock_send.call_args_list[0][0][0].body.decode())
|
2020-09-15 14:05:38 +02:00
|
|
|
|
assert body['from'] == 'EO'
|
|
|
|
|
assert body['to'] == ['+336123456789']
|
|
|
|
|
|
2022-02-02 16:28:57 +01:00
|
|
|
|
body = json.loads(mock_send.call_args_list[5][0][0].body.decode())
|
|
|
|
|
assert body['from'] == 'EO'
|
|
|
|
|
assert set(body['to']) == {'+33111111111', '+33222222222', '+33333333333'}
|
|
|
|
|
|
|
|
|
|
body = json.loads(mock_send.call_args_list[6][0][0].body.decode())
|
|
|
|
|
assert body['from'] == 'EO'
|
|
|
|
|
assert set(body['to']) == {'+336123456789'}
|
|
|
|
|
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
2020-09-17 12:13:15 +02:00
|
|
|
|
@override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO')
|
2020-09-15 14:05:38 +02:00
|
|
|
|
def test_agenda_reminders_retry(freezer):
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
2022-02-02 17:45:15 +01:00
|
|
|
|
settings = AgendaReminderSettings.objects.create(agenda=agenda)
|
2020-09-15 14:05:38 +02:00
|
|
|
|
start_datetime = now() + datetime.timedelta(days=2)
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=start_datetime, places=10, label='Event')
|
|
|
|
|
|
2022-02-02 17:45:15 +01:00
|
|
|
|
settings.days_before_email = 1
|
2020-09-15 14:05:38 +02:00
|
|
|
|
settings.save()
|
|
|
|
|
booking = Booking.objects.create(event=event, user_email='t@test.org')
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
|
|
|
|
|
def send_mail_error(*args, **kwargs):
|
|
|
|
|
raise smtplib.SMTPException
|
|
|
|
|
|
2022-02-14 17:31:01 +01:00
|
|
|
|
with mock.patch('chrono.agendas.management.commands.utils.send_mail') as mock_send:
|
2020-09-15 14:05:38 +02:00
|
|
|
|
mock_send.return_value = None
|
|
|
|
|
mock_send.side_effect = send_mail_error
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert mock_send.call_count == 1
|
|
|
|
|
booking.refresh_from_db()
|
2022-02-02 17:45:15 +01:00
|
|
|
|
assert not booking.email_reminder_datetime
|
|
|
|
|
assert not booking.sms_reminder_datetime
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
mock_send.side_effect = None
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert mock_send.call_count == 2
|
|
|
|
|
booking.refresh_from_db()
|
2022-02-02 17:45:15 +01:00
|
|
|
|
assert booking.email_reminder_datetime
|
|
|
|
|
assert not booking.sms_reminder_datetime
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
2022-02-02 17:45:15 +01:00
|
|
|
|
settings.days_before_email = None
|
|
|
|
|
settings.days_before_sms = 1
|
2020-09-15 14:05:38 +02:00
|
|
|
|
settings.save()
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
booking = Booking.objects.create(event=event, user_phone_number='+336123456789')
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
|
|
|
|
|
def mocked_requests_connection_error(*args, **kwargs):
|
|
|
|
|
raise requests.ConnectionError('unreachable')
|
|
|
|
|
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
|
|
|
mock_send.side_effect = mocked_requests_connection_error
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert mock_send.call_count == 1
|
|
|
|
|
booking.refresh_from_db()
|
2022-02-02 17:45:15 +01:00
|
|
|
|
assert not booking.sms_reminder_datetime
|
|
|
|
|
assert not booking.email_reminder_datetime
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
mock_send.side_effect = None
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert mock_send.call_count == 2
|
|
|
|
|
booking.refresh_from_db()
|
2022-02-02 17:45:15 +01:00
|
|
|
|
assert booking.sms_reminder_datetime
|
|
|
|
|
assert not booking.email_reminder_datetime
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
2022-02-02 17:45:15 +01:00
|
|
|
|
settings.days_before_email = 1
|
2020-09-15 14:05:38 +02:00
|
|
|
|
settings.save()
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
booking = Booking.objects.create(event=event, user_phone_number='+336123456789', user_email='t@test.org')
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send, mock.patch(
|
2022-02-14 17:31:01 +01:00
|
|
|
|
'chrono.agendas.management.commands.utils.send_mail'
|
2020-09-15 14:05:38 +02:00
|
|
|
|
) as mock_send_mail:
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
|
mock_send_mail.return_value = None
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
|
|
|
|
|
assert mock_send.call_count == 1
|
|
|
|
|
assert mock_send_mail.call_count == 1
|
|
|
|
|
booking.refresh_from_db()
|
2022-02-02 17:45:15 +01:00
|
|
|
|
assert booking.sms_reminder_datetime
|
|
|
|
|
assert booking.email_reminder_datetime
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert mock_send.call_count == 1
|
|
|
|
|
assert mock_send_mail.call_count == 1
|
|
|
|
|
|
|
|
|
|
|
2022-02-02 17:45:15 +01:00
|
|
|
|
@override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO')
|
|
|
|
|
def test_agenda_reminders_different_days_before(freezer):
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
|
|
|
|
AgendaReminderSettings.objects.create(agenda=agenda, days_before_email=1, days_before_sms=2)
|
|
|
|
|
start_datetime = now() + datetime.timedelta(days=3)
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=start_datetime, places=10, label='Event')
|
|
|
|
|
Booking.objects.create(event=event, user_email='t@test.org', user_phone_number='+336123456789')
|
|
|
|
|
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send_sms, mock.patch(
|
2022-02-14 17:31:01 +01:00
|
|
|
|
'chrono.agendas.management.commands.utils.send_mail'
|
2022-02-02 17:45:15 +01:00
|
|
|
|
) as mock_send_mail:
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send_sms.return_value = mock_response
|
|
|
|
|
mock_send_mail.return_value = None
|
|
|
|
|
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert mock_send_sms.call_count == 0
|
|
|
|
|
assert mock_send_mail.call_count == 0
|
|
|
|
|
|
|
|
|
|
# two days before
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert mock_send_sms.call_count == 1
|
|
|
|
|
assert mock_send_mail.call_count == 0
|
|
|
|
|
|
|
|
|
|
# one day before
|
|
|
|
|
freezer.move_to('2020-01-03 15:00')
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert mock_send_sms.call_count == 1
|
|
|
|
|
assert mock_send_mail.call_count == 1
|
|
|
|
|
|
|
|
|
|
|
2020-09-15 14:05:38 +02:00
|
|
|
|
@override_settings(TIME_ZONE='UTC')
|
|
|
|
|
def test_agenda_reminders_email_content(mailoutbox, freezer):
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
2021-07-09 15:41:40 +02:00
|
|
|
|
AgendaReminderSettings.objects.create(
|
2022-02-02 17:45:15 +01:00
|
|
|
|
agenda=agenda, days_before_email=1, email_extra_info='Do no forget ID card.'
|
2020-09-15 14:05:38 +02:00
|
|
|
|
)
|
|
|
|
|
start_datetime = now() + datetime.timedelta(days=2)
|
2020-12-08 17:24:03 +01:00
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=start_datetime,
|
|
|
|
|
places=10,
|
|
|
|
|
label='Pool party',
|
|
|
|
|
description='Come !',
|
|
|
|
|
url='https://example.party',
|
|
|
|
|
pricing='10€',
|
|
|
|
|
)
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
2021-07-09 15:41:40 +02:00
|
|
|
|
Booking.objects.create(event=event, user_email='t@test.org')
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
|
|
|
|
|
mail = mailoutbox[0]
|
|
|
|
|
assert mail.subject == 'Reminder for your booking tomorrow at 2 p.m.'
|
|
|
|
|
mail_bodies = (mail.body, mail.alternatives[0][0])
|
|
|
|
|
for body in mail_bodies:
|
|
|
|
|
assert 'Hi,' in body
|
2022-10-06 15:03:17 +02:00
|
|
|
|
assert 'You have booked event "Pool party", on Friday 3 January at 2 p.m..' in body
|
2020-09-15 14:05:38 +02:00
|
|
|
|
assert 'Do no forget ID card.' in body
|
2020-12-08 17:24:03 +01:00
|
|
|
|
assert 'Come !' in body
|
|
|
|
|
assert 'Pricing: 10€' in body
|
2021-08-20 15:07:28 +02:00
|
|
|
|
assert 'cancel' not in body
|
|
|
|
|
assert 'if present' not in body # assert separation with preview code
|
2020-12-08 17:24:03 +01:00
|
|
|
|
assert 'More information: https://example.party' in mail_bodies[0]
|
|
|
|
|
assert '<a href="https://example.party">More information</a>' in mail_bodies[1]
|
2020-09-15 14:05:38 +02:00
|
|
|
|
mailoutbox.clear()
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
2021-07-09 15:41:40 +02:00
|
|
|
|
Booking.objects.create(event=event, user_email='t@test.org', form_url='https://example.org/')
|
2020-09-15 14:05:38 +02:00
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
|
|
|
|
|
mail = mailoutbox[0]
|
|
|
|
|
assert 'If in need to cancel it, you can do so here: https://example.org/' in mail.body
|
|
|
|
|
assert 'Edit or cancel booking' in mail.alternatives[0][0]
|
|
|
|
|
assert 'href="https://example.org/"' in mail.alternatives[0][0]
|
|
|
|
|
|
2021-09-21 14:20:26 +02:00
|
|
|
|
# check url translation
|
|
|
|
|
Booking.objects.all().delete()
|
|
|
|
|
mailoutbox.clear()
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
Booking.objects.create(event=event, user_email='t@test.org', form_url='publik://default/someform/1/')
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
|
|
|
|
|
mail = mailoutbox[0]
|
|
|
|
|
assert 'If in need to cancel it, you can do so here: http://example.org/someform/1/' in mail.body
|
|
|
|
|
assert 'Edit or cancel booking' in mail.alternatives[0][0]
|
|
|
|
|
assert 'href="http://example.org/someform/1/"' in mail.alternatives[0][0]
|
|
|
|
|
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
2020-09-17 12:13:15 +02:00
|
|
|
|
@override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO', TIME_ZONE='UTC')
|
2020-09-15 14:05:38 +02:00
|
|
|
|
def test_agenda_reminders_sms_content(freezer):
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
|
|
|
|
AgendaReminderSettings.objects.create(
|
2022-02-02 17:45:15 +01:00
|
|
|
|
agenda=agenda, days_before_sms=1, sms_extra_info='Do no forget ID card.'
|
2020-09-15 14:05:38 +02:00
|
|
|
|
)
|
|
|
|
|
start_datetime = now() + datetime.timedelta(days=2)
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=start_datetime, places=10, label='Pool party')
|
|
|
|
|
|
2021-07-09 15:41:40 +02:00
|
|
|
|
Booking.objects.create(event=event, user_phone_number='+336123456789')
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
|
|
|
|
|
body = json.loads(mock_send.call_args[0][0].body.decode())
|
|
|
|
|
assert (
|
|
|
|
|
body['message']
|
2022-10-06 15:03:17 +02:00
|
|
|
|
== 'Reminder: you have booked event "Pool party", on 03/01 at 2 p.m.. Do no forget ID card.'
|
2020-09-15 14:05:38 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2022-02-14 16:10:35 +01:00
|
|
|
|
@override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO', TIME_ZONE='UTC')
|
|
|
|
|
def test_agenda_reminders_templated_content(mailoutbox, freezer):
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
agenda = Agenda.objects.create(label='Main Center', kind='events')
|
|
|
|
|
AgendaReminderSettings.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
days_before_email=1,
|
|
|
|
|
days_before_sms=1,
|
|
|
|
|
email_extra_info='Go to {{ booking.event.agenda.label }}.\nTake your {{ booking.extra_data.document_type }}.',
|
|
|
|
|
sms_extra_info='Take your {{ booking.extra_data.document_type }}.',
|
|
|
|
|
)
|
|
|
|
|
start_datetime = now() + datetime.timedelta(days=2)
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=start_datetime, places=10, label='Pool party')
|
|
|
|
|
|
|
|
|
|
Booking.objects.create(
|
|
|
|
|
event=event,
|
|
|
|
|
user_email='t@test.org',
|
|
|
|
|
user_phone_number='+336123456789',
|
|
|
|
|
extra_data={'document_type': '"receipt"'},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
|
|
|
|
|
mail = mailoutbox[0]
|
|
|
|
|
assert 'Go to Main Center.\nTake your "receipt".' in mail.body
|
|
|
|
|
assert '<p>Go to Main Center.<br>Take your "receipt".</p>' in mail.alternatives[0][0]
|
|
|
|
|
|
|
|
|
|
body = json.loads(mock_send.call_args[0][0].body.decode())
|
|
|
|
|
assert 'Take your "receipt".' in body['message']
|
|
|
|
|
|
|
|
|
|
# in case of invalid template, send anyway
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
Booking.objects.create(event=event, user_email='t@test.org', user_phone_number='+336123456789')
|
|
|
|
|
agenda.reminder_settings.email_extra_info = 'Take your {{ syntax error }}'
|
|
|
|
|
agenda.reminder_settings.sms_extra_info = 'Take your {{ syntax error }}'
|
|
|
|
|
agenda.reminder_settings.save()
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
|
|
|
|
mock_response = mock.Mock(status_code=200)
|
|
|
|
|
mock_send.return_value = mock_response
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
|
|
|
|
|
assert len(mailoutbox) == 2
|
|
|
|
|
assert 'Take your' not in mailoutbox[1].body
|
|
|
|
|
|
|
|
|
|
body = json.loads(mock_send.call_args[0][0].body.decode())
|
|
|
|
|
assert 'Take your' not in body['message']
|
|
|
|
|
|
|
|
|
|
|
2020-09-15 14:05:38 +02:00
|
|
|
|
@override_settings(TIME_ZONE='UTC')
|
|
|
|
|
def test_agenda_reminders_meetings(mailoutbox, freezer):
|
|
|
|
|
freezer.move_to('2020-01-01 11:00')
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='meetings')
|
|
|
|
|
desk = Desk.objects.create(agenda=agenda, label='Desk')
|
|
|
|
|
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
|
2021-07-09 15:41:40 +02:00
|
|
|
|
TimePeriod.objects.create(
|
2020-09-15 14:05:38 +02:00
|
|
|
|
desk=desk, weekday=now().weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
|
|
|
|
|
)
|
2022-02-02 17:45:15 +01:00
|
|
|
|
AgendaReminderSettings.objects.create(agenda=agenda, days_before_email=2)
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
places=1,
|
|
|
|
|
desk=desk,
|
|
|
|
|
meeting_type=meetingtype,
|
|
|
|
|
start_datetime=now() + datetime.timedelta(days=5), # 06/01
|
|
|
|
|
)
|
2021-12-07 10:10:30 +01:00
|
|
|
|
Booking.objects.create(
|
|
|
|
|
event=event,
|
|
|
|
|
user_email='t@test.org',
|
|
|
|
|
user_display_label='Birth certificate',
|
|
|
|
|
form_url='publik://default/someform/1/',
|
|
|
|
|
)
|
2020-09-15 14:05:38 +02:00
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-04 15:00')
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert len(mailoutbox) == 1
|
|
|
|
|
|
|
|
|
|
mail = mailoutbox[0]
|
|
|
|
|
assert mail.subject == 'Reminder for your meeting in 2 days at 11 a.m.'
|
|
|
|
|
assert 'Your meeting "Birth certificate" is scheduled on Monday 6 January at 11 a.m..' in mail.body
|
2021-12-07 10:10:30 +01:00
|
|
|
|
assert 'If in need to cancel it, you can do so here: http://example.org/someform/1/' in mail.body
|
|
|
|
|
assert 'href="http://example.org/someform/1/"' in mail.alternatives[0][0]
|
2020-08-13 11:48:52 +02:00
|
|
|
|
|
|
|
|
|
|
2022-09-20 10:08:43 +02:00
|
|
|
|
def test_agenda_reminders_waiting_list(mailoutbox, freezer):
|
|
|
|
|
agenda = Agenda.objects.create(label='Events', kind='events')
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-01 14:00')
|
|
|
|
|
# configure reminder the day before
|
|
|
|
|
AgendaReminderSettings.objects.create(agenda=agenda, days_before_email=1)
|
|
|
|
|
# event starts in 2 days
|
|
|
|
|
start_datetime = now() + datetime.timedelta(days=2)
|
|
|
|
|
event = Event.objects.create(agenda=agenda, start_datetime=start_datetime, places=10, label='Event')
|
|
|
|
|
|
|
|
|
|
for _ in range(5):
|
|
|
|
|
Booking.objects.create(event=event, user_email='t@test.org')
|
|
|
|
|
# extra booking in waiting list, should be ignored
|
|
|
|
|
Booking.objects.create(event=event, user_email='t@test.org', in_waiting_list=True)
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-01-02 15:00')
|
|
|
|
|
call_command('send_booking_reminders')
|
|
|
|
|
assert len(mailoutbox) == 5
|
|
|
|
|
mailoutbox.clear()
|
|
|
|
|
|
|
|
|
|
|
2020-08-13 11:48:52 +02:00
|
|
|
|
def test_anonymize_bookings(freezer):
|
|
|
|
|
day = datetime.datetime(year=2020, month=1, day=1)
|
|
|
|
|
freezer.move_to(day)
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
2020-12-08 16:31:41 +01:00
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda, start_datetime=now() + datetime.timedelta(days=10), places=10, label='Event'
|
|
|
|
|
)
|
2020-08-13 11:48:52 +02:00
|
|
|
|
|
2021-07-09 15:41:40 +02:00
|
|
|
|
for _ in range(5):
|
2020-08-13 11:48:52 +02:00
|
|
|
|
Booking.objects.create(
|
|
|
|
|
event=event,
|
|
|
|
|
extra_data={'test': True},
|
|
|
|
|
label='john',
|
|
|
|
|
user_display_label='john',
|
|
|
|
|
user_external_id='john',
|
2021-04-16 10:11:58 +02:00
|
|
|
|
user_first_name='john',
|
|
|
|
|
user_last_name='doe',
|
2020-08-13 11:48:52 +02:00
|
|
|
|
backoffice_url='https://example.org',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
freezer.move_to(day + datetime.timedelta(days=50))
|
|
|
|
|
new_event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10, label='Event')
|
|
|
|
|
booking = Booking.objects.create(event=new_event, label='hop')
|
|
|
|
|
|
|
|
|
|
freezer.move_to(day + datetime.timedelta(days=101))
|
|
|
|
|
call_command('anonymize_bookings')
|
|
|
|
|
assert not Booking.objects.filter(anonymization_datetime__isnull=False).exists()
|
|
|
|
|
|
|
|
|
|
# now ask for anonymization
|
|
|
|
|
agenda.anonymize_delay = 100
|
|
|
|
|
agenda.save()
|
|
|
|
|
|
2020-12-08 16:31:41 +01:00
|
|
|
|
call_command('anonymize_bookings')
|
|
|
|
|
# bookings were placed more than 100 days ago but event was only 90 days ago
|
|
|
|
|
assert not Booking.objects.filter(anonymization_datetime__isnull=False).exists()
|
|
|
|
|
|
|
|
|
|
freezer.move_to(day + datetime.timedelta(days=111))
|
2020-08-13 11:48:52 +02:00
|
|
|
|
call_command('anonymize_bookings')
|
|
|
|
|
assert (
|
|
|
|
|
Booking.objects.filter(
|
|
|
|
|
label='',
|
|
|
|
|
user_display_label='',
|
|
|
|
|
user_external_id='',
|
2021-04-16 10:11:58 +02:00
|
|
|
|
user_first_name='',
|
|
|
|
|
user_last_name='',
|
2020-08-13 11:48:52 +02:00
|
|
|
|
backoffice_url='https://example.org',
|
|
|
|
|
extra_data={},
|
|
|
|
|
anonymization_datetime=now(),
|
|
|
|
|
).count()
|
|
|
|
|
== 5
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
booking.refresh_from_db()
|
|
|
|
|
assert booking.label == 'hop'
|
|
|
|
|
assert not booking.anonymization_datetime
|
2020-12-22 17:26:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_recurring_events(freezer):
|
|
|
|
|
freezer.move_to('2021-01-06 12:00') # Wednesday
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now(),
|
2021-04-21 16:21:31 +02:00
|
|
|
|
recurrence_days=[now().weekday()],
|
2020-12-22 17:26:29 +01:00
|
|
|
|
label='Event',
|
|
|
|
|
places=10,
|
|
|
|
|
waiting_list_places=10,
|
|
|
|
|
duration=10,
|
|
|
|
|
description='Description',
|
|
|
|
|
url='https://example.com',
|
|
|
|
|
pricing='10€',
|
|
|
|
|
)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
|
|
|
|
|
recurrences = event.get_recurrences(localtime(), localtime() + datetime.timedelta(days=15))
|
|
|
|
|
assert len(recurrences) == 3
|
|
|
|
|
|
|
|
|
|
first_event = recurrences[0]
|
2021-01-19 15:35:31 +01:00
|
|
|
|
assert first_event.slug == event.slug + '--2021-01-06-1300'
|
2020-12-22 17:26:29 +01:00
|
|
|
|
|
|
|
|
|
event_json = event.export_json()
|
|
|
|
|
first_event_json = first_event.export_json()
|
2021-04-21 16:21:31 +02:00
|
|
|
|
different_fields = ['slug', 'recurrence_days', 'recurrence_week_interval']
|
2020-12-22 17:26:29 +01:00
|
|
|
|
assert all(first_event_json[k] == event_json[k] for k in event_json if k not in different_fields)
|
|
|
|
|
|
|
|
|
|
second_event = recurrences[1]
|
|
|
|
|
assert second_event.start_datetime == first_event.start_datetime + datetime.timedelta(days=7)
|
|
|
|
|
assert second_event.start_datetime.weekday() == first_event.start_datetime.weekday()
|
2021-01-19 15:35:31 +01:00
|
|
|
|
assert second_event.slug == 'event--2021-01-13-1300'
|
2020-12-22 17:26:29 +01:00
|
|
|
|
|
|
|
|
|
different_fields = ['slug', 'start_datetime']
|
|
|
|
|
second_event_json = second_event.export_json()
|
|
|
|
|
assert all(first_event_json[k] == second_event_json[k] for k in event_json if k not in different_fields)
|
|
|
|
|
|
|
|
|
|
new_recurrences = event.get_recurrences(
|
|
|
|
|
localtime() + datetime.timedelta(days=15),
|
|
|
|
|
localtime() + datetime.timedelta(days=30),
|
|
|
|
|
)
|
|
|
|
|
assert len(recurrences) == 3
|
|
|
|
|
assert new_recurrences[0].start_datetime == recurrences[-1].start_datetime + datetime.timedelta(days=7)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_recurring_events_dst(freezer, settings):
|
|
|
|
|
freezer.move_to('2020-10-24 12:00')
|
|
|
|
|
settings.TIME_ZONE = 'Europe/Brussels'
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
2021-04-21 16:21:31 +02:00
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda, start_datetime=now(), recurrence_days=[now().weekday()], places=5
|
|
|
|
|
)
|
2020-12-22 17:26:29 +01:00
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
dt = localtime()
|
|
|
|
|
recurrences = event.get_recurrences(dt, dt + datetime.timedelta(days=8))
|
|
|
|
|
event_before_dst, event_after_dst = recurrences
|
|
|
|
|
assert event_before_dst.start_datetime.hour + 1 == event_after_dst.start_datetime.hour
|
2021-01-19 15:35:31 +01:00
|
|
|
|
assert event_before_dst.slug == 'agenda-event--2020-10-24-1400'
|
|
|
|
|
assert event_after_dst.slug == 'agenda-event--2020-10-31-1400'
|
2020-12-22 17:26:29 +01:00
|
|
|
|
|
|
|
|
|
freezer.move_to('2020-11-24 12:00')
|
|
|
|
|
new_recurrences = event.get_recurrences(dt, dt + datetime.timedelta(days=8))
|
|
|
|
|
new_event_before_dst, new_event_after_dst = new_recurrences
|
|
|
|
|
assert event_before_dst.start_datetime == new_event_before_dst.start_datetime
|
|
|
|
|
assert event_after_dst.start_datetime == new_event_after_dst.start_datetime
|
|
|
|
|
assert event_before_dst.slug == new_event_before_dst.slug
|
|
|
|
|
assert event_after_dst.slug == new_event_after_dst.slug
|
|
|
|
|
|
|
|
|
|
|
2021-04-21 16:21:31 +02:00
|
|
|
|
def test_recurring_events_repetition(freezer):
|
2020-12-22 17:26:29 +01:00
|
|
|
|
freezer.move_to('2021-01-06 12:00') # Wednesday
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now(),
|
2021-04-21 16:21:31 +02:00
|
|
|
|
recurrence_days=list(range(7)), # everyday
|
2020-12-22 17:26:29 +01:00
|
|
|
|
places=5,
|
|
|
|
|
)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
start_datetime = localtime(event.start_datetime)
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2021-01-06 12:01') # recurrence on same day should not be returned
|
|
|
|
|
recurrences = event.get_recurrences(
|
|
|
|
|
localtime() + datetime.timedelta(days=1), localtime() + datetime.timedelta(days=7)
|
|
|
|
|
)
|
|
|
|
|
assert len(recurrences) == 6
|
|
|
|
|
assert recurrences[0].start_datetime == start_datetime + datetime.timedelta(days=2)
|
|
|
|
|
assert recurrences[-1].start_datetime == start_datetime + datetime.timedelta(days=7)
|
|
|
|
|
for i in range(len(recurrences) - 1):
|
|
|
|
|
assert recurrences[i].start_datetime + datetime.timedelta(days=1) == recurrences[i + 1].start_datetime
|
|
|
|
|
|
2021-04-21 16:21:31 +02:00
|
|
|
|
event.recurrence_days = list(range(5)) # from Monday to Friday
|
2020-12-22 17:26:29 +01:00
|
|
|
|
event.save()
|
|
|
|
|
recurrences = event.get_recurrences(
|
|
|
|
|
localtime() + datetime.timedelta(days=1), localtime() + datetime.timedelta(days=7)
|
|
|
|
|
)
|
|
|
|
|
assert len(recurrences) == 4
|
|
|
|
|
assert recurrences[0].start_datetime == start_datetime + datetime.timedelta(days=2)
|
|
|
|
|
assert recurrences[1].start_datetime == start_datetime + datetime.timedelta(days=5)
|
|
|
|
|
assert recurrences[-1].start_datetime == start_datetime + datetime.timedelta(days=7)
|
|
|
|
|
|
2021-04-21 16:21:31 +02:00
|
|
|
|
event.recurrence_days = [localtime(event.start_datetime).weekday()] # from Monday to Friday
|
|
|
|
|
event.recurrence_week_interval = 2
|
2020-12-22 17:26:29 +01:00
|
|
|
|
event.save()
|
|
|
|
|
recurrences = event.get_recurrences(
|
|
|
|
|
localtime() + datetime.timedelta(days=3), localtime() + datetime.timedelta(days=45)
|
|
|
|
|
)
|
|
|
|
|
assert len(recurrences) == 3
|
|
|
|
|
assert recurrences[0].start_datetime == start_datetime + datetime.timedelta(days=14)
|
|
|
|
|
assert recurrences[-1].start_datetime == start_datetime + datetime.timedelta(days=14) * len(recurrences)
|
|
|
|
|
for i in range(len(recurrences) - 1):
|
|
|
|
|
assert (
|
|
|
|
|
recurrences[i].start_datetime + datetime.timedelta(days=14) == recurrences[i + 1].start_datetime
|
|
|
|
|
)
|
2021-01-13 15:08:40 +01:00
|
|
|
|
|
2021-04-21 16:21:31 +02:00
|
|
|
|
event.recurrence_days = [3] # Tuesday but start_datetime is a Wednesday
|
|
|
|
|
event.recurrence_week_interval = 1
|
|
|
|
|
event.save()
|
|
|
|
|
recurrences = event.get_recurrences(localtime(), localtime() + datetime.timedelta(days=10))
|
|
|
|
|
assert len(recurrences) == 2
|
|
|
|
|
# no recurrence exist on Wednesday
|
|
|
|
|
assert all(localtime(r.start_datetime).weekday() == 3 for r in recurrences)
|
|
|
|
|
|
2021-01-13 15:08:40 +01:00
|
|
|
|
|
2021-03-24 11:42:33 +01:00
|
|
|
|
@pytest.mark.freeze_time('2021-01-06')
|
2021-01-13 15:08:40 +01:00
|
|
|
|
def test_recurring_events_with_end_date():
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now(),
|
2021-04-21 16:21:31 +02:00
|
|
|
|
recurrence_days=list(range(7)),
|
2021-01-13 15:08:40 +01:00
|
|
|
|
places=5,
|
|
|
|
|
recurrence_end_date=(now() + datetime.timedelta(days=5)).date(),
|
|
|
|
|
)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
start_datetime = localtime(event.start_datetime)
|
|
|
|
|
|
|
|
|
|
recurrences = event.get_recurrences(
|
|
|
|
|
localtime(event.start_datetime), localtime(event.start_datetime) + datetime.timedelta(days=10)
|
|
|
|
|
)
|
|
|
|
|
assert len(recurrences) == 5
|
|
|
|
|
assert recurrences[0].start_datetime == start_datetime
|
|
|
|
|
assert recurrences[-1].start_datetime == start_datetime + datetime.timedelta(days=4)
|
2021-03-22 15:01:26 +01:00
|
|
|
|
|
|
|
|
|
|
2021-01-28 15:40:57 +01:00
|
|
|
|
@override_settings(
|
|
|
|
|
EXCEPTIONS_SOURCES={
|
|
|
|
|
'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
def test_recurring_events_exceptions(freezer):
|
|
|
|
|
freezer.move_to('2021-05-01 12:00')
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
2021-06-30 15:50:00 +02:00
|
|
|
|
desk = Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
|
|
|
|
desk.import_timeperiod_exceptions_from_settings()
|
2021-01-28 15:40:57 +01:00
|
|
|
|
|
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now(),
|
2021-04-21 16:21:31 +02:00
|
|
|
|
recurrence_days=list(range(7)),
|
2021-01-28 15:40:57 +01:00
|
|
|
|
places=5,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
recurrences = event.get_recurrences(now(), now() + datetime.timedelta(days=7))
|
|
|
|
|
assert recurrences[0].start_datetime.strftime('%m-%d') == '05-01'
|
|
|
|
|
|
|
|
|
|
desk.import_timeperiod_exceptions_from_settings(enable=True)
|
|
|
|
|
recurrences = event.get_recurrences(now(), now() + datetime.timedelta(days=7))
|
|
|
|
|
# 05-01 is a holiday
|
|
|
|
|
assert recurrences[0].start_datetime.strftime('%m-%d') == '05-02'
|
|
|
|
|
first_event = recurrences[0]
|
|
|
|
|
|
|
|
|
|
# exception before first_event start_datetime
|
|
|
|
|
time_period_exception = TimePeriodException.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=first_event.start_datetime - datetime.timedelta(hours=1),
|
|
|
|
|
end_datetime=first_event.start_datetime - datetime.timedelta(minutes=30),
|
|
|
|
|
)
|
|
|
|
|
recurrences = event.get_recurrences(now(), now() + datetime.timedelta(days=7))
|
|
|
|
|
assert recurrences[0].start_datetime.strftime('%m-%d') == '05-02'
|
|
|
|
|
|
|
|
|
|
# exception wraps around first_event start_datetime
|
|
|
|
|
time_period_exception.end_datetime = first_event.start_datetime + datetime.timedelta(minutes=30)
|
|
|
|
|
time_period_exception.save()
|
|
|
|
|
recurrences = event.get_recurrences(now(), now() + datetime.timedelta(days=7))
|
|
|
|
|
assert recurrences[0].start_datetime.strftime('%m-%d') == '05-03'
|
|
|
|
|
|
|
|
|
|
# exception starts after first_event start_datetime
|
|
|
|
|
time_period_exception.start_datetime = first_event.start_datetime + datetime.timedelta(minutes=15)
|
|
|
|
|
time_period_exception.save()
|
|
|
|
|
recurrences = event.get_recurrences(now(), now() + datetime.timedelta(days=7))
|
|
|
|
|
assert recurrences[0].start_datetime.strftime('%m-%d') == '05-02'
|
|
|
|
|
assert recurrences[1].start_datetime.strftime('%m-%d') == '05-03'
|
|
|
|
|
|
|
|
|
|
# exception spans multiple days
|
|
|
|
|
time_period_exception.end_datetime = first_event.start_datetime + datetime.timedelta(days=3)
|
|
|
|
|
time_period_exception.save()
|
|
|
|
|
recurrences = event.get_recurrences(now(), now() + datetime.timedelta(days=7))
|
|
|
|
|
assert recurrences[0].start_datetime.strftime('%m-%d') == '05-02'
|
|
|
|
|
assert recurrences[1].start_datetime.strftime('%m-%d') == '05-06'
|
|
|
|
|
|
|
|
|
|
# move exception to unavailability calendar
|
|
|
|
|
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
time_period_exception.desk = None
|
|
|
|
|
time_period_exception.unavailability_calendar = unavailability_calendar
|
|
|
|
|
time_period_exception.save()
|
|
|
|
|
recurrences = event.get_recurrences(now(), now() + datetime.timedelta(days=7))
|
|
|
|
|
assert recurrences[0].start_datetime.strftime('%m-%d') == '05-02'
|
|
|
|
|
assert recurrences[1].start_datetime.strftime('%m-%d') == '05-03'
|
|
|
|
|
|
|
|
|
|
unavailability_calendar.desks.add(desk)
|
|
|
|
|
recurrences = event.get_recurrences(now(), now() + datetime.timedelta(days=7))
|
|
|
|
|
assert recurrences[0].start_datetime.strftime('%m-%d') == '05-02'
|
|
|
|
|
assert recurrences[1].start_datetime.strftime('%m-%d') == '05-06'
|
2021-02-09 14:09:41 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_recurring_events_exceptions_update_recurrences(freezer):
|
|
|
|
|
freezer.move_to('2021-05-01 12:00')
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
2021-06-30 15:50:00 +02:00
|
|
|
|
desk = Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
2021-02-09 14:09:41 +01:00
|
|
|
|
|
|
|
|
|
daily_event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now(),
|
2021-04-21 16:21:31 +02:00
|
|
|
|
recurrence_days=list(range(7)),
|
2021-02-09 14:09:41 +01:00
|
|
|
|
places=5,
|
|
|
|
|
recurrence_end_date=datetime.date(year=2021, month=5, day=8),
|
|
|
|
|
)
|
|
|
|
|
weekly_event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now(),
|
2021-04-21 16:21:31 +02:00
|
|
|
|
recurrence_days=[now().weekday()],
|
2021-02-09 14:09:41 +01:00
|
|
|
|
places=5,
|
|
|
|
|
recurrence_end_date=datetime.date(year=2021, month=6, day=1),
|
|
|
|
|
)
|
2022-03-16 15:10:34 +01:00
|
|
|
|
weekly_event_no_end_date = Event.objects.create(
|
2021-02-09 14:09:41 +01:00
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now() + datetime.timedelta(hours=2),
|
2022-03-16 15:10:34 +01:00
|
|
|
|
recurrence_days=[now().weekday()],
|
2021-02-09 14:09:41 +01:00
|
|
|
|
places=5,
|
|
|
|
|
)
|
2022-03-16 15:10:34 +01:00
|
|
|
|
Event.create_events_recurrences([daily_event, weekly_event, weekly_event_no_end_date])
|
2021-02-09 14:09:41 +01:00
|
|
|
|
|
|
|
|
|
assert Event.objects.filter(primary_event=daily_event).count() == 7
|
|
|
|
|
assert Event.objects.filter(primary_event=weekly_event).count() == 5
|
2022-03-16 15:10:34 +01:00
|
|
|
|
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 53
|
2021-02-09 14:09:41 +01:00
|
|
|
|
|
|
|
|
|
time_period_exception = TimePeriodException.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=datetime.date(year=2021, month=5, day=5),
|
|
|
|
|
end_datetime=datetime.date(year=2021, month=5, day=10),
|
|
|
|
|
)
|
|
|
|
|
agenda.update_event_recurrences()
|
|
|
|
|
assert Event.objects.filter(primary_event=daily_event).count() == 4
|
|
|
|
|
assert Event.objects.filter(primary_event=weekly_event).count() == 4
|
2022-03-16 15:10:34 +01:00
|
|
|
|
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 52
|
2021-02-09 14:09:41 +01:00
|
|
|
|
|
|
|
|
|
time_period_exception.delete()
|
|
|
|
|
agenda.update_event_recurrences()
|
|
|
|
|
assert Event.objects.filter(primary_event=daily_event).count() == 7
|
|
|
|
|
assert Event.objects.filter(primary_event=weekly_event).count() == 5
|
2022-03-16 15:10:34 +01:00
|
|
|
|
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 53
|
2021-02-09 14:09:41 +01:00
|
|
|
|
|
2022-03-17 12:37:31 +01:00
|
|
|
|
event = Event.objects.get(
|
|
|
|
|
primary_event=weekly_event_no_end_date, start_datetime=now() + datetime.timedelta(days=7, hours=2)
|
2021-02-09 14:09:41 +01:00
|
|
|
|
)
|
2021-07-09 15:41:40 +02:00
|
|
|
|
Booking.objects.create(event=event)
|
2021-02-09 14:09:41 +01:00
|
|
|
|
time_period_exception.save()
|
|
|
|
|
|
|
|
|
|
agenda.update_event_recurrences()
|
|
|
|
|
assert Booking.objects.count() == 1
|
2022-03-16 15:10:34 +01:00
|
|
|
|
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 53
|
2021-02-09 14:09:41 +01:00
|
|
|
|
assert agenda.recurrence_exceptions_report.events.get() == event
|
2021-04-21 16:21:31 +02:00
|
|
|
|
|
2022-03-16 15:10:34 +01:00
|
|
|
|
# no recurrence end date means new events are created as time moves on
|
|
|
|
|
freezer.move_to('2021-06-01 12:00')
|
|
|
|
|
agenda.update_event_recurrences()
|
|
|
|
|
assert Event.objects.filter(primary_event=weekly_event_no_end_date).count() == 57
|
|
|
|
|
|
2021-04-21 16:21:31 +02:00
|
|
|
|
|
2022-01-03 15:36:49 +01:00
|
|
|
|
def test_recurring_events_exceptions_update_recurrences_start_datetime_modified(freezer):
|
|
|
|
|
freezer.move_to('2021-09-06 12:00') # Monday
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
desk = Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
|
|
|
|
|
|
|
|
|
daily_event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now(),
|
|
|
|
|
recurrence_days=list(range(7)),
|
|
|
|
|
places=5,
|
|
|
|
|
recurrence_end_date=datetime.date(year=2021, month=9, day=13),
|
|
|
|
|
)
|
|
|
|
|
daily_event.create_all_recurrences()
|
|
|
|
|
assert daily_event.recurrences.count() == 7
|
|
|
|
|
|
|
|
|
|
monday_recurrence = daily_event.recurrences.get(start_datetime__day=6)
|
|
|
|
|
monday_recurrence.start_datetime += datetime.timedelta(days=7)
|
|
|
|
|
monday_recurrence.save()
|
|
|
|
|
|
|
|
|
|
sunday_recurrence = daily_event.recurrences.get(start_datetime__day=12)
|
|
|
|
|
sunday_recurrence.start_datetime += datetime.timedelta(hours=1)
|
|
|
|
|
sunday_recurrence.save()
|
|
|
|
|
|
|
|
|
|
# exception from Monday 06/09 to Tuesday 07/09
|
|
|
|
|
TimePeriodException.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=datetime.date(year=2021, month=9, day=6),
|
|
|
|
|
end_datetime=datetime.date(year=2021, month=9, day=8),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# exception on Sunday 12/09
|
|
|
|
|
TimePeriodException.objects.create(
|
|
|
|
|
desk=desk,
|
|
|
|
|
start_datetime=datetime.date(year=2021, month=9, day=12),
|
|
|
|
|
end_datetime=datetime.date(year=2021, month=9, day=13),
|
|
|
|
|
)
|
|
|
|
|
agenda.update_event_recurrences()
|
|
|
|
|
|
|
|
|
|
# Tuesday event was deleted
|
|
|
|
|
assert daily_event.recurrences.count() == 6
|
|
|
|
|
assert not Event.objects.filter(start_datetime__day=7).exists()
|
|
|
|
|
# Monday and Sunday still exist
|
|
|
|
|
assert Event.objects.filter(pk__in=[monday_recurrence.pk, sunday_recurrence.pk]).exists()
|
|
|
|
|
|
|
|
|
|
|
2022-03-17 16:36:17 +01:00
|
|
|
|
@pytest.mark.freeze_time('2022-02-22 14:00')
|
|
|
|
|
def test_recurring_events_update_recurrences_new_event(freezer):
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
|
|
|
|
|
|
|
|
|
daily_event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now(),
|
|
|
|
|
recurrence_days=list(range(7)),
|
|
|
|
|
places=5,
|
|
|
|
|
recurrence_end_date=now() + datetime.timedelta(days=7),
|
|
|
|
|
)
|
|
|
|
|
agenda.update_event_recurrences()
|
|
|
|
|
assert daily_event.recurrences.count() == 7
|
|
|
|
|
|
|
|
|
|
Event.objects.all().delete()
|
|
|
|
|
daily_event_no_end_date = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now(),
|
|
|
|
|
recurrence_days=[1],
|
|
|
|
|
recurrence_week_interval=3,
|
|
|
|
|
places=5,
|
|
|
|
|
)
|
|
|
|
|
agenda.update_event_recurrences()
|
|
|
|
|
assert daily_event_no_end_date.recurrences.count() == 18
|
|
|
|
|
|
|
|
|
|
daily_event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now().replace(hour=10),
|
|
|
|
|
recurrence_days=list(range(7)),
|
|
|
|
|
places=5,
|
|
|
|
|
recurrence_end_date=now() + datetime.timedelta(days=7),
|
|
|
|
|
)
|
|
|
|
|
agenda.update_event_recurrences()
|
|
|
|
|
assert daily_event.recurrences.count() == 7
|
|
|
|
|
assert daily_event_no_end_date.recurrences.count() == 18
|
|
|
|
|
|
|
|
|
|
|
2021-04-21 16:21:31 +02:00
|
|
|
|
def test_recurring_events_display(freezer):
|
|
|
|
|
freezer.move_to('2021-01-06 12:30')
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
event = Event.objects.create(
|
2021-06-30 17:52:05 +02:00
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now() + datetime.timedelta(days=1),
|
|
|
|
|
recurrence_days=list(range(7)),
|
|
|
|
|
places=5,
|
2021-04-21 16:21:31 +02:00
|
|
|
|
)
|
|
|
|
|
|
2021-06-30 17:52:05 +02:00
|
|
|
|
assert event.get_recurrence_display() == 'Daily at 1:30 p.m., from Jan. 7, 2021'
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2021-01-07 12:30')
|
2021-04-21 16:21:31 +02:00
|
|
|
|
assert event.get_recurrence_display() == 'Daily at 1:30 p.m.'
|
|
|
|
|
|
|
|
|
|
event.recurrence_days = [1, 2, 3, 4]
|
|
|
|
|
event.save()
|
|
|
|
|
assert event.get_recurrence_display() == 'From Tuesday to Friday at 1:30 p.m.'
|
|
|
|
|
|
|
|
|
|
event.recurrence_days = [4, 5, 6]
|
|
|
|
|
event.save()
|
|
|
|
|
assert event.get_recurrence_display() == 'From Friday to Sunday at 1:30 p.m.'
|
|
|
|
|
|
|
|
|
|
event.recurrence_days = [1, 4, 6]
|
|
|
|
|
event.save()
|
2021-12-22 12:02:01 +01:00
|
|
|
|
assert event.get_recurrence_display() == 'On Tuesdays, Fridays, Sundays at 1:30 p.m.'
|
2021-04-21 16:21:31 +02:00
|
|
|
|
|
|
|
|
|
event.recurrence_days = [0]
|
|
|
|
|
event.recurrence_week_interval = 2
|
|
|
|
|
event.save()
|
2021-12-22 12:02:01 +01:00
|
|
|
|
assert event.get_recurrence_display() == 'On Mondays at 1:30 p.m., once every two weeks'
|
2021-04-21 16:21:31 +02:00
|
|
|
|
|
|
|
|
|
event.recurrence_week_interval = 3
|
|
|
|
|
event.recurrence_end_date = now() + datetime.timedelta(days=7)
|
|
|
|
|
event.save()
|
|
|
|
|
assert (
|
|
|
|
|
event.get_recurrence_display()
|
2021-12-22 12:02:01 +01:00
|
|
|
|
== 'On Mondays at 1:30 p.m., once every three weeks, until Jan. 14, 2021'
|
2021-06-30 17:52:05 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
freezer.move_to('2021-01-06 12:30')
|
|
|
|
|
assert (
|
|
|
|
|
event.get_recurrence_display()
|
2021-12-22 12:02:01 +01:00
|
|
|
|
== 'On Mondays at 1:30 p.m., once every three weeks, from Jan. 7, 2021, until Jan. 14, 2021'
|
2021-04-21 16:21:31 +02:00
|
|
|
|
)
|
2021-08-16 15:59:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_event_triggered_fields():
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
event = Event.objects.create(
|
|
|
|
|
agenda=agenda, start_datetime=now() + datetime.timedelta(days=10), places=10, label='Event'
|
|
|
|
|
)
|
|
|
|
|
event2 = Event.objects.create(
|
|
|
|
|
agenda=agenda, start_datetime=now() + datetime.timedelta(days=10), places=10, label='Event'
|
|
|
|
|
)
|
|
|
|
|
assert event.booked_places == 0
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is False
|
|
|
|
|
assert event.full is False
|
|
|
|
|
|
|
|
|
|
event.booked_places = 42
|
|
|
|
|
event.booked_waiting_list_places = 42
|
|
|
|
|
event.almost_full = True
|
|
|
|
|
event.full = True
|
|
|
|
|
event.save()
|
|
|
|
|
# computed by triggers
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 0
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is False
|
|
|
|
|
assert event.full is False
|
|
|
|
|
|
|
|
|
|
# add bookings for other event: no impact
|
|
|
|
|
for _ in range(10):
|
|
|
|
|
Booking.objects.create(event=event2)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 0
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is False
|
|
|
|
|
assert event.full is False
|
|
|
|
|
|
|
|
|
|
# add bookings
|
|
|
|
|
for _ in range(9):
|
|
|
|
|
Booking.objects.create(event=event)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 9
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is False
|
|
|
|
|
|
|
|
|
|
Booking.objects.create(event=event)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is True
|
|
|
|
|
|
|
|
|
|
# cancel bookings for other event: no impact
|
|
|
|
|
event2.booking_set.filter(cancellation_datetime__isnull=True).first().cancel()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is True
|
|
|
|
|
|
|
|
|
|
# cancel bookings
|
|
|
|
|
event.booking_set.filter(cancellation_datetime__isnull=True).first().cancel()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 9
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is False
|
|
|
|
|
event.booking_set.filter(cancellation_datetime__isnull=True).first().cancel()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 8
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is False
|
|
|
|
|
assert event.full is False
|
|
|
|
|
|
|
|
|
|
# update places
|
|
|
|
|
event.places = 20
|
|
|
|
|
event.save()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 8
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is False
|
|
|
|
|
assert event.full is False
|
|
|
|
|
|
|
|
|
|
Booking.objects.create(event=event)
|
|
|
|
|
Booking.objects.create(event=event)
|
|
|
|
|
event.places = 10
|
|
|
|
|
event.save()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is True
|
|
|
|
|
|
|
|
|
|
# with a waiting list
|
|
|
|
|
event.waiting_list_places = 5
|
|
|
|
|
event.save()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is False
|
|
|
|
|
|
|
|
|
|
# add bookings for other event: no impact
|
|
|
|
|
for _ in range(10):
|
|
|
|
|
Booking.objects.create(event=event2, in_waiting_list=True)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 0
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is False
|
|
|
|
|
|
|
|
|
|
# add bookings
|
|
|
|
|
Booking.objects.create(event=event, in_waiting_list=True)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 1
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is False
|
|
|
|
|
for _ in range(1, 5):
|
|
|
|
|
Booking.objects.create(event=event, in_waiting_list=True)
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 5
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is True
|
|
|
|
|
|
|
|
|
|
# cancel bookings for other event: no impact
|
|
|
|
|
event2.booking_set.filter(in_waiting_list=True, cancellation_datetime__isnull=True).first().cancel()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 5
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is True
|
|
|
|
|
|
|
|
|
|
# cancel bookings
|
|
|
|
|
event.booking_set.filter(in_waiting_list=True, cancellation_datetime__isnull=True).first().cancel()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 4
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is False
|
|
|
|
|
|
|
|
|
|
# update waiting list places
|
|
|
|
|
event.waiting_list_places = 4
|
|
|
|
|
event.save()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 4
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is True
|
|
|
|
|
|
|
|
|
|
# delete bookings
|
|
|
|
|
event.booking_set.filter(in_waiting_list=True, cancellation_datetime__isnull=True).first().delete()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 10
|
|
|
|
|
assert event.booked_waiting_list_places == 3
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is False
|
|
|
|
|
event.booking_set.filter(in_waiting_list=False, cancellation_datetime__isnull=True).first().delete()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 9
|
|
|
|
|
assert event.booked_waiting_list_places == 3
|
|
|
|
|
assert event.almost_full is True
|
|
|
|
|
assert event.full is False
|
|
|
|
|
event.booking_set.filter(in_waiting_list=False, cancellation_datetime__isnull=True).first().delete()
|
|
|
|
|
event.refresh_from_db()
|
|
|
|
|
assert event.booked_places == 8
|
|
|
|
|
assert event.booked_waiting_list_places == 3
|
|
|
|
|
assert event.almost_full is False
|
|
|
|
|
assert event.full is False
|
2022-03-08 18:33:57 +01:00
|
|
|
|
|
|
|
|
|
|
2022-03-09 15:23:14 +01:00
|
|
|
|
def test_recurring_events_create_past_recurrences(freezer):
|
|
|
|
|
freezer.move_to('2021-09-06 12:00') # Monday
|
|
|
|
|
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
|
|
|
|
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
|
|
|
|
|
|
|
|
|
daily_event = Event.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
start_datetime=now() - datetime.timedelta(days=3),
|
|
|
|
|
recurrence_days=list(range(7)),
|
|
|
|
|
places=5,
|
|
|
|
|
recurrence_end_date=now() + datetime.timedelta(days=3),
|
|
|
|
|
)
|
|
|
|
|
daily_event.create_all_recurrences()
|
|
|
|
|
assert daily_event.recurrences.count() == 6
|
2022-02-22 15:59:27 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.freeze_time('2022-02-22 14:00') # Tuesday of 8th week
|
|
|
|
|
def test_shared_custody_agenda():
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-02-22 15:59:27 +01:00
|
|
|
|
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, days=list(range(7)), weeks='even', guardian=father)
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, days=list(range(7)), weeks='odd', guardian=mother)
|
|
|
|
|
|
|
|
|
|
slots = agenda.get_custody_slots(now().date(), now().date() + datetime.timedelta(days=30))
|
|
|
|
|
assert [x.date for x in slots] == [now().date() + datetime.timedelta(days=i) for i in range(30)]
|
|
|
|
|
assert all(x.guardian == father for x in slots if x.date.isocalendar()[1] % 2 == 0)
|
|
|
|
|
assert all(x.guardian == mother for x in slots if x.date.isocalendar()[1] % 2 == 1)
|
|
|
|
|
|
|
|
|
|
# add mother custody period on father's week, on 23/02 and 24/02
|
|
|
|
|
SharedCustodyPeriod.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
guardian=mother,
|
|
|
|
|
date_start=datetime.date(year=2022, month=2, day=23),
|
|
|
|
|
date_end=datetime.date(year=2022, month=2, day=25),
|
|
|
|
|
)
|
|
|
|
|
slots = agenda.get_custody_slots(now().date(), now().date() + datetime.timedelta(days=5))
|
2022-04-25 16:56:44 +02:00
|
|
|
|
slots = [(x.date.strftime('%d/%m'), str(x.guardian)) for x in slots]
|
2022-02-22 15:59:27 +01:00
|
|
|
|
assert slots == [
|
|
|
|
|
('22/02', 'John Doe'),
|
|
|
|
|
('23/02', 'Jane Doe'),
|
|
|
|
|
('24/02', 'Jane Doe'),
|
|
|
|
|
('25/02', 'John Doe'),
|
|
|
|
|
('26/02', 'John Doe'),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# add father custody period on father's week, nothing should change
|
|
|
|
|
SharedCustodyPeriod.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
guardian=father,
|
|
|
|
|
date_start=datetime.date(year=2022, month=2, day=25),
|
|
|
|
|
date_end=datetime.date(year=2022, month=3, day=3),
|
|
|
|
|
)
|
|
|
|
|
slots = agenda.get_custody_slots(now().date(), now().date() + datetime.timedelta(days=5))
|
2022-04-25 16:56:44 +02:00
|
|
|
|
slots = [(x.date.strftime('%d/%m'), str(x.guardian)) for x in slots]
|
2022-02-22 15:59:27 +01:00
|
|
|
|
assert slots == [
|
|
|
|
|
('22/02', 'John Doe'),
|
|
|
|
|
('23/02', 'Jane Doe'),
|
|
|
|
|
('24/02', 'Jane Doe'),
|
|
|
|
|
('25/02', 'John Doe'),
|
|
|
|
|
('26/02', 'John Doe'),
|
|
|
|
|
]
|
|
|
|
|
|
2022-03-29 17:49:54 +02:00
|
|
|
|
# check min_date/max_date
|
|
|
|
|
slots = agenda.get_custody_slots(
|
|
|
|
|
datetime.date(year=2022, month=2, day=27), datetime.date(year=2022, month=3, day=1)
|
|
|
|
|
)
|
2022-04-25 16:56:44 +02:00
|
|
|
|
slots = [(x.date.strftime('%d/%m'), str(x.guardian)) for x in slots]
|
2022-03-29 17:49:54 +02:00
|
|
|
|
assert slots == [('27/02', 'John Doe'), ('28/02', 'John Doe')]
|
|
|
|
|
|
2022-02-22 15:59:27 +01:00
|
|
|
|
|
|
|
|
|
@pytest.mark.freeze_time('2022-02-22 14:00') # Tuesday
|
|
|
|
|
def test_shared_custody_agenda_different_periodicity():
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-02-22 15:59:27 +01:00
|
|
|
|
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, days=[1, 2, 3], guardian=father)
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, days=[0, 4, 5, 6], guardian=mother)
|
|
|
|
|
slots = agenda.get_custody_slots(now().date(), now().date() + datetime.timedelta(days=14))
|
2022-04-25 16:56:44 +02:00
|
|
|
|
assert [(x.date.strftime('%A %d/%m'), str(x.guardian)) for x in slots] == [
|
2022-02-22 15:59:27 +01:00
|
|
|
|
('Tuesday 22/02', 'John Doe'),
|
|
|
|
|
('Wednesday 23/02', 'John Doe'),
|
|
|
|
|
('Thursday 24/02', 'John Doe'),
|
|
|
|
|
('Friday 25/02', 'Jane Doe'),
|
|
|
|
|
('Saturday 26/02', 'Jane Doe'),
|
|
|
|
|
('Sunday 27/02', 'Jane Doe'),
|
|
|
|
|
('Monday 28/02', 'Jane Doe'),
|
|
|
|
|
('Tuesday 01/03', 'John Doe'),
|
|
|
|
|
('Wednesday 02/03', 'John Doe'),
|
|
|
|
|
('Thursday 03/03', 'John Doe'),
|
|
|
|
|
('Friday 04/03', 'Jane Doe'),
|
|
|
|
|
('Saturday 05/03', 'Jane Doe'),
|
|
|
|
|
('Sunday 06/03', 'Jane Doe'),
|
|
|
|
|
('Monday 07/03', 'Jane Doe'),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
'rules,complete',
|
|
|
|
|
(
|
|
|
|
|
([], False),
|
|
|
|
|
([{'days': [0]}], False),
|
|
|
|
|
([{'days': [0], 'weeks': 'odd'}], False),
|
|
|
|
|
([{'days': list(range(7))}], True),
|
|
|
|
|
([{'days': list(range(7)), 'weeks': 'odd'}], False),
|
|
|
|
|
([{'days': list(range(7)), 'weeks': 'odd'}, {'days': list(range(7)), 'weeks': 'even'}], True),
|
|
|
|
|
([{'days': [0, 1, 2]}, {'days': [3, 4, 5, 6]}], True),
|
|
|
|
|
([{'days': [0, 1, 2]}, {'days': [3, 4, 5]}], False),
|
|
|
|
|
([{'days': [0, 1, 2, 3]}, {'days': [3, 4, 5, 6]}], False), # overlapping rules, should not exist
|
|
|
|
|
(
|
|
|
|
|
[
|
|
|
|
|
{'days': [0, 1, 2]},
|
|
|
|
|
{'days': [3, 4, 5]},
|
|
|
|
|
{'days': [6], 'weeks': 'odd'},
|
|
|
|
|
{'days': [6], 'weeks': 'even'},
|
|
|
|
|
],
|
|
|
|
|
True,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
def test_shared_custody_agenda_is_complete(rules, complete):
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-02-22 15:59:27 +01:00
|
|
|
|
|
|
|
|
|
for i, rule in enumerate(rules):
|
|
|
|
|
guardian = father if i % 2 else mother
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, guardian=guardian, **rule)
|
|
|
|
|
|
|
|
|
|
assert agenda.is_complete() is complete
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
'rules,days,weeks,overlaps',
|
|
|
|
|
(
|
|
|
|
|
([], [1], '', False),
|
|
|
|
|
([{'days': [0]}], [1], '', False),
|
|
|
|
|
([{'days': [1]}], [1], '', True),
|
|
|
|
|
([{'days': [0]}, {'days': [1]}], [1], '', True),
|
|
|
|
|
([{'days': [0], 'weeks': 'odd'}, {'days': [1]}], [1], '', True),
|
|
|
|
|
([{'days': [0]}, {'days': [1], 'weeks': 'odd'}], [1], '', True),
|
|
|
|
|
([{'days': [0]}, {'days': [1]}], [1], 'odd', True),
|
|
|
|
|
([{'days': [0]}, {'days': [1], 'weeks': 'odd'}], [1], 'even', False),
|
|
|
|
|
([{'days': [0, 1], 'weeks': 'odd'}], [1], 'odd', True),
|
|
|
|
|
([{'days': [0, 1]}], [1], '', True),
|
|
|
|
|
([{'days': [0, 1], 'weeks': 'even'}], [1], 'odd', False),
|
|
|
|
|
([{'days': [0, 1]}], [0, 3, 4], '', True),
|
|
|
|
|
([{'days': [0, 1]}], [2, 3], '', False),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
def test_shared_custody_agenda_rule_overlaps(rules, days, weeks, overlaps):
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-02-22 15:59:27 +01:00
|
|
|
|
|
|
|
|
|
for i, rule in enumerate(rules):
|
|
|
|
|
guardian = father if i % 2 else mother
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, guardian=guardian, **rule)
|
|
|
|
|
|
|
|
|
|
assert agenda.rule_overlaps(days, weeks) is overlaps
|
|
|
|
|
|
|
|
|
|
|
2022-03-24 17:05:17 +01:00
|
|
|
|
def test_shared_custody_agenda_holiday_rule_overlaps():
|
|
|
|
|
calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-03-24 17:05:17 +01:00
|
|
|
|
|
|
|
|
|
summer_holiday = TimePeriodExceptionGroup.objects.create(
|
|
|
|
|
unavailability_calendar=calendar, label='Summer', slug='summer'
|
|
|
|
|
)
|
|
|
|
|
winter_holiday = TimePeriodExceptionGroup.objects.create(
|
|
|
|
|
unavailability_calendar=calendar, label='Winter', slug='winter'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
rule = SharedCustodyHolidayRule.objects.create(agenda=agenda, guardian=father, holiday=summer_holiday)
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='', periodicity='') is True
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='', periodicity='', instance=rule) is False
|
|
|
|
|
assert agenda.holiday_rule_overlaps(winter_holiday, years='', periodicity='') is False
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='') is True
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='', periodicity='first-half') is True
|
|
|
|
|
|
|
|
|
|
rule.years = 'odd'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='', periodicity='') is True
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='') is True
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='even', periodicity='') is False
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'first-half'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='', periodicity='') is True
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='first-half') is True
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='second-half') is False
|
|
|
|
|
assert (
|
|
|
|
|
agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='first-and-third-quarters')
|
|
|
|
|
is True
|
|
|
|
|
)
|
|
|
|
|
assert (
|
|
|
|
|
agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='second-and-fourth-quarters')
|
|
|
|
|
is True
|
|
|
|
|
)
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='even', periodicity='first-half') is False
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'second-and-fourth-quarters'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='', periodicity='') is True
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='first-half') is True
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='second-half') is True
|
|
|
|
|
assert (
|
|
|
|
|
agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='first-and-third-quarters')
|
|
|
|
|
is False
|
|
|
|
|
)
|
|
|
|
|
assert (
|
|
|
|
|
agenda.holiday_rule_overlaps(summer_holiday, years='odd', periodicity='second-and-fourth-quarters')
|
|
|
|
|
is True
|
|
|
|
|
)
|
|
|
|
|
assert agenda.holiday_rule_overlaps(summer_holiday, years='even', periodicity='first-half') is False
|
|
|
|
|
|
|
|
|
|
|
2022-02-22 15:59:27 +01:00
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
'periods,date_start,date_end,overlaps',
|
|
|
|
|
(
|
|
|
|
|
([], '2022-02-03', '2022-02-04', False),
|
|
|
|
|
([('2022-02-03', '2022-02-04')], '2022-02-03', '2022-02-04', True),
|
|
|
|
|
([('2022-02-03', '2022-02-04')], '2022-02-01', '2022-02-04', True),
|
|
|
|
|
([('2022-02-03', '2022-02-04')], '2022-02-03', '2022-02-06', True),
|
|
|
|
|
([('2022-02-03', '2022-02-04')], '2022-02-04', '2022-02-06', False),
|
|
|
|
|
([('2022-02-03', '2022-02-04')], '2022-02-01', '2022-02-03', False),
|
|
|
|
|
([('2022-02-03', '2022-02-04'), ('2022-01-31', '2022-02-01')], '2022-02-01', '2022-02-03', False),
|
|
|
|
|
([('2022-02-03', '2022-02-04'), ('2022-01-31', '2022-02-01')], '2022-01-01', '2022-02-10', True),
|
|
|
|
|
([('2022-02-03', '2022-02-10')], '2022-02-05', '2022-02-06', True),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
def test_shared_custody_agenda_period_overlaps(periods, date_start, date_end, overlaps):
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-02-22 15:59:27 +01:00
|
|
|
|
|
|
|
|
|
for i, dates in enumerate(periods):
|
|
|
|
|
guardian = father if i % 2 else mother
|
|
|
|
|
SharedCustodyPeriod.objects.create(
|
|
|
|
|
agenda=agenda, guardian=guardian, date_start=dates[0], date_end=dates[1]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert agenda.period_overlaps(date_start, date_end) is overlaps
|
|
|
|
|
|
|
|
|
|
|
2022-03-24 17:05:17 +01:00
|
|
|
|
def test_shared_custody_agenda_period_holiday_rule_no_overlaps():
|
|
|
|
|
calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-03-24 17:05:17 +01:00
|
|
|
|
|
|
|
|
|
summer_holiday = TimePeriodExceptionGroup.objects.create(
|
|
|
|
|
unavailability_calendar=calendar, label='Summer', slug='summer'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
rule = SharedCustodyHolidayRule.objects.create(agenda=agenda, guardian=father, holiday=summer_holiday)
|
|
|
|
|
SharedCustodyPeriod.objects.create(
|
|
|
|
|
holiday_rule=rule, agenda=agenda, guardian=father, date_start='2022-02-03', date_end='2022-02-05'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert agenda.period_overlaps('2022-02-03', '2022-02-05') is False
|
|
|
|
|
|
|
|
|
|
|
2022-02-22 15:59:27 +01:00
|
|
|
|
def test_shared_custody_agenda_rule_label():
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-02-22 15:59:27 +01:00
|
|
|
|
|
|
|
|
|
rule = SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7)))
|
|
|
|
|
assert rule.label == 'daily'
|
|
|
|
|
|
|
|
|
|
rule.days = [1, 2, 3, 4]
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'from Tuesday to Friday'
|
|
|
|
|
|
|
|
|
|
rule.days = [4, 5, 6]
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'from Friday to Sunday'
|
|
|
|
|
|
|
|
|
|
rule.days = [1, 4, 6]
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'on Tuesdays, Fridays, Sundays'
|
|
|
|
|
|
|
|
|
|
rule.days = [0]
|
|
|
|
|
rule.weeks = 'even'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'on Mondays, on even weeks'
|
|
|
|
|
|
|
|
|
|
rule.weeks = 'odd'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'on Mondays, on odd weeks'
|
|
|
|
|
|
|
|
|
|
|
2022-03-24 17:05:17 +01:00
|
|
|
|
def test_shared_custody_agenda_holiday_rule_label():
|
|
|
|
|
calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-03-24 17:05:17 +01:00
|
|
|
|
|
|
|
|
|
summer_holiday = TimePeriodExceptionGroup.objects.create(
|
|
|
|
|
unavailability_calendar=calendar, label='Summer Holidays', slug='summer'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
rule = SharedCustodyHolidayRule.objects.create(agenda=agenda, guardian=father, holiday=summer_holiday)
|
|
|
|
|
assert rule.label == 'Summer Holidays'
|
|
|
|
|
|
|
|
|
|
rule.years = 'even'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'Summer Holidays, on even years'
|
|
|
|
|
|
|
|
|
|
rule.years = 'odd'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'Summer Holidays, on odd years'
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'first-half'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'Summer Holidays, the first half, on odd years'
|
|
|
|
|
|
|
|
|
|
rule.years = ''
|
|
|
|
|
rule.periodicity = 'second-half'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'Summer Holidays, the second half'
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'first-and-third-quarters'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'Summer Holidays, the first and third quarters'
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'second-and-fourth-quarters'
|
|
|
|
|
rule.save()
|
|
|
|
|
assert rule.label == 'Summer Holidays, the second and fourth quarters'
|
|
|
|
|
|
|
|
|
|
|
2022-02-22 15:59:27 +01:00
|
|
|
|
def test_shared_custody_agenda_period_label(freezer):
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-02-22 15:59:27 +01:00
|
|
|
|
|
|
|
|
|
period = SharedCustodyPeriod.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
guardian=father,
|
|
|
|
|
date_start=datetime.date(2021, 7, 10),
|
|
|
|
|
date_end=datetime.date(2021, 7, 11),
|
|
|
|
|
)
|
|
|
|
|
assert str(period) == 'John Doe, 07/10/2021'
|
|
|
|
|
|
|
|
|
|
period.date_end = datetime.date(2021, 7, 13)
|
|
|
|
|
period.save()
|
|
|
|
|
assert str(period) == 'John Doe, 07/10/2021 → 07/13/2021'
|
2022-03-24 17:05:17 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_shared_custody_agenda_holiday_rule_create_periods():
|
|
|
|
|
calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-03-24 17:05:17 +01:00
|
|
|
|
|
|
|
|
|
summer_holiday = TimePeriodExceptionGroup.objects.create(
|
|
|
|
|
unavailability_calendar=calendar, label='Summer', slug='summer'
|
|
|
|
|
)
|
|
|
|
|
TimePeriodException.objects.create(
|
|
|
|
|
unavailability_calendar=calendar,
|
|
|
|
|
start_datetime=make_aware(datetime.datetime(year=2021, month=7, day=6, hour=0, minute=0)),
|
|
|
|
|
end_datetime=make_aware(datetime.datetime(year=2021, month=9, day=2, hour=0, minute=0)),
|
|
|
|
|
group=summer_holiday,
|
|
|
|
|
)
|
|
|
|
|
TimePeriodException.objects.create(
|
|
|
|
|
unavailability_calendar=calendar,
|
|
|
|
|
start_datetime=make_aware(datetime.datetime(year=2022, month=7, day=7, hour=0, minute=0)),
|
|
|
|
|
end_datetime=make_aware(datetime.datetime(year=2022, month=9, day=1, hour=0, minute=0)),
|
|
|
|
|
group=summer_holiday,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
rule = SharedCustodyHolidayRule.objects.create(agenda=agenda, guardian=father, holiday=summer_holiday)
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period1, period2 = SharedCustodyPeriod.objects.all()
|
|
|
|
|
assert period1.holiday_rule == rule
|
|
|
|
|
assert period1.guardian == father
|
|
|
|
|
assert period1.agenda == agenda
|
|
|
|
|
assert period1.date_start == datetime.date(year=2021, month=7, day=6)
|
|
|
|
|
assert period1.date_end == datetime.date(year=2021, month=9, day=2)
|
|
|
|
|
assert period2.holiday_rule == rule
|
|
|
|
|
assert period2.guardian == father
|
|
|
|
|
assert period2.agenda == agenda
|
|
|
|
|
assert period2.date_start == datetime.date(year=2022, month=7, day=7)
|
|
|
|
|
assert period2.date_end == datetime.date(year=2022, month=9, day=1)
|
|
|
|
|
|
|
|
|
|
rule.years = 'odd'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period = SharedCustodyPeriod.objects.get()
|
|
|
|
|
assert period.date_start == datetime.date(year=2021, month=7, day=6)
|
|
|
|
|
assert period.date_end == datetime.date(year=2021, month=9, day=2)
|
|
|
|
|
|
|
|
|
|
rule.years = 'even'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period = SharedCustodyPeriod.objects.get()
|
|
|
|
|
assert period.date_start == datetime.date(year=2022, month=7, day=7)
|
|
|
|
|
assert period.date_end == datetime.date(year=2022, month=9, day=1)
|
|
|
|
|
|
|
|
|
|
rule.years = ''
|
|
|
|
|
rule.periodicity = 'first-half'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period1, period2 = SharedCustodyPeriod.objects.all()
|
|
|
|
|
assert period1.date_start == datetime.date(year=2021, month=7, day=6)
|
|
|
|
|
assert period1.date_end == datetime.date(year=2021, month=8, day=1)
|
|
|
|
|
assert period1.date_end.weekday() == 6
|
|
|
|
|
assert period2.date_start == datetime.date(year=2022, month=7, day=7)
|
|
|
|
|
assert period2.date_end == datetime.date(year=2022, month=7, day=31)
|
|
|
|
|
assert period2.date_end.weekday() == 6
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'second-half'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period1, period2 = SharedCustodyPeriod.objects.all()
|
|
|
|
|
assert period1.date_start == datetime.date(year=2021, month=8, day=1)
|
|
|
|
|
assert period1.date_start.weekday() == 6
|
|
|
|
|
assert period1.date_end == datetime.date(year=2021, month=9, day=2)
|
|
|
|
|
assert period2.date_start == datetime.date(year=2022, month=7, day=31)
|
|
|
|
|
assert period2.date_start.weekday() == 6
|
|
|
|
|
assert period2.date_end == datetime.date(year=2022, month=9, day=1)
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'first-and-third-quarters'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period1, period2, period3, period4 = SharedCustodyPeriod.objects.all()
|
|
|
|
|
assert period1.date_start == datetime.date(year=2021, month=7, day=6)
|
|
|
|
|
assert period1.date_end == datetime.date(year=2021, month=7, day=25)
|
|
|
|
|
assert period1.date_end.weekday() == 6
|
|
|
|
|
assert period2.date_start == datetime.date(year=2021, month=8, day=8)
|
|
|
|
|
assert period2.date_end == datetime.date(year=2021, month=8, day=22)
|
|
|
|
|
assert period2.date_end.weekday() == 6
|
|
|
|
|
assert period3.date_start == datetime.date(year=2022, month=7, day=7)
|
|
|
|
|
assert period3.date_end == datetime.date(year=2022, month=7, day=24)
|
|
|
|
|
assert period3.date_end.weekday() == 6
|
|
|
|
|
assert period4.date_start == datetime.date(year=2022, month=8, day=7)
|
|
|
|
|
assert period4.date_end == datetime.date(year=2022, month=8, day=21)
|
|
|
|
|
assert period4.date_end.weekday() == 6
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'second-and-fourth-quarters'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period1, period2, period3, period4 = SharedCustodyPeriod.objects.all()
|
|
|
|
|
assert period1.date_start == datetime.date(year=2021, month=7, day=25)
|
|
|
|
|
assert period1.date_start.weekday() == 6
|
|
|
|
|
assert period1.date_end == datetime.date(year=2021, month=8, day=8)
|
|
|
|
|
assert period2.date_start == datetime.date(year=2021, month=8, day=22)
|
|
|
|
|
assert period2.date_start.weekday() == 6
|
|
|
|
|
assert period2.date_end == datetime.date(year=2021, month=9, day=2)
|
|
|
|
|
assert period3.date_start == datetime.date(year=2022, month=7, day=24)
|
|
|
|
|
assert period3.date_start.weekday() == 6
|
|
|
|
|
assert period3.date_end == datetime.date(year=2022, month=8, day=7)
|
|
|
|
|
assert period4.date_start == datetime.date(year=2022, month=8, day=21)
|
|
|
|
|
assert period4.date_start.weekday() == 6
|
|
|
|
|
assert period4.date_end == datetime.date(year=2022, month=9, day=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_shared_custody_agenda_holiday_rule_create_periods_christmas_holidays():
|
|
|
|
|
calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-03-24 17:05:17 +01:00
|
|
|
|
|
|
|
|
|
christmas_holiday = TimePeriodExceptionGroup.objects.create(
|
|
|
|
|
unavailability_calendar=calendar, label='Christmas', slug='christmas'
|
|
|
|
|
)
|
|
|
|
|
TimePeriodException.objects.create(
|
|
|
|
|
unavailability_calendar=calendar,
|
|
|
|
|
start_datetime=make_aware(datetime.datetime(year=2021, month=12, day=18, hour=0, minute=0)),
|
|
|
|
|
end_datetime=make_aware(datetime.datetime(year=2022, month=1, day=3, hour=0, minute=0)),
|
|
|
|
|
group=christmas_holiday,
|
|
|
|
|
)
|
|
|
|
|
TimePeriodException.objects.create(
|
|
|
|
|
unavailability_calendar=calendar,
|
|
|
|
|
start_datetime=make_aware(datetime.datetime(year=2022, month=12, day=17, hour=0, minute=0)),
|
|
|
|
|
end_datetime=make_aware(datetime.datetime(year=2023, month=1, day=3, hour=0, minute=0)),
|
|
|
|
|
group=christmas_holiday,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
rule = SharedCustodyHolidayRule.objects.create(agenda=agenda, guardian=father, holiday=christmas_holiday)
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period1, period2 = SharedCustodyPeriod.objects.all()
|
|
|
|
|
assert period1.date_start == datetime.date(year=2021, month=12, day=18)
|
|
|
|
|
assert period1.date_end == datetime.date(year=2022, month=1, day=3)
|
|
|
|
|
assert period2.date_start == datetime.date(year=2022, month=12, day=17)
|
|
|
|
|
assert period2.date_end == datetime.date(year=2023, month=1, day=3)
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'first-half'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period1, period2 = SharedCustodyPeriod.objects.all()
|
|
|
|
|
assert period1.date_start == datetime.date(year=2021, month=12, day=18)
|
|
|
|
|
assert period1.date_end == datetime.date(year=2021, month=12, day=26)
|
|
|
|
|
assert period1.date_end.weekday() == 6
|
|
|
|
|
assert period2.date_start == datetime.date(year=2022, month=12, day=17)
|
|
|
|
|
assert period2.date_end == datetime.date(year=2022, month=12, day=25)
|
|
|
|
|
assert period2.date_end.weekday() == 6
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'second-half'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
period1, period2 = SharedCustodyPeriod.objects.all()
|
|
|
|
|
assert period1.date_start == datetime.date(year=2021, month=12, day=26)
|
|
|
|
|
assert period1.date_start.weekday() == 6
|
|
|
|
|
assert period1.date_end == datetime.date(year=2022, month=1, day=3)
|
|
|
|
|
assert period2.date_start == datetime.date(year=2022, month=12, day=25)
|
|
|
|
|
assert period2.date_start.weekday() == 6
|
|
|
|
|
assert period2.date_end == datetime.date(year=2023, month=1, day=3)
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'first-and-third-quarters'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
assert not SharedCustodyPeriod.objects.exists()
|
|
|
|
|
|
|
|
|
|
rule.periodicity = 'second-and-fourth-quarters'
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
assert not SharedCustodyPeriod.objects.exists()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_shared_custody_agenda_holiday_rules_application():
|
|
|
|
|
calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-03-24 17:05:17 +01:00
|
|
|
|
|
|
|
|
|
christmas_holiday = TimePeriodExceptionGroup.objects.create(
|
|
|
|
|
unavailability_calendar=calendar, label='Christmas', slug='christmas'
|
|
|
|
|
)
|
|
|
|
|
TimePeriodException.objects.create(
|
|
|
|
|
unavailability_calendar=calendar,
|
|
|
|
|
start_datetime=make_aware(datetime.datetime(year=2021, month=12, day=18, hour=0, minute=0)),
|
|
|
|
|
end_datetime=make_aware(datetime.datetime(year=2022, month=1, day=3, hour=0, minute=0)),
|
|
|
|
|
group=christmas_holiday,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, days=list(range(7)), weeks='even', guardian=father)
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, days=list(range(7)), weeks='odd', guardian=mother)
|
|
|
|
|
|
|
|
|
|
rule = SharedCustodyHolidayRule.objects.create(agenda=agenda, guardian=father, holiday=christmas_holiday)
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
|
|
|
|
|
date_start = datetime.date(year=2021, month=12, day=13) # Monday, even week
|
|
|
|
|
slots = agenda.get_custody_slots(date_start, date_start + datetime.timedelta(days=30))
|
2022-04-25 16:56:44 +02:00
|
|
|
|
guardians = [str(x.guardian) for x in slots]
|
2022-03-24 17:05:17 +01:00
|
|
|
|
assert all(name == 'John Doe' for name in guardians[:21])
|
|
|
|
|
assert all(name == 'Jane Doe' for name in guardians[21:28])
|
|
|
|
|
assert all(name == 'John Doe' for name in guardians[28:])
|
2022-03-24 17:04:53 +01:00
|
|
|
|
|
|
|
|
|
# check exceptional custody periods take precedence over holiday rules
|
|
|
|
|
SharedCustodyPeriod.objects.create(
|
|
|
|
|
agenda=agenda,
|
|
|
|
|
guardian=mother,
|
|
|
|
|
date_start=datetime.date(year=2021, month=12, day=27),
|
|
|
|
|
date_end=datetime.date(year=2021, month=12, day=29),
|
|
|
|
|
)
|
|
|
|
|
slots = agenda.get_custody_slots(date_start, date_start + datetime.timedelta(days=30))
|
2022-04-25 16:56:44 +02:00
|
|
|
|
slots = [(x.date.strftime('%d/%m'), str(x.guardian)) for x in slots]
|
2022-03-24 17:04:53 +01:00
|
|
|
|
assert slots[13:17] == [
|
|
|
|
|
('26/12', 'John Doe'),
|
|
|
|
|
('27/12', 'Jane Doe'),
|
|
|
|
|
('28/12', 'Jane Doe'),
|
|
|
|
|
('29/12', 'John Doe'),
|
|
|
|
|
]
|
2022-03-29 16:28:39 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_shared_custody_agenda_update_holiday_rules_command():
|
|
|
|
|
calendar = UnavailabilityCalendar.objects.create(label='Calendar')
|
|
|
|
|
|
2022-04-25 16:56:44 +02:00
|
|
|
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
|
|
|
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
2022-11-28 14:49:26 +01:00
|
|
|
|
child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe')
|
2022-06-29 17:13:46 +02:00
|
|
|
|
agenda = SharedCustodyAgenda.objects.create(
|
2022-11-28 14:49:26 +01:00
|
|
|
|
first_guardian=father, second_guardian=mother, child=child, date_start=now()
|
2022-06-29 17:13:46 +02:00
|
|
|
|
)
|
2022-03-29 16:28:39 +02:00
|
|
|
|
|
|
|
|
|
christmas_holiday = TimePeriodExceptionGroup.objects.create(
|
|
|
|
|
unavailability_calendar=calendar, label='Christmas', slug='christmas'
|
|
|
|
|
)
|
|
|
|
|
exception = TimePeriodException.objects.create(
|
|
|
|
|
unavailability_calendar=calendar,
|
|
|
|
|
start_datetime=make_aware(datetime.datetime(year=2021, month=12, day=18, hour=0, minute=0)),
|
|
|
|
|
end_datetime=make_aware(datetime.datetime(year=2022, month=1, day=3, hour=0, minute=0)),
|
|
|
|
|
group=christmas_holiday,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, days=list(range(7)), weeks='even', guardian=father)
|
|
|
|
|
SharedCustodyRule.objects.create(agenda=agenda, days=list(range(7)), weeks='odd', guardian=mother)
|
|
|
|
|
|
|
|
|
|
rule = SharedCustodyHolidayRule.objects.create(agenda=agenda, guardian=father, holiday=christmas_holiday)
|
|
|
|
|
rule.update_or_create_periods()
|
|
|
|
|
|
|
|
|
|
period = SharedCustodyPeriod.objects.get()
|
|
|
|
|
assert period.date_start == datetime.date(year=2021, month=12, day=18)
|
|
|
|
|
assert period.date_end == datetime.date(year=2022, month=1, day=3)
|
|
|
|
|
|
|
|
|
|
exception.start_datetime += datetime.timedelta(days=1)
|
|
|
|
|
exception.save()
|
|
|
|
|
TimePeriodException.objects.create(
|
|
|
|
|
unavailability_calendar=calendar,
|
|
|
|
|
start_datetime=make_aware(datetime.datetime(year=2022, month=12, day=18, hour=0, minute=0)),
|
|
|
|
|
end_datetime=make_aware(datetime.datetime(year=2023, month=1, day=3, hour=0, minute=0)),
|
|
|
|
|
group=christmas_holiday,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
call_command('update_shared_custody_holiday_rules')
|
|
|
|
|
period1, period2 = SharedCustodyPeriod.objects.all()
|
|
|
|
|
assert period1.date_start == datetime.date(year=2021, month=12, day=19)
|
|
|
|
|
assert period1.date_end == datetime.date(year=2022, month=1, day=3)
|
|
|
|
|
assert period2.date_start == datetime.date(year=2022, month=12, day=18)
|
|
|
|
|
assert period2.date_end == datetime.date(year=2023, month=1, day=3)
|