From 67df06d721fadb0386d2f34ec290025cb6eec542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Wed, 8 Dec 2021 17:58:20 +0100 Subject: [PATCH 1/2] manager: import/export categories (#57424) --- chrono/agendas/models.py | 13 +++++ chrono/manager/forms.py | 1 + chrono/manager/utils.py | 18 ++++++- tests/manager/test_all.py | 36 -------------- .../{test_import.py => test_import_export.py} | 43 ++++++++++++++++ tests/test_import_export.py | 49 +++++++++++++++++-- 6 files changed, 117 insertions(+), 43 deletions(-) rename tests/manager/{test_import.py => test_import_export.py} (89%) diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 052a8cb4..0a442ec6 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -2166,6 +2166,19 @@ class Category(models.Model): def base_slug(self): return slugify(self.label) + @classmethod + def import_json(cls, data, overwrite=False): + data = clean_import_data(cls, data) + slug = data.pop('slug') + category, created = cls.objects.update_or_create(slug=slug, defaults=data) + return created, category + + def export_json(self): + return { + 'label': self.label, + 'slug': self.slug, + } + def ics_directory_path(instance, filename): return f'ics/{str(uuid.uuid4())}/{filename}' diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py index 690fc9d4..40f6b1f3 100644 --- a/chrono/manager/forms.py +++ b/chrono/manager/forms.py @@ -922,3 +922,4 @@ class AgendasExportForm(forms.Form): label=_('Unavailability calendars'), required=False, initial=True ) absence_reason_groups = forms.BooleanField(label=_('Absence reason groups'), required=False, initial=True) + categories = forms.BooleanField(label=_('Categories'), required=False, initial=True) diff --git a/chrono/manager/utils.py b/chrono/manager/utils.py index 005ae66d..a2aaea28 100644 --- a/chrono/manager/utils.py +++ b/chrono/manager/utils.py @@ -21,12 +21,20 @@ from django.contrib.auth.models import Group from django.db import transaction from django.db.models import Q -from chrono.agendas.models import AbsenceReasonGroup, Agenda, AgendaImportError, UnavailabilityCalendar +from chrono.agendas.models import ( + AbsenceReasonGroup, + Agenda, + AgendaImportError, + Category, + UnavailabilityCalendar, +) -def export_site(agendas=True, unavailability_calendars=True, absence_reason_groups=True): +def export_site(agendas=True, unavailability_calendars=True, absence_reason_groups=True, categories=True): '''Dump site objects to JSON-dumpable dictionnary''' data = collections.OrderedDict() + if categories: + data['categories'] = [x.export_json() for x in Category.objects.all()] if absence_reason_groups: data['absence_reason_groups'] = [x.export_json() for x in AbsenceReasonGroup.objects.all()] if unavailability_calendars: @@ -43,6 +51,7 @@ def import_site(data, if_empty=False, clean=False, overwrite=False): Agenda.objects.exists() or UnavailabilityCalendar.objects.exists() or AbsenceReasonGroup.objects.exists() + or Category.objects.exists() ): return @@ -50,6 +59,7 @@ def import_site(data, if_empty=False, clean=False, overwrite=False): Agenda.objects.all().delete() UnavailabilityCalendar.objects.all().delete() AbsenceReasonGroup.objects.all().delete() + Category.objects.all().delete() results = { 'agendas': collections.defaultdict(list), @@ -59,6 +69,7 @@ def import_site(data, if_empty=False, clean=False, overwrite=False): agendas = data.get('agendas', []) unavailability_calendars = data.get('unavailability_calendars', []) absence_reason_groups = data.get('absence_reason_groups', []) + categories = data.get('categories', []) role_names = set() for objs in (agendas, unavailability_calendars): @@ -71,6 +82,9 @@ def import_site(data, if_empty=False, clean=False, overwrite=False): existing_roles_names = set(existing_roles.values_list('name', flat=True)) raise AgendaImportError('Missing roles: "%s"' % ', '.join(role_names - existing_roles_names)) + for category in categories: + Category.import_json(category, overwrite=overwrite) + with transaction.atomic(): for objs, cls, label in ( (absence_reason_groups, AbsenceReasonGroup, 'absence_reason_groups'), diff --git a/tests/manager/test_all.py b/tests/manager/test_all.py index 216acc9c..688d7b63 100644 --- a/tests/manager/test_all.py +++ b/tests/manager/test_all.py @@ -1,5 +1,4 @@ import datetime -import json from unittest import mock import freezegun @@ -2914,41 +2913,6 @@ def test_manager_reminders_preview(app, admin_user): ) -def test_export_site(app, admin_user): - login(app) - resp = app.get('/manage/') - resp = resp.click('Export') - - with freezegun.freeze_time('2020-06-15'): - resp = resp.form.submit() - assert resp.headers['content-type'] == 'application/json' - assert resp.headers['content-disposition'] == 'attachment; filename="export_agendas_20200615.json"' - - site_json = json.loads(resp.text) - assert site_json == {'unavailability_calendars': [], 'agendas': [], 'absence_reason_groups': []} - - agenda = Agenda.objects.create(label='Foo Bar', kind='events') - Desk.objects.create(agenda=agenda, slug='_exceptions_holder') - UnavailabilityCalendar.objects.create(label='Calendar 1') - resp = app.get('/manage/agendas/export/') - resp = resp.form.submit() - - site_json = json.loads(resp.text) - assert len(site_json['agendas']) == 1 - assert len(site_json['unavailability_calendars']) == 1 - assert len(site_json['absence_reason_groups']) == 0 - - resp = app.get('/manage/agendas/export/') - resp.form['agendas'] = False - resp.form['absence_reason_groups'] = False - resp = resp.form.submit() - - site_json = json.loads(resp.text) - assert 'agendas' not in site_json - assert 'unavailability_calendars' in site_json - assert 'absence_reason_groups' not in site_json - - def test_manager_agenda_roles(app, admin_user, manager_user): agenda = Agenda.objects.create(label='Events', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') diff --git a/tests/manager/test_import.py b/tests/manager/test_import_export.py similarity index 89% rename from tests/manager/test_import.py rename to tests/manager/test_import_export.py index 768a13ba..e4d98559 100644 --- a/tests/manager/test_import.py +++ b/tests/manager/test_import_export.py @@ -23,6 +23,49 @@ from .test_all import login pytestmark = pytest.mark.django_db +def test_export_site(app, admin_user): + login(app) + resp = app.get('/manage/') + resp = resp.click('Export') + + with freezegun.freeze_time('2020-06-15'): + resp = resp.form.submit() + assert resp.headers['content-type'] == 'application/json' + assert resp.headers['content-disposition'] == 'attachment; filename="export_agendas_20200615.json"' + + site_json = json.loads(resp.text) + assert site_json == { + 'unavailability_calendars': [], + 'agendas': [], + 'absence_reason_groups': [], + 'categories': [], + } + + agenda = Agenda.objects.create(label='Foo Bar', kind='events') + Desk.objects.create(agenda=agenda, slug='_exceptions_holder') + UnavailabilityCalendar.objects.create(label='Calendar 1') + resp = app.get('/manage/agendas/export/') + resp = resp.form.submit() + + site_json = json.loads(resp.text) + assert len(site_json['agendas']) == 1 + assert len(site_json['unavailability_calendars']) == 1 + assert len(site_json['absence_reason_groups']) == 0 + assert len(site_json['categories']) == 0 + + resp = app.get('/manage/agendas/export/') + resp.form['agendas'] = False + resp.form['absence_reason_groups'] = False + resp.form['categories'] = False + resp = resp.form.submit() + + site_json = json.loads(resp.text) + assert 'agendas' not in site_json + assert 'unavailability_calendars' in site_json + assert 'absence_reason_groups' not in site_json + assert 'categories' not in site_json + + def test_import_agenda_as_manager(app, manager_user): # open /manage/ access to manager_user, and check agenda import is not # allowed. diff --git a/tests/test_import_export.py b/tests/test_import_export.py index 9633706c..49660e91 100644 --- a/tests/test_import_export.py +++ b/tests/test_import_export.py @@ -374,7 +374,7 @@ def test_import_export_resources(app): assert list(agenda.resources.all()) == [resource] -def test_import_export_categorys(app): +def test_import_export_categories(app): category = Category.objects.create(label='foo') agenda = Agenda.objects.create(label='Foo Bar', category=category) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') @@ -382,19 +382,21 @@ def test_import_export_categorys(app): import_site(data={}, clean=True) assert Agenda.objects.count() == 0 - category.delete() + assert Category.objects.count() == 0 + data = json.loads(output) + del data['categories'] with pytest.raises(AgendaImportError) as excinfo: - import_site(json.loads(output), overwrite=True) + import_site(data, overwrite=True) assert str(excinfo.value) == 'Missing "foo" category' category = Category.objects.create(label='foobar') with pytest.raises(AgendaImportError) as excinfo: - import_site(json.loads(output), overwrite=True) + import_site(data, overwrite=True) assert str(excinfo.value) == 'Missing "foo" category' category = Category.objects.create(label='foo') - import_site(json.loads(output), overwrite=True) + import_site(data, overwrite=True) agenda = Agenda.objects.get(slug=agenda.slug) assert agenda.category == category @@ -932,6 +934,43 @@ def test_import_export_absence_reason_group(app): assert AbsenceReason.objects.get(group=group, label='Baz', slug='baz') +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' + + @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) -- 2.30.2