import copy import datetime import json import os import shutil import sys import tempfile from io import StringIO from unittest import mock import pytest from django.contrib.auth.models import Group from django.core.files.base import ContentFile from django.core.management import CommandError, call_command from django.test import override_settings from django.utils.encoding import force_bytes from chrono.agendas.models import ( Agenda, AgendaImportError, AgendaNotificationsSettings, AgendaReminderSettings, Category, Desk, Event, EventsType, MeetingType, Resource, SharedCustodySettings, TimePeriod, TimePeriodException, TimePeriodExceptionSource, UnavailabilityCalendar, VirtualMember, ) from chrono.manager.utils import import_site from chrono.utils.timezone import make_aware, now from .test_agendas import ICS_SAMPLE, ICS_SAMPLE_WITH_NO_EVENTS pytestmark = pytest.mark.django_db def get_output_of_command(command, *args, **kwargs): old_stdout = sys.stdout output = sys.stdout = StringIO() call_command(command, *args, **kwargs) sys.stdout = old_stdout return output.getvalue() def test_import_export(app): agenda_events = Agenda.objects.create(label='Events Agenda', kind='events') first_event = Event.objects.create( agenda=agenda_events, start_datetime=make_aware(datetime.datetime(2020, 7, 21, 16, 42, 35)), places=10 ) agenda_meetings = Agenda.objects.create(label='Meetings Agenda', kind='meetings') MeetingType.objects.create(agenda=agenda_meetings, label='Meeting Type', duration=30) desk = Desk.objects.create(agenda=agenda_meetings, label='Desk') exceptions_desk = Desk.objects.create(agenda=agenda_events, slug='_exceptions_holder') tpx_start = make_aware(datetime.datetime(2017, 5, 22, 8, 0)) tpx_end = make_aware(datetime.datetime(2017, 5, 22, 12, 30)) TimePeriodException.objects.create(desk=desk, start_datetime=tpx_start, end_datetime=tpx_end) TimePeriodException.objects.create(desk=exceptions_desk, start_datetime=tpx_start, end_datetime=tpx_end) TimePeriod.objects.create( desk=desk, weekday=2, start_time=tpx_start.time(), end_time=tpx_end.time(), weekday_indexes=[1, 3] ) TimePeriod.objects.create( desk=desk, date=datetime.date(2022, 10, 24), start_time=tpx_start.time(), end_time=tpx_end.time() ) output = get_output_of_command('export_site') assert len(json.loads(output)['agendas']) == 2 import_site(data={}, clean=True) empty_output = get_output_of_command('export_site') assert len(json.loads(empty_output)['agendas']) == 0 Agenda(label='test').save() old_stdin = sys.stdin sys.stdin = StringIO(json.dumps({})) assert Agenda.objects.count() == 1 try: call_command('import_site', '-', clean=True) finally: sys.stdin = old_stdin assert Agenda.objects.count() == 0 with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(output)) f.flush() call_command('import_site', f.name) assert Agenda.objects.count() == 2 first_imported_event = Agenda.objects.get(label='Events Agenda').event_set.first() assert first_imported_event.start_datetime == first_event.start_datetime assert TimePeriodException.objects.get(desk__agenda__kind='meetings').start_datetime == tpx_start assert TimePeriodException.objects.get(desk__agenda__kind='meetings').end_datetime == tpx_end assert TimePeriodException.objects.get(desk__agenda__kind='events').start_datetime == tpx_start assert TimePeriodException.objects.get(desk__agenda__kind='events').end_datetime == tpx_end assert TimePeriod.objects.filter(weekday_indexes=[1, 3]).count() == 1 assert TimePeriod.objects.filter(date=datetime.date(2022, 10, 24)).count() == 1 agenda1 = Agenda.objects.get(label='Events Agenda') agenda2 = Agenda.objects.get(label='Meetings Agenda') event = Event(agenda=agenda1, start_datetime=now(), places=10) event.save() desk, _ = Desk.objects.get_or_create(agenda=agenda2, label='Desk A', slug='desk-a') timeperiod = TimePeriod( desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0) ) timeperiod.save() exception = TimePeriodException( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)), end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)), ) exception.save() import_site(json.loads(output), overwrite=True) assert Event.objects.filter(id=event.id).count() == 0 assert Desk.objects.filter(slug='desk-a').count() == 0 assert TimePeriod.objects.filter(id=timeperiod.id).count() == 0 assert TimePeriodException.objects.filter(id=exception.id).count() == 0 event = Event(agenda=agenda1, start_datetime=now(), places=10) event.save() desk, _ = Desk.objects.get_or_create(agenda=agenda2, label='Desk A', slug='desk-a') timeperiod = TimePeriod( weekday=2, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0) ) timeperiod.save() exception = TimePeriodException( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)), end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)), ) exception.save() import_site(json.loads(output), overwrite=False) assert Event.objects.filter(id=event.id).count() == 1 assert TimePeriod.objects.filter(id=timeperiod.id).count() == 1 assert TimePeriodException.objects.filter(id=exception.id).count() == 1 import_site(data={}, if_empty=True) assert Agenda.objects.count() == 2 import_site(data={}, clean=True) tempdir = tempfile.mkdtemp('chrono-test') empty_output = get_output_of_command('export_site', output=os.path.join(tempdir, 't.json')) assert os.path.exists(os.path.join(tempdir, 't.json')) shutil.rmtree(tempdir) def test_import_export_bad_date_format(app): agenda_events = Agenda.objects.create(label='Events Agenda', kind='events') Desk.objects.create(agenda=agenda_events, slug='_exceptions_holder') Event.objects.create( agenda=agenda_events, start_datetime=make_aware(datetime.datetime(2020, 7, 21, 16, 42, 35)), places=10 ) output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['agendas']) == 1 payload['agendas'][0]['events'][0]['start_datetime'] = '20-07-21 16:42:00' with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert '%s' % excinfo.value == 'Bad datetime format "20-07-21 16:42:00"' Agenda.objects.all().delete() agenda_meetings = Agenda.objects.create(label='Meetings Agenda', kind='meetings') desk = Desk.objects.create(agenda=agenda_meetings, label='Desk') tpx_start = make_aware(datetime.datetime(2017, 5, 22, 8, 0)) tpx_end = make_aware(datetime.datetime(2017, 5, 22, 12, 30)) TimePeriodException.objects.create(desk=desk, start_datetime=tpx_start, end_datetime=tpx_end) output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['agendas']) == 1 payload['agendas'][0]['desks'][0]['exceptions'][0]['start_datetime'] = '17-05-22 08:00:00' with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert '%s' % excinfo.value == 'Bad datetime format "17-05-22 08:00:00"' def test_import_export_bad_end_time_format(app): agenda_events = Agenda.objects.create(label='Events Agenda', kind='events') Desk.objects.create(agenda=agenda_events, slug='_exceptions_holder') Event.objects.create( agenda=agenda_events, start_datetime=make_aware(datetime.datetime(2020, 7, 21, 16, 42, 35)), places=10, end_time=datetime.time(20, 00), ) output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['agendas']) == 1 payload['agendas'][0]['events'][0]['end_time'] = 'xxx20:00' with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert '%s' % excinfo.value == 'Bad time format "xxx20:00"' def test_import_export_events_agenda_options(app): agenda = Agenda.objects.create( label='Foo Bar', kind='events', default_view='open_events', anonymize_delay=42, booking_form_url='{{ eservices_url }}backoffice/submission/inscription-aux-activites/', minimal_booking_delay_in_working_days=True, booking_user_block_template='foo bar', booking_check_filters='foo,bar', event_display_template='foo bar', mark_event_checked_auto=True, disable_check_update=True, enable_check_for_future_events=True, booking_extra_user_block_template='foobar', ) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') output = get_output_of_command('export_site') assert len(json.loads(output)['agendas']) == 1 import_site(data={}, clean=True) with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(output)) f.flush() call_command('import_site', f.name) assert Agenda.objects.count() == 1 agenda = Agenda.objects.first() assert agenda.default_view == 'open_events' assert agenda.anonymize_delay == 42 assert agenda.booking_form_url == '{{ eservices_url }}backoffice/submission/inscription-aux-activites/' assert agenda.minimal_booking_delay_in_working_days is True assert agenda.booking_user_block_template == 'foo bar' assert agenda.booking_check_filters == 'foo,bar' assert agenda.event_display_template == 'foo bar' assert agenda.mark_event_checked_auto is True assert agenda.disable_check_update is True assert agenda.enable_check_for_future_events is True assert agenda.booking_extra_user_block_template == 'foobar' agenda.partial_bookings = True agenda.invoicing_tolerance = 5 agenda.invoicing_unit = 'minute' agenda.save() output = get_output_of_command('export_site') assert len(json.loads(output)['agendas']) == 1 import_site(data={}, clean=True) with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(output)) f.flush() call_command('import_site', f.name) assert Agenda.objects.count() == 1 agenda = Agenda.objects.first() assert agenda.partial_bookings is True assert agenda.invoicing_tolerance == 5 assert agenda.invoicing_unit == 'minute' def test_import_export_event_details(app): events_type = EventsType.objects.create( label='Foo', custom_fields=[ {'varname': 'text', 'label': 'Text', 'field_type': 'text'}, {'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'}, {'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'}, ], ) agenda = Agenda.objects.create(label='Foo Bar', kind='events', events_type=events_type) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') Event.objects.create( slug='event', agenda=agenda, description='description', pricing='100', url='https://example.net/', publication_datetime=make_aware(datetime.datetime(2020, 5, 11)), places=42, start_datetime=now(), end_time=datetime.time(20, 00), duration=30, custom_fields={ 'text': 'foo', 'textarea': 'foo bar', 'bool': True, }, ) # check event (agenda, slug) unicity agenda2 = Agenda.objects.create(label='Foo Bar 2', kind='events') Desk.objects.create(agenda=agenda2, slug='_exceptions_holder') Event.objects.create( slug='event', agenda=agenda2, places=42, start_datetime=now(), ) output = get_output_of_command('export_site') assert len(json.loads(output)['agendas']) == 2 import_site(data={}, clean=True) with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(output)) f.flush() call_command('import_site', f.name) assert Agenda.objects.count() == 2 assert Event.objects.count() == 2 first_imported_event = Agenda.objects.get(label='Foo Bar').event_set.first() assert first_imported_event.slug == 'event' assert first_imported_event.description == 'description' assert first_imported_event.pricing == '100' assert first_imported_event.url == 'https://example.net/' assert str(first_imported_event.publication_datetime) == '2020-05-10 22:00:00+00:00' assert str(first_imported_event.publication_datetime.tzinfo) == 'UTC' assert first_imported_event.duration == 30 assert first_imported_event.end_time == datetime.time(20, 00) assert first_imported_event.custom_fields == { 'text': 'foo', 'textarea': 'foo bar', 'bool': True, } assert Agenda.objects.get(label='Foo Bar 2').event_set.first().slug == 'event' def test_import_export_recurring_event(app, freezer): freezer.move_to('2021-01-12 12:10') events_type = EventsType.objects.create( label='Foo', custom_fields=[ {'varname': 'text', 'label': 'Text', 'field_type': 'text'}, {'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'}, {'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'}, ], ) agenda = Agenda.objects.create(label='Foo Bar', kind='events', events_type=events_type) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( agenda=agenda, start_datetime=now(), end_time=datetime.time(20, 00), recurrence_days=[now().isoweekday()], recurrence_week_interval=2, places=10, slug='test', custom_fields={ 'text': 'foo', 'textarea': 'foo bar', 'bool': True, }, ) assert Event.objects.count() == 1 output = get_output_of_command('export_site') assert len(json.loads(output)['agendas']) == 1 import_site(data={}, clean=True) with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(output)) f.flush() call_command('import_site', f.name) assert Agenda.objects.count() == 1 assert Event.objects.count() == 28 event = Agenda.objects.get(label='Foo Bar').event_set.filter(primary_event__isnull=True).get() assert event.recurrence_days == [now().isoweekday()] assert event.recurrence_week_interval == 2 # importing event with end recurrence date removes recurrences event.recurrence_end_date = now() + datetime.timedelta(days=7) event.recurrence_week_interval = 1 event.save() output = get_output_of_command('export_site') import_site(data={}, clean=True) with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(output)) f.flush() call_command('import_site', f.name) event = Event.objects.get(slug='test') assert Event.objects.filter(primary_event=event).count() == 1 first_event = event.recurrences.first() assert first_event.custom_fields == { 'text': 'foo', 'textarea': 'foo bar', 'bool': True, } first_event.custom_fields = {} first_event.save() # import again with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(output)) f.flush() call_command('import_site', f.name) event = Event.objects.get(slug='test') assert Event.objects.filter(primary_event=event).count() == 1 first_event2 = event.recurrences.first() assert first_event.pk == first_event2.pk assert first_event2.custom_fields == { 'text': 'foo', 'textarea': 'foo bar', 'bool': True, } # import again but change places payload = json.loads(output) payload['agendas'][0]['events'][0]['places'] = 42 with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(json.dumps(payload))) f.flush() call_command('import_site', f.name) event = Event.objects.get(slug='test') assert event.places == 42 assert event.end_time == datetime.time(20, 00) assert Event.objects.filter(primary_event=event, places=42).count() == 1 def test_import_export_meetings_agenda_options(app): agenda = Agenda.objects.create( label='Foo Bar', kind='meetings', default_view='month', ) output = get_output_of_command('export_site') assert len(json.loads(output)['agendas']) == 1 import_site(data={}, clean=True) with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(output)) f.flush() call_command('import_site', f.name) assert Agenda.objects.count() == 1 agenda = Agenda.objects.first() assert agenda.default_view == 'month' def test_import_export_permissions(app): meetings_agenda = Agenda.objects.create(label='Foo Bar', kind='meetings') group1 = Group.objects.create(name='gé1') group2 = Group.objects.create(name='gé2') meetings_agenda.view_role = group1 meetings_agenda.edit_role = group2 meetings_agenda.save() output = get_output_of_command('export_site') assert len(json.loads(output)['agendas']) == 1 import_site(data={}, clean=True) assert Agenda.objects.count() == 0 Group.objects.all().delete() with pytest.raises(AgendaImportError) as excinfo: import_site(json.loads(output), overwrite=True) assert 'gé1' in str(excinfo.value) and 'gé2' in str(excinfo.value) group1 = Group(name='gé1') group1.save() with pytest.raises(AgendaImportError) as excinfo: import_site(json.loads(output), overwrite=True) assert '%s' % excinfo.value == 'Missing roles: "gé2"' with tempfile.NamedTemporaryFile() as f: f.write(force_bytes(output)) f.flush() with pytest.raises(CommandError) as excinfo: call_command('import_site', f.name) assert '%s' % excinfo.value == 'Missing roles: "gé2"' group2 = Group(name='gé2') group2.save() import_site(json.loads(output), overwrite=True) agenda = Agenda.objects.get(slug=meetings_agenda.slug) assert agenda.view_role == group1 assert agenda.edit_role == group2 def test_import_export_agenda_with_resources(app): meetings_agenda = Agenda.objects.create(label='Foo Bar', kind='meetings') resource = Resource.objects.create(label='foo') meetings_agenda.resources.add(resource) output = get_output_of_command('export_site') import_site(data={}, clean=True) assert Agenda.objects.count() == 0 assert Resource.objects.count() == 0 data = json.loads(output) del data['resources'] with pytest.raises(AgendaImportError) as excinfo: import_site(data, overwrite=True) assert str(excinfo.value) == 'Missing "foo" resource' Resource.objects.create(label='foobar') with pytest.raises(AgendaImportError) as excinfo: import_site(data, overwrite=True) assert str(excinfo.value) == 'Missing "foo" resource' resource = Resource.objects.create(label='foo') import_site(data, overwrite=True) agenda = Agenda.objects.get(slug=meetings_agenda.slug) assert list(agenda.resources.all()) == [resource] def test_import_export_agenda_with_category(app): category = Category.objects.create(label='foo') agenda = Agenda.objects.create(label='Foo Bar', category=category) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') output = get_output_of_command('export_site') import_site(data={}, clean=True) assert Agenda.objects.count() == 0 assert Category.objects.count() == 0 data = json.loads(output) del data['categories'] import_site(data.copy(), overwrite=True) agenda = Agenda.objects.get(slug=agenda.slug) assert agenda.category is None category = Category.objects.create(label='foobar') import_site(data.copy(), overwrite=True) agenda = Agenda.objects.get(slug=agenda.slug) assert agenda.category is None category = Category.objects.create(label='foo') import_site(data.copy(), overwrite=True) agenda = Agenda.objects.get(slug=agenda.slug) assert agenda.category == category def test_import_export_agenda_with_events_type(app): events_type = EventsType.objects.create(label='foo') agenda = Agenda.objects.create(label='Foo Bar', kind='events', events_type=events_type) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') output = get_output_of_command('export_site') import_site(data={}, clean=True) assert Agenda.objects.count() == 0 assert EventsType.objects.count() == 0 data = json.loads(output) del data['events_types'] with pytest.raises(AgendaImportError) as excinfo: import_site(data, overwrite=True) assert str(excinfo.value) == 'Missing "foo" events type' EventsType.objects.create(label='foobar') with pytest.raises(AgendaImportError) as excinfo: import_site(data, overwrite=True) assert str(excinfo.value) == 'Missing "foo" events type' events_type = EventsType.objects.create(label='foo') import_site(data, overwrite=True) agenda = Agenda.objects.get(slug=agenda.slug) assert agenda.events_type == events_type def test_import_export_virtual_agenda(app): virtual_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual') output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['agendas']) == 1 virtual_agenda.delete() import_site(json.loads(output), overwrite=True) virtual_agenda = Agenda.objects.get(label='Virtual Agenda', slug='virtual-agenda', kind='virtual') assert virtual_agenda.minimal_booking_delay is None assert virtual_agenda.maximal_booking_delay is None assert virtual_agenda.real_agendas.count() == 0 assert virtual_agenda.excluded_timeperiods.count() == 0 # add booking delay virtual_agenda.minimal_booking_delay = 2 virtual_agenda.maximal_booking_delay = 10 virtual_agenda.save() output = get_output_of_command('export_site') payload = json.loads(output) virtual_agenda.delete() import_site(json.loads(output), overwrite=True) virtual_agenda = Agenda.objects.get(label='Virtual Agenda', slug='virtual-agenda', kind='virtual') assert virtual_agenda.minimal_booking_delay == 2 assert virtual_agenda.maximal_booking_delay == 10 # add excluded timeperiods tp1 = TimePeriod.objects.create( agenda=virtual_agenda, weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0) ) tp2 = TimePeriod.objects.create( agenda=virtual_agenda, weekday=2, start_time=datetime.time(12, 0), end_time=datetime.time(13, 0) ) output = get_output_of_command('export_site') payload = json.loads(output) virtual_agenda.delete() tp1.delete() tp2.delete() import_site(json.loads(output), overwrite=True) virtual_agenda = Agenda.objects.get(label='Virtual Agenda', slug='virtual-agenda', kind='virtual') assert virtual_agenda.excluded_timeperiods.count() == 2 tp1 = TimePeriod.objects.get( agenda=virtual_agenda, weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0) ) tp2 = TimePeriod.objects.get( agenda=virtual_agenda, weekday=2, start_time=datetime.time(12, 0), end_time=datetime.time(13, 0) ) def test_import_export_virtual_agenda_with_included_agenda(app): virtual_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual') foo_agenda = Agenda.objects.create(label='Zoo', kind='meetings') bar_agenda = Agenda.objects.create(label='Bar', kind='meetings') mt1 = MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30) mt2 = MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30) VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=foo_agenda) VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=bar_agenda) output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['agendas']) == 3 virtual_agenda.delete() foo_agenda.delete() bar_agenda.delete() mt1.delete() mt2.delete() import_site(payload, overwrite=True) virtual_agenda = Agenda.objects.get(label='Virtual Agenda', slug='virtual-agenda', kind='virtual') assert virtual_agenda.real_agendas.count() == 2 assert virtual_agenda.real_agendas.filter(label='Zoo').count() == 1 assert virtual_agenda.real_agendas.filter(label='Bar').count() == 1 # add incompatible meetingtype bar_agenda = Agenda.objects.get(label='Bar', kind='meetings') mt2 = MeetingType.objects.get(agenda=bar_agenda, label='Meeting Type') mt2.duration = 10 mt2.save() output = get_output_of_command('export_site') payload = json.loads(output) virtual_agenda.delete() with pytest.raises(AgendaImportError) as excinfo: import_site(json.loads(output), overwrite=False) assert ( 'This agenda does not have the same meeting types provided by the virtual agenda.' in '%s' % excinfo.value ) def test_import_export_virtual_agenda_with_missing_agenda(app): virtual_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual') foo_agenda = Agenda.objects.create(label='Foo', kind='meetings') VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=foo_agenda) output = get_output_of_command('export_site') payload = json.loads(output) del payload['agendas'][0] # remove real agenda virtual_agenda.delete() foo_agenda.delete() with pytest.raises(AgendaImportError) as excinfo: import_site(payload, overwrite=False) assert 'The real agenda "foo" does not exist.' in '%s' % excinfo.value def test_import_export_desk_missing_fields(app): meetings_agenda = Agenda.objects.create(label='Foo Bar', kind='meetings') MeetingType.objects.create(agenda=meetings_agenda, label='Meeting Type', duration=30) desk = Desk.objects.create(agenda=meetings_agenda, label='Desk') TimePeriod.objects.create( desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0) ) TimePeriodException.objects.create( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)), end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)), ) output = get_output_of_command('export_site') payload = json.loads(output) del payload['agendas'][0]['desks'][0]['timeperiods'] del payload['agendas'][0]['desks'][0]['exceptions'] Agenda.objects.all().delete() import_site(payload) assert TimePeriod.objects.exists() is False assert TimePeriodException.objects.exists() is False def test_import_export_unknown_fields(app): events_agenda = Agenda.objects.create(label='Events Agenda', kind='events') Desk.objects.create(agenda=events_agenda, slug='_exceptions_holder') Event.objects.create(agenda=events_agenda, start_datetime=now(), places=10) meetings_agenda = Agenda.objects.create(label='Meetings Agenda', kind='meetings') MeetingType.objects.create(agenda=meetings_agenda, label='Meeting Type', duration=30) Desk.objects.create(agenda=meetings_agenda, label='Desk') desk = meetings_agenda.desk_set.first() TimePeriod.objects.create( desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0) ) TimePeriodException.objects.create( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)), end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)), ) output = get_output_of_command('export_site') payload = json.loads(output) Agenda.objects.all().delete() assert Agenda.objects.exists() is False assert MeetingType.objects.exists() is False assert Desk.objects.exists() is False assert TimePeriod.objects.exists() is False assert TimePeriodException.objects.exists() is False # add unknown fields everywhere payload['agendas'][0]['unknown_field'] = 'foobar' payload['agendas'][0]['events'][0]['unknown_field'] = 'foobar' payload['agendas'][0]['real_agendas'] = ['foobar'] # not available for a non virtual agenda payload['agendas'][1]['unknown_field'] = 'foobar' payload['agendas'][1]['meetingtypes'][0]['unknown_field'] = 'foobar' payload['agendas'][1]['desks'][0]['unknown_field'] = 'foobar' payload['agendas'][1]['desks'][0]['timeperiods'][0]['unknown_field'] = 'foobar' payload['agendas'][1]['desks'][0]['exceptions'][0]['unknown_field'] = 'foobar' import_site(payload) assert Agenda.objects.exists() is True assert MeetingType.objects.exists() is True assert Desk.objects.exists() is True assert TimePeriod.objects.exists() is True assert TimePeriodException.objects.exists() is True def test_import_export_slug_fields(app): agenda_events = Agenda.objects.create(label='Events Agenda', kind='events') Desk.objects.create(agenda=agenda_events, slug='_exceptions_holder') Event.objects.create(agenda=agenda_events, start_datetime=now(), places=10) agenda_meetings = Agenda.objects.create(label='Meetings Agenda', kind='meetings') MeetingType.objects.create(agenda=agenda_meetings, label='Meeting Type', duration=30) Desk.objects.create(agenda=agenda_meetings, label='Desk') output = get_output_of_command('export_site') original_payload = json.loads(output) Agenda.objects.all().delete() assert Agenda.objects.exists() is False assert MeetingType.objects.exists() is False assert Desk.objects.exists() is False assert Event.objects.exists() is False payload = copy.deepcopy(original_payload) payload['agendas'][0]['slug'] = 'events-agenda&' with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert str(excinfo.value) == 'Bad slug format "events-agenda&"' payload = copy.deepcopy(original_payload) payload['agendas'][0]['events'][0]['slug'] = 'events-agenda-event&' with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert str(excinfo.value) == 'Bad slug format "events-agenda-event&"' payload = copy.deepcopy(original_payload) payload['agendas'][1]['desks'][0]['slug'] = 'desk&' with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert str(excinfo.value) == 'Bad slug format "desk&"' payload = copy.deepcopy(original_payload) payload['agendas'][1]['meetingtypes'][0]['slug'] = 'meeting-type&' with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert str(excinfo.value) == 'Bad slug format "meeting-type&"' def test_import_export_notification_settings(): agenda = Agenda.objects.create(label='Foo bar', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') AgendaNotificationsSettings.objects.create( agenda=agenda, almost_full_event=AgendaNotificationsSettings.EDIT_ROLE, full_event=AgendaNotificationsSettings.VIEW_ROLE, cancelled_event=AgendaNotificationsSettings.EMAIL_FIELD, cancelled_event_emails=['hop@entrouvert.com', 'top@entrouvert.com'], ) output = get_output_of_command('export_site') payload = json.loads(output) agenda.delete() assert not AgendaNotificationsSettings.objects.exists() import_site(payload) agenda = Agenda.objects.first() AgendaNotificationsSettings.objects.get( agenda=agenda, almost_full_event=AgendaNotificationsSettings.EDIT_ROLE, full_event=AgendaNotificationsSettings.VIEW_ROLE, cancelled_event=AgendaNotificationsSettings.EMAIL_FIELD, cancelled_event_emails=['hop@entrouvert.com', 'top@entrouvert.com'], ) # again - check OneToOneField import_site(payload) def test_import_export_reminder_settings(): agenda = Agenda.objects.create(label='Foo bar', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') AgendaReminderSettings.objects.create( agenda=agenda, days_before_email=2, days_before_sms=1, email_extra_info='test', ) output = get_output_of_command('export_site') payload = json.loads(output) agenda.delete() assert not AgendaReminderSettings.objects.exists() import_site(payload) agenda = Agenda.objects.first() AgendaReminderSettings.objects.get( agenda=agenda, days_before_email=2, days_before_sms=1, email_extra_info='test', ) # again - check OneToOneField import_site(payload) @override_settings( EXCEPTIONS_SOURCES={ 'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'}, } ) def test_import_export_time_period_exception_source(): agenda = Agenda.objects.create(label='Foo bar', kind='meetings') desk = Desk.objects.create(slug='test', agenda=agenda) desk.import_timeperiod_exceptions_from_settings(enable=True) output = get_output_of_command('export_site') payload = json.loads(output) agenda.delete() assert not TimePeriodExceptionSource.objects.exists() import_site(payload) desk = Desk.objects.get(slug='test') source = desk.timeperiodexceptionsource_set.first() assert source.enabled assert desk.timeperiodexception_set.exists() source.disable() output = get_output_of_command('export_site') payload = json.loads(output) Agenda.objects.all().delete() import_site(payload) desk = Desk.objects.get(slug='test') source = desk.timeperiodexceptionsource_set.first() assert not source.enabled assert not desk.timeperiodexception_set.exists() @override_settings( EXCEPTIONS_SOURCES={ 'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'}, } ) def test_import_export_time_period_exception_source_enabled(): agenda = Agenda.objects.create(label='Foo bar', kind='events') desk = Desk.objects.create(agenda=agenda, slug='_exceptions_holder') desk.import_timeperiod_exceptions_from_settings() source = desk.timeperiodexceptionsource_set.first() assert not source.enabled assert source.settings_slug == 'holidays' source.enabled = True source.save() output = get_output_of_command('export_site') payload = json.loads(output) agenda.delete() assert not TimePeriodExceptionSource.objects.exists() import_site(payload) agenda = Agenda.objects.get(label='Foo bar') assert agenda.desk_set.count() == 1 desk = agenda.desk_set.first() assert desk.timeperiodexceptionsource_set.count() == 1 source = desk.timeperiodexceptionsource_set.first() assert source.enabled assert source.settings_slug == 'holidays' @mock.patch('chrono.agendas.models.requests.get') def test_import_export_time_period_exception_source_remote_ics(mocked_get): agenda = Agenda.objects.create(label='Foo bar', kind='meetings') desk = Desk.objects.create(label='Desk', agenda=agenda) source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='http://example.com/sample.ics') mocked_response = mock.Mock() mocked_response.text = ICS_SAMPLE mocked_get.return_value = mocked_response source.refresh_timeperiod_exceptions_from_ics() assert TimePeriodException.objects.count() == 2 output = get_output_of_command('export_site') payload = json.loads(output) Agenda.objects.all().delete() assert not TimePeriodExceptionSource.objects.exists() assert not TimePeriodException.objects.exists() import_site(payload) source = TimePeriodExceptionSource.objects.get() assert source.timeperiodexception_set.count() == 2 assert TimePeriodException.objects.count() == 2 # import again changes nothing import_site(payload) assert TimePeriodExceptionSource.objects.count() == 1 assert TimePeriodException.objects.count() == 2 # empty remote ics mocked_response.text = ICS_SAMPLE_WITH_NO_EVENTS Agenda.objects.all().delete() import_site(payload) assert TimePeriodExceptionSource.objects.count() == 1 assert TimePeriodException.objects.count() == 0 @mock.patch('chrono.agendas.models.requests.get') def test_import_export_time_period_exception_source_ics_file(mocked_get): agenda = Agenda.objects.create(label='Foo bar', kind='meetings') desk = Desk.objects.create(label='Desk', agenda=agenda) source = TimePeriodExceptionSource.objects.create( desk=desk, ics_filename='sample.ics', ics_file=ContentFile(ICS_SAMPLE, name='sample.ics') ) source.refresh_timeperiod_exceptions_from_ics() assert TimePeriodException.objects.count() == 2 output = get_output_of_command('export_site') payload = json.loads(output) Agenda.objects.all().delete() assert not TimePeriodExceptionSource.objects.exists() assert not TimePeriodException.objects.exists() import_site(payload) source = TimePeriodExceptionSource.objects.get() assert source.timeperiodexception_set.count() == 2 assert TimePeriodException.objects.count() == 2 # import again changes nothing import_site(payload) assert TimePeriodExceptionSource.objects.count() == 1 assert TimePeriodException.objects.count() == 2 payload['agendas'][0]['desks'][0]['exception_sources'][0]['ics_file'] = 'garbage' with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert '%s' % excinfo.value == 'Bad ics file' Agenda.objects.all().delete() payload['agendas'][0]['desks'][0]['exception_sources'][0]['ics_file'] = None import_site(payload) assert TimePeriodExceptionSource.objects.count() == 0 assert TimePeriodException.objects.count() == 0 @override_settings( EXCEPTIONS_SOURCES={ 'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'}, } ) def test_import_export_time_period_exception_legacy_file(): agenda = Agenda.objects.create(label='Foo bar', kind='events') desk = Desk.objects.create(agenda=agenda, slug='_exceptions_holder') desk.import_timeperiod_exceptions_from_settings() output = get_output_of_command('export_site') payload = json.loads(output) source = payload['agendas'][0]['exceptions_desk']['exception_sources'][0] del source['ics_file'] del source['ics_filename'] del source['ics_url'] agenda.delete() assert not TimePeriodExceptionSource.objects.exists() import_site(payload) assert TimePeriodExceptionSource.objects.count() == 1 def test_import_export_do_not_duplicate_timeperiod_and_exceptions(): agenda = Agenda.objects.create(label='Foo bar', kind='meetings') desk = Desk.objects.create(slug='test', agenda=agenda) TimePeriod.objects.create( desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0) ) TimePeriod.objects.create( desk=desk, weekday=3, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0) ) tpe1 = TimePeriodException.objects.create( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)), end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)), ) TimePeriodException.objects.create( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 6, 22, 8, 0)), end_datetime=make_aware(datetime.datetime(2017, 6, 22, 12, 30)), ) assert TimePeriod.objects.count() == 2 assert TimePeriodException.objects.count() == 2 output = get_output_of_command('export_site') payload = json.loads(output) import_site(payload) assert TimePeriod.objects.count() == 2 assert TimePeriodException.objects.count() == 2 # duplicated exception output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['agendas'][0]['desks'][0]['exceptions']) == 2 tpe2 = TimePeriodException.objects.create( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)), end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)), ) assert TimePeriod.objects.count() == 2 assert TimePeriodException.objects.count() == 3 assert tpe1.update_datetime < tpe2.update_datetime import_site(payload) assert TimePeriod.objects.count() == 2 assert TimePeriodException.objects.count() == 3 tpe1.refresh_from_db() tpe2.refresh_from_db() assert tpe1.update_datetime == tpe2.update_datetime def test_import_export_unavailability_calendar(app): output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['unavailability_calendars']) == 0 group1 = Group.objects.create(name='gé1') group2 = Group.objects.create(name='gé2') calendar = UnavailabilityCalendar.objects.create(label='Calendar', view_role=group1, edit_role=group2) tp1_start = make_aware(datetime.datetime(2017, 5, 22, 8, 0)) tp1_end = make_aware(datetime.datetime(2017, 5, 22, 12, 30)) tp1 = TimePeriodException.objects.create( unavailability_calendar=calendar, start_datetime=tp1_start, end_datetime=tp1_end ) tp2_start = make_aware(datetime.datetime(2018, 5, 22, 8, 0)) tp2_end = make_aware(datetime.datetime(2018, 5, 22, 12, 30)) tp2 = TimePeriodException.objects.create( unavailability_calendar=calendar, start_datetime=tp2_start, end_datetime=tp2_end ) meetings_agenda = Agenda.objects.create(label='Foo Bar', kind='meetings') MeetingType.objects.create(agenda=meetings_agenda, label='Meeting Type', duration=30) desk = Desk.objects.create(agenda=meetings_agenda, label='Desk') desk.unavailability_calendars.add(calendar) output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['unavailability_calendars']) == 1 assert len(payload['agendas']) == 1 calendar.delete() tp1.delete() tp2.delete() meetings_agenda.delete() assert not UnavailabilityCalendar.objects.exists() assert not TimePeriodException.objects.exists() assert not Agenda.objects.exists() assert not Desk.objects.exists() import_site(copy.deepcopy(payload)) assert UnavailabilityCalendar.objects.count() == 1 calendar = UnavailabilityCalendar.objects.first() assert calendar.label == 'Calendar' assert calendar.view_role == group1 assert calendar.edit_role == group2 assert calendar.timeperiodexception_set.count() == 2 assert TimePeriodException.objects.get( unavailability_calendar=calendar, start_datetime=tp1_start, end_datetime=tp1_end ) assert TimePeriodException.objects.get( unavailability_calendar=calendar, start_datetime=tp2_start, end_datetime=tp2_end ) agenda = Agenda.objects.get(label='Foo Bar') desk = agenda.desk_set.first() assert desk.unavailability_calendars.count() == 1 assert desk.unavailability_calendars.first() == calendar # update update_payload = copy.deepcopy(payload) update_payload['unavailability_calendars'][0]['label'] = 'Calendar Updated' import_site(update_payload) calendar.refresh_from_db() assert calendar.label == 'Calendar Updated' def test_import_export_category(app): output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['categories']) == 0 category = Category.objects.create(label='Foo bar') output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['categories']) == 1 category.delete() assert not Category.objects.exists() import_site(copy.deepcopy(payload)) assert Category.objects.count() == 1 category = Category.objects.first() assert category.label == 'Foo bar' assert category.slug == 'foo-bar' # update update_payload = copy.deepcopy(payload) update_payload['categories'][0]['label'] = 'Foo bar Updated' import_site(update_payload) category.refresh_from_db() assert category.label == 'Foo bar Updated' # insert another category category.slug = 'foo-bar-updated' category.save() import_site(copy.deepcopy(payload)) assert Category.objects.count() == 2 category = Category.objects.latest('pk') assert category.label == 'Foo bar' assert category.slug == 'foo-bar' def test_import_export_events_type(app): output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['events_types']) == 0 events_type = EventsType.objects.create(label='Foo bar') output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['events_types']) == 1 events_type.delete() assert not EventsType.objects.exists() import_site(copy.deepcopy(payload)) assert EventsType.objects.count() == 1 events_type = EventsType.objects.first() assert events_type.label == 'Foo bar' assert events_type.slug == 'foo-bar' # update update_payload = copy.deepcopy(payload) update_payload['events_types'][0]['label'] = 'Foo bar Updated' import_site(update_payload) events_type.refresh_from_db() assert events_type.label == 'Foo bar Updated' # insert another events_type events_type.slug = 'foo-bar-updated' events_type.save() import_site(copy.deepcopy(payload)) assert EventsType.objects.count() == 2 events_type = EventsType.objects.latest('pk') assert events_type.label == 'Foo bar' assert events_type.slug == 'foo-bar' def test_import_export_resource(app): output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['resources']) == 0 resource = Resource.objects.create(label='Foo bar') output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['resources']) == 1 resource.delete() assert not Resource.objects.exists() import_site(copy.deepcopy(payload)) assert Resource.objects.count() == 1 resource = Resource.objects.first() assert resource.label == 'Foo bar' assert resource.slug == 'foo-bar' # update update_payload = copy.deepcopy(payload) update_payload['resources'][0]['label'] = 'Foo bar Updated' import_site(update_payload) resource.refresh_from_db() assert resource.label == 'Foo bar Updated' # insert another resource resource.slug = 'foo-bar-updated' resource.save() import_site(copy.deepcopy(payload)) assert Resource.objects.count() == 2 resource = Resource.objects.latest('pk') assert resource.label == 'Foo bar' assert resource.slug == 'foo-bar' @mock.patch('chrono.agendas.models.Agenda.is_available_for_simple_management') def test_import_export_desk_simple_management(available_mock): agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True) output = get_output_of_command('export_site') payload = json.loads(output) assert len(payload['agendas']) == 1 assert payload['agendas'][0]['desk_simple_management'] is True # check on creation for available_value in [True, False]: agenda.delete() available_mock.return_value = available_value import_site(payload) agenda = Agenda.objects.latest('pk') assert agenda.desk_simple_management == available_value payload['agendas'][0]['desk_simple_management'] = False for available_value in [True, False]: # always ok to set flag to False agenda.delete() available_mock.return_value = available_value import_site(payload) agenda = Agenda.objects.latest('pk') assert agenda.desk_simple_management is False # check on update payload['agendas'][0]['desk_simple_management'] = True for available_value in [True, False]: agenda.desk_simple_management = False agenda.save() available_mock.return_value = available_value import_site(payload) agenda.refresh_from_db() assert agenda.desk_simple_management == available_value # already True, no change agenda.desk_simple_management = True agenda.save() available_mock.return_value = available_value import_site(payload) agenda.refresh_from_db() assert agenda.desk_simple_management is True payload['agendas'][0]['desk_simple_management'] = False for available_value in [True, False]: # always ok to set flag to False for old_value in [True, False]: agenda.desk_simple_management = old_value agenda.save() available_mock.return_value = available_value import_site(payload) agenda.refresh_from_db() assert agenda.desk_simple_management is False # not in payload, no changed del payload['agendas'][0]['desk_simple_management'] for available_value in [True, False]: for old_value in [True, False]: agenda.desk_simple_management = old_value agenda.save() available_mock.return_value = available_value import_site(payload) agenda.refresh_from_db() assert agenda.desk_simple_management == old_value def test_export_deleted_meetingtype(app): agenda = Agenda.objects.create(label='Agenda', kind='meetings') meeting_type = MeetingType.objects.create(agenda=agenda, label='Meeting Type', duration=30) output = get_output_of_command('export_site') assert len(json.loads(output)['agendas'][0]['meetingtypes']) == 1 meeting_type.deleted = True meeting_type.save() output = get_output_of_command('export_site') assert len(json.loads(output)['agendas'][0]['meetingtypes']) == 0 def test_import_export_agenda_update(app): agenda = Agenda.objects.create(label='Foo Bar', kind='meetings') output = get_output_of_command('export_site') data = json.loads(output) assert len(['agendas']) == 1 import_site(data={}, clean=True) import_site(data) agenda = Agenda.objects.get() assert agenda.label == 'Foo Bar' data['agendas'][0]['label'] = 'Bar Foo' import_site(data) agenda = Agenda.objects.get() assert agenda.label == 'Bar Foo' def test_import_export_shared_custody_settings(app): group = Group.objects.create(name='test') calendar = UnavailabilityCalendar.objects.create(label='Calendar') SharedCustodySettings.objects.create(management_role=group, holidays_calendar=calendar) output = get_output_of_command('export_site') import_site(data={}, clean=True) assert SharedCustodySettings.get_singleton().management_role is None Group.objects.all().delete() payload = json.loads(output) del payload['unavailability_calendars'] with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert '%s' % excinfo.value == 'Missing roles: "test"' group = Group.objects.create(name='test') with pytest.raises(AgendaImportError) as excinfo: import_site(payload) assert '%s' % excinfo.value == 'The unavailability calendar "calendar" does not exist.' import_site(json.loads(output)) settings = SharedCustodySettings.get_singleton() assert settings.management_role == group assert settings.holidays_calendar.slug == calendar.slug group2 = Group.objects.create(name='test2') settings.management_role = group2 settings.save() import_site(json.loads(output)) settings = SharedCustodySettings.get_singleton() assert settings.management_role == group