import datetime import pytest from django.core.files.base import ContentFile from django.db import connection from django.test.utils import CaptureQueriesContext from django.utils.timezone import now from chrono.agendas.models import ( Person, SharedCustodyAgenda, SharedCustodyHolidayRule, SharedCustodyPeriod, SharedCustodyRule, SharedCustodySettings, TimePeriodExceptionGroup, UnavailabilityCalendar, ) from tests.utils import login pytestmark = pytest.mark.django_db with open('tests/data/holidays.ics') as f: ICS_HOLIDAYS = f.read() @pytest.mark.freeze_time('2022-02-22 14:00') # Tuesday def test_shared_custody_agenda_settings_rules(app, admin_user): 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') child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=now() ) app = login(app) resp = app.get('/manage/shared-custody/%s/' % agenda.pk).follow() resp = resp.click('Settings') assert 'Custody agenda of John Doe and Jane Doe' in resp.text assert 'Custody rules are not complete.' in resp.text assert 'This agenda doesn\'t have any custody rules yet.' in resp.text resp = resp.click('Add custody rule') resp.form['guardian'] = father.pk resp.form['days'] = list(range(7)) resp.form['weeks'] = 'even' resp = resp.form.submit() assert resp.location.endswith('/manage/shared-custody/%s/settings/' % agenda.pk) resp = resp.follow() assert 'Custody rules are not complete.' in resp.text assert 'John Doe, daily, on even weeks' in resp.text resp = resp.click('Add custody rule') resp.form['guardian'] = mother.pk resp.form['days'] = list(range(7)) resp.form['weeks'] = 'odd' resp = resp.form.submit().follow() assert 'Custody rules are not complete.' not in resp.text assert 'John Doe, daily, on even weeks' in resp.text assert 'Jane Doe, daily, on odd weeks' in resp.text resp = resp.click('John Doe, daily, on even weeks') resp.form['days'] = list(range(6)) resp = resp.form.submit() assert resp.location.endswith('/manage/shared-custody/%s/settings/' % agenda.pk) resp = resp.follow() assert 'Custody rules are not complete.' in resp.text resp = resp.click('John Doe, from Monday to Saturday, on even weeks') resp.form['days'] = [0] resp.form['weeks'] = 'odd' resp = resp.form.submit() assert 'Rule overlaps existing rules.' in resp.text resp.form['weeks'] = 'even' resp = resp.form.submit().follow() resp = resp.click('remove', index=1) resp = resp.form.submit() assert resp.location.endswith('/manage/shared-custody/%s/settings/' % agenda.pk) resp = resp.follow() assert SharedCustodyRule.objects.count() == 1 def test_shared_custody_agenda_settings_rules_require_days(app, admin_user): 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') child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=now() ) app = login(app) resp = app.get('/manage/shared-custody/%s/settings/' % agenda.pk) resp = resp.click('Add custody rule') resp.form['guardian'] = father.pk resp.form['weeks'] = 'even' resp = resp.form.submit() assert 'This field is required.' in resp.text resp.form['days'] = [0] resp.form.submit().follow() assert SharedCustodyRule.objects.count() == 1 @pytest.mark.freeze_time('2022-02-22 14:00') # Tuesday def test_shared_custody_agenda_settings_periods(app, admin_user): 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') child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=now() ) app = login(app) resp = app.get('/manage/shared-custody/%s/settings/' % agenda.pk) assert 'This agenda doesn\'t have any custody period.' in resp.text resp = resp.click('Add custody period') resp.form['guardian'] = father.pk resp.form['date_start'] = '2022-03-01' resp.form['date_end'] = '2022-03-03' resp = resp.form.submit() assert resp.location.endswith('/manage/shared-custody/%s/settings/#open:time-periods' % agenda.pk) resp = resp.follow() assert 'This agenda doesn\'t have any custody period.' not in resp.text assert 'John Doe, 03/01/2022 → 03/03/2022' in resp.text resp = resp.click('John Doe, 03/01/2022 → 03/03/2022') resp.form['guardian'] = mother.pk resp = resp.form.submit() assert resp.location.endswith('/manage/shared-custody/%s/settings/#open:time-periods' % agenda.pk) resp = resp.follow() assert 'Jane Doe, 03/01/2022 → 03/03/2022' in resp.text resp = resp.click('Add custody period') resp.form['guardian'] = mother.pk resp.form['date_start'] = '2022-03-05' resp.form['date_end'] = '2022-03-03' resp = resp.form.submit() assert 'End date must be greater than start date.' in resp.text resp.form['date_start'] = '2022-03-02' resp.form['date_end'] = '2022-03-06' resp = resp.form.submit() assert 'Period overlaps existing periods.' in resp.text resp = app.get('/manage/shared-custody/%s/settings/' % agenda.pk) resp = resp.click('remove', href='delete') resp = resp.form.submit() assert resp.location.endswith('/manage/shared-custody/%s/settings/#open:time-periods' % agenda.pk) resp = resp.follow() assert not SharedCustodyPeriod.objects.exists() @pytest.mark.freeze_time('2022-02-22 14:00') # Tuesday def test_shared_custody_agenda_month_view(app, admin_user): 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') child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=datetime.date(2022, 1, 1) ) SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7)), weeks='even') app = login(app) resp = app.get('/manage/shared-custody/%s/' % agenda.pk).follow() assert 'Custody agenda of John Doe and Jane Doe for James Doe' in resp.text assert 'February 2022' in resp.text assert 'Configuration is not completed yet.' in resp.text SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=list(range(7)), weeks='odd') resp = app.get('/manage/shared-custody/%s/' % agenda.pk).follow() assert 'Configuration is not completed yet.' not in resp.text assert all('Week %s' % i in resp.text for i in range(5, 10)) assert resp.pyquery('tbody tr th.today span').text() == 'Tuesday 22' days = [x.text for x in resp.pyquery('tbody tr th span')] assert len(days) == 7 * 5 assert days[:3] == ['Monday 31', 'Tuesday 1', 'Wednesday 2'] assert days[-3:] == ['Friday 4', 'Saturday 5', 'Sunday 6'] tds = [x.text for x in resp.pyquery('tbody tr td')] for week_number, i in zip(range(5, 10), range(0, 29, 7)): guardian = 'Jane Doe' if week_number % 2 else 'John Doe' assert tds[i : i + 7] == [guardian] * 7 SharedCustodyPeriod.objects.create( agenda=agenda, guardian=father, date_start=datetime.date(2022, 2, 1), date_end=datetime.date(2022, 2, 3), ) resp = app.get('/manage/shared-custody/%s/' % agenda.pk).follow() tds = [x.text for x in resp.pyquery('tbody tr td')] assert tds[:7] == ['Jane Doe', 'John Doe', 'John Doe', 'Jane Doe', 'Jane Doe', 'Jane Doe', 'Jane Doe'] old_resp = resp resp = resp.click('→') assert 'March 2022' in resp.text assert 'today' not in resp.text assert all('Week %s' % i in resp.text for i in range(9, 14)) days = [x.text for x in resp.pyquery('tbody tr th span')] assert len(days) == 7 * 5 assert days[:3] == ['Monday 28', 'Tuesday 1', 'Wednesday 2'] assert days[-3:] == ['Friday 1', 'Saturday 2', 'Sunday 3'] resp = resp.click('←') assert resp.text == old_resp.text app.get('/manage/shared-custody/%s/42/42/' % agenda.pk, status=404) @pytest.mark.freeze_time('2022-02-01 14:00') # Tuesday def test_shared_custody_agenda_month_view_date_start(app, admin_user): 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') child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=datetime.date(2022, 1, 1) ) SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7))) app = login(app) # custody for the whole month resp = app.get('/manage/shared-custody/%s/' % agenda.pk).follow() assert resp.text.count('') == 0 assert len([x.text for x in resp.pyquery('tbody tr td.guardian')]) == 35 # all days are shown days = [x.text for x in resp.pyquery('tbody tr th span')] assert len(days) == 7 * 5 # custody for half the month agenda.date_start = datetime.date(2022, 2, 14) agenda.save() resp = app.get('/manage/shared-custody/%s/' % agenda.pk).follow() assert resp.text.count('') == 14 assert len([x.text for x in resp.pyquery('tbody tr td.guardian')]) == 21 # all days are still shown new_days = [x.text for x in resp.pyquery('tbody tr th span')] assert days == new_days # month before date_start resp = resp.click('←') assert resp.text.count('') == 42 assert len([x.text for x in resp.pyquery('tbody tr td.guardian')]) == 0 def test_shared_custody_agenda_month_view_queries(app, admin_user): 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') child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=now() ) SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=[0, 1, 2], weeks='even') SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=[3, 4, 5, 6], weeks='odd') SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=[0, 1, 2], weeks='odd') SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=[3, 4, 5, 6], weeks='even') for i in range(1, 10): SharedCustodyPeriod.objects.create( agenda=agenda, guardian=mother, date_start=datetime.date(year=2022, month=12, day=i), date_end=datetime.date(year=2022, month=12, day=i + 1), ) # configure holidays unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar') SharedCustodySettings.objects.create(holidays_calendar=unavailability_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() app = login(app) resp = app.get('/manage/shared-custody/%s/settings/' % agenda.pk) resp = resp.click('Add custody rule during holidays') resp.form['guardian'] = father.pk resp.form['holiday'].select(text='Vacances de Noël') resp = resp.form.submit().follow() with CaptureQueriesContext(connection) as ctx: app.get('/manage/shared-custody/%s/2022/12/' % agenda.pk) assert len(ctx.captured_queries) == 10 def test_shared_custody_agenda_holiday_rules(app, admin_user): 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') child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=now() ) app = login(app) resp = app.get('/manage/shared-custody/%s/settings/' % agenda.pk) assert 'Add custody rule during holidays' not in resp.text assert 'Custody rules during holidays' not in resp.text # configure holidays unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar') SharedCustodySettings.objects.create(holidays_calendar=unavailability_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() resp = app.get('/manage/shared-custody/%s/settings/' % agenda.pk) resp = resp.click('Add custody rule during holidays') resp.form['guardian'] = father.pk resp.form['holiday'].select(text='Vacances de Noël') resp.form['years'] = 'odd' resp.form['periodicity'] = 'first-half' resp = resp.form.submit() assert resp.location.endswith('/manage/shared-custody/%s/settings/#open:holidays' % agenda.pk) resp = resp.follow() assert SharedCustodyHolidayRule.objects.count() == 1 assert SharedCustodyPeriod.objects.count() == 3 assert 'This agenda doesn\'t have any custody period.' in resp.text resp = resp.click('John Doe, Vacances de Noël, the first half, on odd years') resp.form['years'] = '' resp = resp.form.submit() assert resp.location.endswith('/manage/shared-custody/%s/settings/#open:holidays' % agenda.pk) resp = resp.follow() assert 'John Doe, Vacances de Noël, the first half' in resp.text assert SharedCustodyHolidayRule.objects.count() == 1 assert SharedCustodyPeriod.objects.count() == 6 resp = resp.click('Add custody rule during holidays') resp.form['guardian'] = mother.pk resp.form['holiday'].select(text='Vacances de Noël') resp.form['periodicity'] = 'first-half' resp = resp.form.submit() assert 'Rule overlaps existing rules.' in resp.text resp.form['periodicity'] = 'second-half' resp = resp.form.submit().follow() assert 'Jane Doe, Vacances de Noël, the second half' in resp.text assert SharedCustodyHolidayRule.objects.count() == 2 assert SharedCustodyPeriod.objects.count() == 12 resp = resp.click('remove', index=1) resp = resp.form.submit() assert resp.location.endswith('/manage/shared-custody/%s/settings/#open:holidays' % agenda.pk) resp = resp.follow() assert SharedCustodyHolidayRule.objects.count() == 1 assert SharedCustodyPeriod.objects.count() == 6 resp = resp.click('Add custody rule during holidays') resp.form['guardian'] = father.pk resp.form['holiday'].select(text='Vacances de Noël') resp.form['periodicity'] = 'first-and-third-quarters' resp = resp.form.submit() assert 'Short holidays cannot be cut into quarters.' in resp.text resp.form['holiday'].select(text='Vacances d’Été') resp = resp.form.submit().follow() assert 'John Doe, Vacances d’Été, the first and third quarters' in resp.text # if dates get deleted, rules still exist but holiday is not shown anymore summer_holidays = TimePeriodExceptionGroup.objects.get(slug='summer_holidays') summer_holidays.exceptions.all().delete() assert SharedCustodyHolidayRule.objects.filter(holiday=summer_holidays).exists() resp = resp.click('Add custody rule during holidays') assert [x[2] for x in resp.form['holiday'].options] == ['---------', 'Vacances de Noël'] # holiday name is shown on month view SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7)), weeks='even') SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=list(range(7)), weeks='odd') resp = app.get('/manage/shared-custody/%s/2022/12/' % agenda.pk) assert 'John Doe (Vacances de Noël)' in resp.text # calendar cannot be deleted resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk) resp = resp.click('Delete') resp = resp.form.submit().follow() assert 'This calendar cannot be deleted because it is used by shared custody agendas.' in resp.text def test_shared_custody_settings_feature_flag(app, admin_user, settings): settings.SHARED_CUSTODY_ENABLED = False app = login(app) resp = app.get('/manage/') assert 'Shared custody' not in resp.text settings.SHARED_CUSTODY_ENABLED = True resp = app.get('/manage/') assert 'Shared custody' in resp.text def test_shared_custody_settings_management_role(app, admin_user, manager_user): 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') child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=now() ) app = login(app, username='manager', password='manager') app.get('/manage/shared-custody/%s/settings/' % agenda.pk, status=403) app.reset() app = login(app) resp = app.get('/manage/') resp = resp.click('Shared custody') resp.form['management_role'] = manager_user.groups.all()[0].pk resp.form.submit() app.reset() app = login(app, username='manager', password='manager') resp = app.get('/manage/shared-custody/%s/settings/' % agenda.pk) assert 'Custody agenda of John Doe and Jane Doe' in resp.text def test_shared_custody_agenda_delete(app, admin_user, manager_user): 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') child = Person.objects.create(user_external_id='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=now() ) SharedCustodySettings.objects.create(management_role=manager_user.groups.all()[0]) app = login(app, username='manager', password='manager') resp = app.get('/manage/shared-custody/%s/settings/' % agenda.pk) assert 'Delete' not in resp.text app = login(app) resp = app.get('/manage/shared-custody/%s/settings/' % agenda.pk) resp = resp.click('Delete') resp = resp.form.submit().follow() assert not SharedCustodyAgenda.objects.exists()