manager: import/export categories (#57424)

This commit is contained in:
Lauréline Guérin 2021-12-08 17:58:20 +01:00 committed by Valentin Deniaud
parent 842d7629d8
commit f974cfe604
6 changed files with 117 additions and 43 deletions

View File

@ -2178,6 +2178,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}'

View File

@ -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)

View File

@ -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'),

View File

@ -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')

View File

@ -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.

View File

@ -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)