Compare commits

...

2 Commits

Author SHA1 Message Date
Lauréline Guérin 6485540db6
api: complete agendas dependencies (#82703) 2023-10-24 10:18:44 +02:00
Lauréline Guérin 7bb072e596
api: make import/export endpoints generic for other kinds of objects (#82703) 2023-10-24 10:18:44 +02:00
5 changed files with 366 additions and 95 deletions

View File

@ -308,6 +308,10 @@ class Agenda(models.Model):
validators=[MaxValueValidator(59)],
)
application_component_type = 'agendas'
application_label_singular = _('Agenda')
application_label_plural = _('Agendas')
class Meta:
ordering = ['label']
@ -441,8 +445,15 @@ class Agenda(models.Model):
def get_dependencies(self):
yield self.view_role
yield self.edit_role
yield self.category
if self.kind == 'virtual':
yield from self.real_agendas.all()
if self.kind == 'meetings':
yield from self.resources.all()
for desk in self.desk_set.all():
yield from desk.get_dependencies()
if self.kind == 'events':
yield self.events_type
def export_json(self):
agenda = {
@ -2717,6 +2728,10 @@ class EventsType(models.Model):
label = models.CharField(_('Label'), max_length=150)
custom_fields = models.JSONField(blank=True, default=list)
application_component_type = 'events_types'
application_label_singular = _('Events type')
application_label_plural = _('Events types')
def __str__(self):
return self.label
@ -2748,6 +2763,9 @@ class EventsType(models.Model):
custom_fields.append(values)
return custom_fields
def get_dependencies(self):
return []
@classmethod
def import_json(cls, data, overwrite=False):
data = clean_import_data(cls, data)
@ -3126,6 +3144,9 @@ class Desk(models.Model):
def base_slug(self):
return slugify(self.label)
def get_dependencies(self):
yield from self.unavailability_calendars.all()
@classmethod
def import_json(cls, data):
timeperiods = data.pop('timeperiods', [])
@ -3270,6 +3291,10 @@ class Resource(models.Model):
label = models.CharField(_('Label'), max_length=150)
description = models.TextField(_('Description'), blank=True, help_text=_('Optional description.'))
application_component_type = 'resources'
application_label_singular = _('Resource')
application_label_plural = _('Resources')
def __str__(self):
return self.label
@ -3291,6 +3316,9 @@ class Resource(models.Model):
group_ids = [x.id for x in user.groups.all()]
return self.agenda_set.filter(edit_role_id__in=group_ids).exists()
def get_dependencies(self):
return []
@classmethod
def import_json(cls, data, overwrite=False):
data = clean_import_data(cls, data)
@ -3310,6 +3338,10 @@ class Category(models.Model):
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
label = models.CharField(_('Label'), max_length=150)
application_component_type = 'agendas_categories'
application_label_singular = _('Category (agendas)')
application_label_plural = _('Categories (agendas)')
def __str__(self):
return self.label
@ -3325,6 +3357,9 @@ class Category(models.Model):
def base_slug(self):
return slugify(self.label)
def get_dependencies(self):
return []
@classmethod
def import_json(cls, data, overwrite=False):
data = clean_import_data(cls, data)
@ -3638,6 +3673,10 @@ class UnavailabilityCalendar(models.Model):
on_delete=models.SET_NULL,
)
application_component_type = 'unavailability_calendars'
application_label_singular = _('Unavailability calendar')
application_label_plural = _('Unavailability calendars')
class Meta:
ordering = ['label']
@ -3668,6 +3707,10 @@ class UnavailabilityCalendar(models.Model):
def get_absolute_url(self):
return reverse('chrono-manager-unavailability-calendar-view', kwargs={'pk': self.id})
def get_dependencies(self):
yield self.view_role
yield self.edit_role
def export_json(self):
unavailability_calendar = {
'label': self.label,

View File

@ -19,87 +19,123 @@ import json
import tarfile
from django.contrib.auth.models import Group
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from rest_framework import permissions
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from chrono.agendas.models import Agenda
from chrono.agendas.models import Agenda, Category, EventsType, Resource, UnavailabilityCalendar
from chrono.apps.export_import.models import Application, ApplicationElement
from chrono.manager.utils import import_site
klasses = {
klass.application_component_type: klass
for klass in [Agenda, Category, EventsType, Resource, UnavailabilityCalendar]
}
klasses_translation = {
'agendas_categories': 'categories',
}
klasses_translation_reverse = {v: k for k, v in klasses_translation.items()}
class Index(GenericAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get(self, request, *args, **kwargs):
response = [
{
'id': 'agendas',
'text': _('Agendas'),
'singular': _('Agenda'),
data = []
for klass in klasses.values():
component_type = {
'id': klass.application_component_type,
'text': klass.application_label_plural,
'singular': klass.application_label_singular,
'urls': {
'list': request.build_absolute_uri(reverse('api-export-import-agendas-list')),
'list': request.build_absolute_uri(
reverse(
'api-export-import-components-list',
kwargs={'component_type': klass.application_component_type},
)
),
},
},
]
}
if klass not in [Agenda]:
component_type['minor'] = True
data.append(component_type)
return Response({'data': response})
return Response({'data': data})
index = Index.as_view()
def get_agenda_bundle_entry(request, agenda, order):
def get_component_bundle_entry(request, component):
return {
'id': str(agenda.slug),
'text': agenda.label,
'type': 'agendas',
'id': str(component.slug),
'text': component.label,
'type': component.application_component_type,
'urls': {
'export': request.build_absolute_uri(
reverse('api-export-import-agenda-export', kwargs={'slug': str(agenda.slug)})
reverse(
'api-export-import-component-export',
kwargs={
'component_type': component.application_component_type,
'slug': str(component.slug),
},
)
),
'dependencies': request.build_absolute_uri(
reverse('api-export-import-agenda-dependencies', kwargs={'slug': str(agenda.slug)})
reverse(
'api-export-import-component-dependencies',
kwargs={
'component_type': component.application_component_type,
'slug': str(component.slug),
},
)
),
'redirect': request.build_absolute_uri(
reverse('api-export-import-agenda-redirect', kwargs={'slug': str(agenda.slug)})
reverse(
'api-export-import-component-redirect',
kwargs={
'component_type': component.application_component_type,
'slug': str(component.slug),
},
)
),
},
}
class ListAgendas(GenericAPIView):
class ListComponents(GenericAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get(self, request, *args, **kwargs):
response = [
get_agenda_bundle_entry(request, x, i) for i, x in enumerate(Agenda.objects.order_by('slug'))
]
klass = klasses[kwargs['component_type']]
response = [get_component_bundle_entry(request, x) for x in klass.objects.order_by('slug')]
return Response({'data': response})
list_agendas = ListAgendas.as_view()
list_components = ListComponents.as_view()
class ExportAgenda(GenericAPIView):
class ExportComponent(GenericAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get(self, request, slug, *args, **kwargs):
serialisation = Agenda.objects.get(slug=slug).export_json()
klass = klasses[kwargs['component_type']]
serialisation = klass.objects.get(slug=slug).export_json()
return Response({'data': serialisation})
export_agenda = ExportAgenda.as_view()
export_component = ExportComponent.as_view()
class AgendaDependencies(GenericAPIView):
class ComponentDependencies(GenericAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get(self, request, slug, *args, **kwargs):
agenda = Agenda.objects.get(slug=slug)
klass = klasses[kwargs['component_type']]
component = klass.objects.get(slug=slug)
def dependency_dict(element):
if isinstance(element, Group):
@ -109,20 +145,21 @@ class AgendaDependencies(GenericAPIView):
'type': 'roles',
'urls': {},
}
elif isinstance(element, Agenda):
return get_agenda_bundle_entry(request, element, 0)
return element
return get_component_bundle_entry(request, element)
dependencies = [dependency_dict(x) for x in agenda.get_dependencies() if x]
dependencies = [dependency_dict(x) for x in component.get_dependencies() if x]
return Response({'err': 0, 'data': dependencies})
agenda_dependencies = AgendaDependencies.as_view()
component_dependencies = ComponentDependencies.as_view()
def agenda_redirect(request, slug):
agenda = get_object_or_404(Agenda, slug=slug)
return redirect(reverse('chrono-manager-agenda-view', kwargs={'pk': agenda.pk}))
def component_redirect(request, component_type, slug):
klass = klasses[component_type]
component = get_object_or_404(klass, slug=slug)
if klass == Agenda:
return redirect(reverse('chrono-manager-agenda-view', kwargs={'pk': component.pk}))
raise Http404
class BundleCheck(GenericAPIView):
@ -141,7 +178,7 @@ class BundleImport(GenericAPIView):
def put(self, request, *args, **kwargs):
tar_io = io.BytesIO(request.read())
agendas = []
components = {}
with tarfile.open(fileobj=tar_io) as tar:
manifest = json.loads(tar.extractfile('manifest.json').read().decode())
self.application = Application.update_or_create_from_manifest(
@ -151,34 +188,44 @@ class BundleImport(GenericAPIView):
)
for element in manifest.get('elements'):
if element.get('type') != 'agendas':
component_type = element['type']
if component_type not in klasses or element['type'] == 'roles':
continue
agendas.append(
json.loads(tar.extractfile(f'agendas/{element["slug"]}').read().decode()).get('data')
component_type = klasses_translation.get(component_type, component_type)
if component_type not in components:
components[component_type] = []
component_content = (
tar.extractfile('%s/%s' % (element['type'], element['slug'])).read().decode()
)
components[component_type].append(json.loads(component_content).get('data'))
# init cache of application elements, from manifest
self.application_elements = set()
# import agendas
self.do_something(agendas)
self.do_something(components)
# create application elements
self.link_objects(agendas)
self.link_objects(components)
# remove obsolete application elements
self.unlink_obsolete_objects()
return Response({'err': 0})
def do_something(self, agendas):
if agendas:
import_site({'agendas': agendas})
def do_something(self, components):
if components:
import_site(components)
def link_objects(self, agendas):
for agenda in agendas:
try:
existing_agenda = Agenda.objects.get(slug=agenda['slug'])
except Agenda.DoesNotExist:
pass
else:
element = ApplicationElement.update_or_create_for_object(self.application, existing_agenda)
self.application_elements.add(element.content_object)
def link_objects(self, components):
for component_type, component_list in components.items():
component_type = klasses_translation_reverse.get(component_type, component_type)
klass = klasses[component_type]
for component in component_list:
try:
existing_component = klass.objects.get(slug=component['slug'])
except klass.DoesNotExist:
pass
else:
element = ApplicationElement.update_or_create_for_object(
self.application, existing_component
)
self.application_elements.add(element.content_object)
def unlink_obsolete_objects(self):
known_elements = ApplicationElement.objects.filter(application=self.application)

View File

@ -20,24 +20,28 @@ from . import api_views
urlpatterns = [
path('export-import/', api_views.index, name='api-export-import'),
path('export-import/agendas/', api_views.list_agendas, name='api-export-import-agendas-list'),
path(
'export-import/agendas/<slug:slug>/',
api_views.export_agenda,
name='api-export-import-agenda-export',
),
path(
'export-import/agendas/<slug:slug>/dependencies/',
api_views.agenda_dependencies,
name='api-export-import-agenda-dependencies',
),
path(
'export-import/agendas/<slug:slug>/redirect/',
api_views.agenda_redirect,
name='api-export-import-agenda-redirect',
),
path('export-import/bundle-check/', api_views.bundle_check),
path('export-import/bundle-declare/', api_views.bundle_declare),
path('export-import/bundle-import/', api_views.bundle_import),
path('export-import/unlink/', api_views.bundle_unlink),
path(
'export-import/<slug:component_type>/',
api_views.list_components,
name='api-export-import-components-list',
),
path(
'export-import/<slug:component_type>/<slug:slug>/',
api_views.export_component,
name='api-export-import-component-export',
),
path(
'export-import/<slug:component_type>/<slug:slug>/dependencies/',
api_views.component_dependencies,
name='api-export-import-component-dependencies',
),
path(
'export-import/<slug:component_type>/<slug:slug>/redirect/',
api_views.component_redirect,
name='api-export-import-component-redirect',
),
]

View File

@ -106,6 +106,10 @@ 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))
# sort agendas to import virtual agendas first
if data.get('agendas'):
data['agendas'] = sorted(data['agendas'], key=lambda a: a['kind'] == 'virtual')
with transaction.atomic():
for cls, key in (
(Category, 'categories'),

View File

@ -7,7 +7,7 @@ import pytest
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from chrono.agendas.models import Agenda
from chrono.agendas.models import Agenda, Category, Desk, EventsType, Resource, UnavailabilityCalendar
from chrono.apps.export_import.models import Application, ApplicationElement
pytestmark = pytest.mark.django_db
@ -23,14 +23,41 @@ def test_object_types(app, user):
'text': 'Agendas',
'singular': 'Agenda',
'urls': {'list': 'http://testserver/api/export-import/agendas/'},
}
},
{
'id': 'agendas_categories',
'minor': True,
'singular': 'Category (agendas)',
'text': 'Categories (agendas)',
'urls': {'list': 'http://testserver/api/export-import/agendas_categories/'},
},
{
'id': 'events_types',
'minor': True,
'singular': 'Type dévènements',
'text': 'Types dévènements',
'urls': {'list': 'http://testserver/api/export-import/events_types/'},
},
{
'id': 'resources',
'minor': True,
'singular': 'Ressource',
'text': 'Ressources',
'urls': {'list': 'http://testserver/api/export-import/resources/'},
},
{
'id': 'unavailability_calendars',
'minor': True,
'singular': 'Unavailability calendar',
'text': 'Calendrier dindisponibilités',
'urls': {'list': 'http://testserver/api/export-import/unavailability_calendars/'},
},
]
}
def test_list_agendas(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
Agenda.objects.all().delete()
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
Agenda.objects.create(label='Event', slug='event', kind='events')
resp = app.get('/api/export-import/agendas/')
@ -64,18 +91,100 @@ def test_export_agenda(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
group1 = Group.objects.create(name='group1')
group2 = Group.objects.create(name='group2')
Agenda.objects.all().delete()
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings', edit_role=group1, view_role=group2)
resp = app.get('/api/export-import/agendas/rdv/')
assert resp.json['data']['label'] == 'Rdv'
assert resp.json['data']['permissions'] == {'view': 'group2', 'edit': 'group1'}
def test_export_minor_components(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
Category.objects.create(slug='cat', label='Category')
Resource.objects.create(slug='foo', label='Foo')
EventsType.objects.create(slug='foo', label='Foo')
UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
resp = app.get('/api/export-import/agendas_categories/cat/')
assert resp.json['data']['label'] == 'Category'
resp = app.get('/api/export-import/resources/foo/')
assert resp.json['data']['label'] == 'Foo'
resp = app.get('/api/export-import/events_types/foo/')
assert resp.json['data']['label'] == 'Foo'
resp = app.get('/api/export-import/unavailability_calendars/foo/')
assert resp.json['data']['label'] == 'Foo'
def test_agenda_dependencies_category(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
category = Category.objects.create(slug='cat', label='Category')
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings', category=category)
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
assert resp.json == {
'data': [
{
'id': 'cat',
'text': 'Category',
'type': 'agendas_categories',
'urls': {
'dependencies': 'http://testserver/api/export-import/agendas_categories/cat/dependencies/',
'export': 'http://testserver/api/export-import/agendas_categories/cat/',
'redirect': 'http://testserver/api/export-import/agendas_categories/cat/redirect/',
},
}
],
'err': 0,
}
def test_agenda_dependencies_resources(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
meetings_agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
meetings_agenda.resources.add(Resource.objects.create(slug='foo', label='Foo'))
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
assert resp.json == {
'data': [
{
'id': 'foo',
'text': 'Foo',
'type': 'resources',
'urls': {
'dependencies': 'http://testserver/api/export-import/resources/foo/dependencies/',
'export': 'http://testserver/api/export-import/resources/foo/',
'redirect': 'http://testserver/api/export-import/resources/foo/redirect/',
},
}
],
'err': 0,
}
def test_agenda_dependencies_unavailability_calendars(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
meetings_agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
desk = Desk.objects.create(slug='foo', label='Foo', agenda=meetings_agenda)
desk.unavailability_calendars.add(UnavailabilityCalendar.objects.create(slug='foo', label='Foo'))
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
assert resp.json == {
'data': [
{
'id': 'foo',
'text': 'Foo',
'type': 'unavailability_calendars',
'urls': {
'dependencies': 'http://testserver/api/export-import/unavailability_calendars/foo/dependencies/',
'export': 'http://testserver/api/export-import/unavailability_calendars/foo/',
'redirect': 'http://testserver/api/export-import/unavailability_calendars/foo/redirect/',
},
}
],
'err': 0,
}
def test_agenda_dependencies_groups(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
group1 = Group.objects.create(name='group1')
group2 = Group.objects.create(name='group2')
Agenda.objects.all().delete()
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings', edit_role=group1, view_role=group2)
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
# note: with hobo.agent.common installed, 'groups' will contain group slugs,
@ -91,7 +200,6 @@ def test_agenda_dependencies_groups(app, user):
def test_agenda_dependencies_virtual_agendas(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
Agenda.objects.all().delete()
rdv1 = Agenda.objects.create(label='Rdv1', slug='rdv1', kind='meetings')
rdv2 = Agenda.objects.create(label='Rdv2', slug='rdv2', kind='meetings')
virt = Agenda.objects.create(label='Virt', slug='virt', kind='virtual')
@ -125,6 +233,28 @@ def test_agenda_dependencies_virtual_agendas(app, user):
}
def test_agenda_dependencies_events_type(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
events_type = EventsType.objects.create(slug='foo', label='Foo')
Agenda.objects.create(label='Evt', slug='evt', kind='events', events_type=events_type)
resp = app.get('/api/export-import/agendas/evt/dependencies/')
assert resp.json == {
'data': [
{
'id': 'foo',
'text': 'Foo',
'type': 'events_types',
'urls': {
'dependencies': 'http://testserver/api/export-import/events_types/foo/dependencies/',
'export': 'http://testserver/api/export-import/events_types/foo/',
'redirect': 'http://testserver/api/export-import/events_types/foo/redirect/',
},
}
],
'err': 0,
}
def test_agenda_redirect(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
@ -135,11 +265,39 @@ def test_agenda_redirect(app, user):
def create_bundle(app, user, visible=True, version_number='42.0'):
app.authorization = ('Basic', ('john.doe', 'password'))
group, _ = Group.objects.get_or_create(name='plop')
Agenda.objects.get_or_create(
slug='rdv', defaults={'label': 'Rdv', 'kind': 'meetings', 'edit_role': group}
category, _ = Category.objects.get_or_create(slug='foo', label='Foo')
meetings_agenda, _ = Agenda.objects.get_or_create(
slug='rdv', label='Rdv', kind='meetings', edit_role=group, category=category
)
agenda_export = app.get('/api/export-import/agendas/rdv/').content
resource, _ = Resource.objects.get_or_create(slug='foo', label='Foo')
meetings_agenda.resources.add(resource)
desk, _ = Desk.objects.get_or_create(slug='foo', label='Foo', agenda=meetings_agenda)
unavailability_calendar, _ = UnavailabilityCalendar.objects.get_or_create(slug='foo', label='Foo')
desk.unavailability_calendars.add(unavailability_calendar)
events_type, _ = EventsType.objects.get_or_create(slug='foo', label='Foo')
events_agenda, _ = Agenda.objects.get_or_create(
label='Evt', slug='evt', kind='events', events_type=events_type
)
Desk.objects.get_or_create(agenda=events_agenda, slug='_exceptions_holder')
sub_agenda, _ = Agenda.objects.get_or_create(slug='sub', label='Sub', kind='meetings')
virtual_agenda, _ = Agenda.objects.get_or_create(label='Virt', slug='virt', kind='virtual')
virtual_agenda.real_agendas.add(sub_agenda)
components = [
(meetings_agenda, False),
(category, True),
(resource, True),
(unavailability_calendar, True),
(events_agenda, False),
(events_type, True),
(virtual_agenda, False),
(sub_agenda, True),
]
tar_io = io.BytesIO()
with tarfile.open(mode='w', fileobj=tar_io) as tar:
@ -152,11 +310,17 @@ def create_bundle(app, user, visible=True, version_number='42.0'):
'visible': visible,
'version_number': version_number,
'version_notes': 'foo bar blah',
'elements': [
{'type': 'agendas', 'slug': 'rdv', 'name': 'Test agenda', 'auto-dependency': False},
{'type': 'form', 'slug': 'xxx', 'name': 'Xxx'},
],
'elements': [],
}
for component, auto_dependency in components:
manifest_json['elements'].append(
{
'type': component.application_component_type,
'slug': component.slug,
'name': component.label,
'auto-dependency': auto_dependency,
}
)
manifest_fd = io.BytesIO(json.dumps(manifest_json, indent=2).encode())
tarinfo = tarfile.TarInfo('manifest.json')
tarinfo.size = len(manifest_fd.getvalue())
@ -169,9 +333,13 @@ def create_bundle(app, user, visible=True, version_number='42.0'):
tarinfo.size = len(icon_fd.getvalue())
tar.addfile(tarinfo, fileobj=icon_fd)
tarinfo = tarfile.TarInfo('agendas/rdv')
tarinfo.size = len(agenda_export)
tar.addfile(tarinfo, fileobj=io.BytesIO(agenda_export))
for component, _ in components:
component_export = app.get(
'/api/export-import/%s/%s/' % (component.application_component_type, component.slug)
).content
tarinfo = tarfile.TarInfo('%s/%s' % (component.application_component_type, component.slug))
tarinfo.size = len(component_export)
tar.addfile(tarinfo, fileobj=io.BytesIO(component_export))
bundle = tar_io.getvalue()
return bundle
@ -184,13 +352,18 @@ def bundle(app, user):
def test_bundle_import(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
Agenda.objects.all().delete()
bundles = []
for version_number in ['42.0', '42.1']:
bundles.append(create_bundle(app, user, version_number=version_number))
Agenda.objects.all().delete()
Category.objects.all().delete()
Resource.objects.all().delete()
EventsType.objects.all().delete()
UnavailabilityCalendar.objects.all().delete()
resp = app.put('/api/export-import/bundle-import/', bundles[0])
assert Agenda.objects.all().count() == 1
assert Agenda.objects.all().count() == 4
assert resp.json['err'] == 0
assert Application.objects.count() == 1
application = Application.objects.latest('pk')
@ -203,7 +376,7 @@ def test_bundle_import(app, user):
assert re.match(r'applications/icons/foo(_\w+)?.png', application.icon.name)
assert application.editable is False
assert application.visible is True
assert ApplicationElement.objects.count() == 1
assert ApplicationElement.objects.count() == 8
# check editable flag is kept on install
application.editable = True
@ -219,12 +392,12 @@ def test_bundle_import(app, user):
# check update
resp = app.put('/api/export-import/bundle-import/', bundles[1])
assert Agenda.objects.all().count() == 1
assert Agenda.objects.all().count() == 4
assert resp.json['err'] == 0
assert Application.objects.count() == 1
application = Application.objects.latest('pk')
assert application.editable is False
assert ApplicationElement.objects.count() == 1
assert ApplicationElement.objects.count() == 8
assert (
ApplicationElement.objects.filter(
application=application,
@ -240,7 +413,7 @@ def test_bundle_declare(app, user):
bundle = create_bundle(app, user, visible=False)
resp = app.put('/api/export-import/bundle-declare/', bundle)
assert Agenda.objects.all().count() == 1
assert Agenda.objects.all().count() == 4
assert resp.json['err'] == 0
assert Application.objects.count() == 1
application = Application.objects.latest('pk')
@ -253,7 +426,7 @@ def test_bundle_declare(app, user):
assert re.match(r'applications/icons/foo(_\w+)?.png', application.icon.name)
assert application.editable is True
assert application.visible is False
assert ApplicationElement.objects.count() == 1
assert ApplicationElement.objects.count() == 8
bundle = create_bundle(app, user, visible=True)
# create link to element not present in manifest: it should be unlinked
@ -263,14 +436,14 @@ def test_bundle_declare(app, user):
content_type=ContentType.objects.get_for_model(Agenda),
object_id=last_page.pk + 1,
)
# and remove a page to have an unkown reference in manifest
# and remove agendas to have unkown references in manifest
Agenda.objects.all().delete()
resp = app.put('/api/export-import/bundle-declare/', bundle)
assert Application.objects.count() == 1
application = Application.objects.latest('pk')
assert application.visible is True
assert ApplicationElement.objects.count() == 0
assert ApplicationElement.objects.count() == 4 # category, events_type, unavailability_calendar, resource
def test_bundle_unlink(app, user, bundle):