0001-manager-import-export-categories-57424.patch
chrono/agendas/models.py | ||
---|---|---|
2166 | 2166 |
def base_slug(self): |
2167 | 2167 |
return slugify(self.label) |
2168 | 2168 | |
2169 |
@classmethod |
|
2170 |
def import_json(cls, data, overwrite=False): |
|
2171 |
data = clean_import_data(cls, data) |
|
2172 |
slug = data.pop('slug') |
|
2173 |
category, created = cls.objects.update_or_create(slug=slug, defaults=data) |
|
2174 |
return created, category |
|
2175 | ||
2176 |
def export_json(self): |
|
2177 |
return { |
|
2178 |
'label': self.label, |
|
2179 |
'slug': self.slug, |
|
2180 |
} |
|
2181 | ||
2169 | 2182 | |
2170 | 2183 |
def ics_directory_path(instance, filename): |
2171 | 2184 |
return f'ics/{str(uuid.uuid4())}/{filename}' |
chrono/manager/forms.py | ||
---|---|---|
922 | 922 |
label=_('Unavailability calendars'), required=False, initial=True |
923 | 923 |
) |
924 | 924 |
absence_reason_groups = forms.BooleanField(label=_('Absence reason groups'), required=False, initial=True) |
925 |
categories = forms.BooleanField(label=_('Categories'), required=False, initial=True) |
chrono/manager/utils.py | ||
---|---|---|
21 | 21 |
from django.db import transaction |
22 | 22 |
from django.db.models import Q |
23 | 23 | |
24 |
from chrono.agendas.models import AbsenceReasonGroup, Agenda, AgendaImportError, UnavailabilityCalendar |
|
24 |
from chrono.agendas.models import ( |
|
25 |
AbsenceReasonGroup, |
|
26 |
Agenda, |
|
27 |
AgendaImportError, |
|
28 |
Category, |
|
29 |
UnavailabilityCalendar, |
|
30 |
) |
|
25 | 31 | |
26 | 32 | |
27 |
def export_site(agendas=True, unavailability_calendars=True, absence_reason_groups=True): |
|
33 |
def export_site(agendas=True, unavailability_calendars=True, absence_reason_groups=True, categories=True):
|
|
28 | 34 |
'''Dump site objects to JSON-dumpable dictionnary''' |
29 | 35 |
data = collections.OrderedDict() |
36 |
if categories: |
|
37 |
data['categories'] = [x.export_json() for x in Category.objects.all()] |
|
30 | 38 |
if absence_reason_groups: |
31 | 39 |
data['absence_reason_groups'] = [x.export_json() for x in AbsenceReasonGroup.objects.all()] |
32 | 40 |
if unavailability_calendars: |
... | ... | |
43 | 51 |
Agenda.objects.exists() |
44 | 52 |
or UnavailabilityCalendar.objects.exists() |
45 | 53 |
or AbsenceReasonGroup.objects.exists() |
54 |
or Category.objects.exists() |
|
46 | 55 |
): |
47 | 56 |
return |
48 | 57 | |
... | ... | |
50 | 59 |
Agenda.objects.all().delete() |
51 | 60 |
UnavailabilityCalendar.objects.all().delete() |
52 | 61 |
AbsenceReasonGroup.objects.all().delete() |
62 |
Category.objects.all().delete() |
|
53 | 63 | |
54 | 64 |
results = { |
55 | 65 |
'agendas': collections.defaultdict(list), |
... | ... | |
59 | 69 |
agendas = data.get('agendas', []) |
60 | 70 |
unavailability_calendars = data.get('unavailability_calendars', []) |
61 | 71 |
absence_reason_groups = data.get('absence_reason_groups', []) |
72 |
categories = data.get('categories', []) |
|
62 | 73 | |
63 | 74 |
role_names = set() |
64 | 75 |
for objs in (agendas, unavailability_calendars): |
... | ... | |
71 | 82 |
existing_roles_names = set(existing_roles.values_list('name', flat=True)) |
72 | 83 |
raise AgendaImportError('Missing roles: "%s"' % ', '.join(role_names - existing_roles_names)) |
73 | 84 | |
85 |
for category in categories: |
|
86 |
Category.import_json(category, overwrite=overwrite) |
|
87 | ||
74 | 88 |
with transaction.atomic(): |
75 | 89 |
for objs, cls, label in ( |
76 | 90 |
(absence_reason_groups, AbsenceReasonGroup, 'absence_reason_groups'), |
tests/manager/test_all.py | ||
---|---|---|
1 | 1 |
import datetime |
2 |
import json |
|
3 | 2 |
from unittest import mock |
4 | 3 | |
5 | 4 |
import freezegun |
... | ... | |
2914 | 2913 |
) |
2915 | 2914 | |
2916 | 2915 | |
2917 |
def test_export_site(app, admin_user): |
|
2918 |
login(app) |
|
2919 |
resp = app.get('/manage/') |
|
2920 |
resp = resp.click('Export') |
|
2921 | ||
2922 |
with freezegun.freeze_time('2020-06-15'): |
|
2923 |
resp = resp.form.submit() |
|
2924 |
assert resp.headers['content-type'] == 'application/json' |
|
2925 |
assert resp.headers['content-disposition'] == 'attachment; filename="export_agendas_20200615.json"' |
|
2926 | ||
2927 |
site_json = json.loads(resp.text) |
|
2928 |
assert site_json == {'unavailability_calendars': [], 'agendas': [], 'absence_reason_groups': []} |
|
2929 | ||
2930 |
agenda = Agenda.objects.create(label='Foo Bar', kind='events') |
|
2931 |
Desk.objects.create(agenda=agenda, slug='_exceptions_holder') |
|
2932 |
UnavailabilityCalendar.objects.create(label='Calendar 1') |
|
2933 |
resp = app.get('/manage/agendas/export/') |
|
2934 |
resp = resp.form.submit() |
|
2935 | ||
2936 |
site_json = json.loads(resp.text) |
|
2937 |
assert len(site_json['agendas']) == 1 |
|
2938 |
assert len(site_json['unavailability_calendars']) == 1 |
|
2939 |
assert len(site_json['absence_reason_groups']) == 0 |
|
2940 | ||
2941 |
resp = app.get('/manage/agendas/export/') |
|
2942 |
resp.form['agendas'] = False |
|
2943 |
resp.form['absence_reason_groups'] = False |
|
2944 |
resp = resp.form.submit() |
|
2945 | ||
2946 |
site_json = json.loads(resp.text) |
|
2947 |
assert 'agendas' not in site_json |
|
2948 |
assert 'unavailability_calendars' in site_json |
|
2949 |
assert 'absence_reason_groups' not in site_json |
|
2950 | ||
2951 | ||
2952 | 2916 |
def test_manager_agenda_roles(app, admin_user, manager_user): |
2953 | 2917 |
agenda = Agenda.objects.create(label='Events', kind='events') |
2954 | 2918 |
Desk.objects.create(agenda=agenda, slug='_exceptions_holder') |
tests/manager/test_import.py → tests/manager/test_import_export.py | ||
---|---|---|
23 | 23 |
pytestmark = pytest.mark.django_db |
24 | 24 | |
25 | 25 | |
26 |
def test_export_site(app, admin_user): |
|
27 |
login(app) |
|
28 |
resp = app.get('/manage/') |
|
29 |
resp = resp.click('Export') |
|
30 | ||
31 |
with freezegun.freeze_time('2020-06-15'): |
|
32 |
resp = resp.form.submit() |
|
33 |
assert resp.headers['content-type'] == 'application/json' |
|
34 |
assert resp.headers['content-disposition'] == 'attachment; filename="export_agendas_20200615.json"' |
|
35 | ||
36 |
site_json = json.loads(resp.text) |
|
37 |
assert site_json == { |
|
38 |
'unavailability_calendars': [], |
|
39 |
'agendas': [], |
|
40 |
'absence_reason_groups': [], |
|
41 |
'categories': [], |
|
42 |
} |
|
43 | ||
44 |
agenda = Agenda.objects.create(label='Foo Bar', kind='events') |
|
45 |
Desk.objects.create(agenda=agenda, slug='_exceptions_holder') |
|
46 |
UnavailabilityCalendar.objects.create(label='Calendar 1') |
|
47 |
resp = app.get('/manage/agendas/export/') |
|
48 |
resp = resp.form.submit() |
|
49 | ||
50 |
site_json = json.loads(resp.text) |
|
51 |
assert len(site_json['agendas']) == 1 |
|
52 |
assert len(site_json['unavailability_calendars']) == 1 |
|
53 |
assert len(site_json['absence_reason_groups']) == 0 |
|
54 |
assert len(site_json['categories']) == 0 |
|
55 | ||
56 |
resp = app.get('/manage/agendas/export/') |
|
57 |
resp.form['agendas'] = False |
|
58 |
resp.form['absence_reason_groups'] = False |
|
59 |
resp.form['categories'] = False |
|
60 |
resp = resp.form.submit() |
|
61 | ||
62 |
site_json = json.loads(resp.text) |
|
63 |
assert 'agendas' not in site_json |
|
64 |
assert 'unavailability_calendars' in site_json |
|
65 |
assert 'absence_reason_groups' not in site_json |
|
66 |
assert 'categories' not in site_json |
|
67 | ||
68 | ||
26 | 69 |
def test_import_agenda_as_manager(app, manager_user): |
27 | 70 |
# open /manage/ access to manager_user, and check agenda import is not |
28 | 71 |
# allowed. |
tests/test_import_export.py | ||
---|---|---|
374 | 374 |
assert list(agenda.resources.all()) == [resource] |
375 | 375 | |
376 | 376 | |
377 |
def test_import_export_categorys(app):
|
|
377 |
def test_import_export_categories(app):
|
|
378 | 378 |
category = Category.objects.create(label='foo') |
379 | 379 |
agenda = Agenda.objects.create(label='Foo Bar', category=category) |
380 | 380 |
Desk.objects.create(agenda=agenda, slug='_exceptions_holder') |
... | ... | |
382 | 382 | |
383 | 383 |
import_site(data={}, clean=True) |
384 | 384 |
assert Agenda.objects.count() == 0 |
385 |
category.delete() |
|
385 |
assert Category.objects.count() == 0 |
|
386 |
data = json.loads(output) |
|
387 |
del data['categories'] |
|
386 | 388 | |
387 | 389 |
with pytest.raises(AgendaImportError) as excinfo: |
388 |
import_site(json.loads(output), overwrite=True)
|
|
390 |
import_site(data, overwrite=True)
|
|
389 | 391 |
assert str(excinfo.value) == 'Missing "foo" category' |
390 | 392 | |
391 | 393 |
category = Category.objects.create(label='foobar') |
392 | 394 |
with pytest.raises(AgendaImportError) as excinfo: |
393 |
import_site(json.loads(output), overwrite=True)
|
|
395 |
import_site(data, overwrite=True)
|
|
394 | 396 |
assert str(excinfo.value) == 'Missing "foo" category' |
395 | 397 | |
396 | 398 |
category = Category.objects.create(label='foo') |
397 |
import_site(json.loads(output), overwrite=True)
|
|
399 |
import_site(data, overwrite=True)
|
|
398 | 400 |
agenda = Agenda.objects.get(slug=agenda.slug) |
399 | 401 |
assert agenda.category == category |
400 | 402 | |
... | ... | |
932 | 934 |
assert AbsenceReason.objects.get(group=group, label='Baz', slug='baz') |
933 | 935 | |
934 | 936 | |
937 |
def test_import_export_category(app): |
|
938 |
output = get_output_of_command('export_site') |
|
939 |
payload = json.loads(output) |
|
940 |
assert len(payload['categories']) == 0 |
|
941 | ||
942 |
category = Category.objects.create(label='Foo bar') |
|
943 | ||
944 |
output = get_output_of_command('export_site') |
|
945 |
payload = json.loads(output) |
|
946 |
assert len(payload['categories']) == 1 |
|
947 | ||
948 |
category.delete() |
|
949 |
assert not Category.objects.exists() |
|
950 | ||
951 |
import_site(copy.deepcopy(payload)) |
|
952 |
assert Category.objects.count() == 1 |
|
953 |
category = Category.objects.first() |
|
954 |
assert category.label == 'Foo bar' |
|
955 |
assert category.slug == 'foo-bar' |
|
956 | ||
957 |
# update |
|
958 |
update_payload = copy.deepcopy(payload) |
|
959 |
update_payload['categories'][0]['label'] = 'Foo bar Updated' |
|
960 |
import_site(update_payload) |
|
961 |
category.refresh_from_db() |
|
962 |
assert category.label == 'Foo bar Updated' |
|
963 | ||
964 |
# insert another category |
|
965 |
category.slug = 'foo-bar-updated' |
|
966 |
category.save() |
|
967 |
import_site(copy.deepcopy(payload)) |
|
968 |
assert Category.objects.count() == 2 |
|
969 |
category = Category.objects.latest('pk') |
|
970 |
assert category.label == 'Foo bar' |
|
971 |
assert category.slug == 'foo-bar' |
|
972 | ||
973 | ||
935 | 974 |
@mock.patch('chrono.agendas.models.Agenda.is_available_for_simple_management') |
936 | 975 |
def test_import_export_desk_simple_management(available_mock): |
937 | 976 |
agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True) |
938 |
- |