applification: implémenter le endpoint bundle-check (#87653) #218

Merged
lguerin merged 4 commits from wip/87653-app-bundle-check into main 2024-03-21 13:36:29 +01:00
4 changed files with 624 additions and 3 deletions

View File

@ -19,6 +19,7 @@ import json
import tarfile
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
@ -41,6 +42,14 @@ klasses_translation = {
}
klasses_translation_reverse = {v: k for k, v in klasses_translation.items()}
compare_urls = {
'agendas': 'chrono-manager-agenda-history-compare',
'categories': 'chrono-manager-category-history-compare',
'events_types': 'chrono-manager-events-type-history-compare',
'resources': 'chrono-manager-resource-history-compare',
'unavailability_calendars': 'chrono-manager-unavailability-calendar-history-compare',
}
class Index(GenericAPIView):
permission_classes = (permissions.IsAdminUser,)
@ -183,6 +192,27 @@ component_dependencies = ComponentDependencies.as_view()
def component_redirect(request, component_type, slug):
klass = klasses[component_type]
component = get_object_or_404(klass, slug=slug)
if component_type not in klasses or component_type == 'roles':
raise Http404
if (
'compare' in request.GET
and request.GET.get('application')
and request.GET.get('version1')
and request.GET.get('version2')
):
component_type = klasses_translation.get(component_type, component_type)
return redirect(
'%s?version1=%s&version2=%s&application=%s'
% (
reverse(compare_urls[component_type], args=[component.pk]),
request.GET['version1'],
request.GET['version2'],
request.GET['application'],
)
)
if klass == Agenda:
return redirect(reverse('chrono-manager-agenda-view', kwargs={'pk': component.pk}))
if klass == Category:
@ -200,7 +230,108 @@ class BundleCheck(GenericAPIView):
permission_classes = (permissions.IsAdminUser,)
def put(self, request, *args, **kwargs):
return Response({'err': 0, 'data': {}})
tar_io = io.BytesIO(request.read())
with tarfile.open(fileobj=tar_io) as tar:
manifest = json.loads(tar.extractfile('manifest.json').read().decode())
application_slug = manifest.get('slug')
application_version = manifest.get('version_number')
if not application_slug or not application_version:
return Response({'data': {}})
differences = []
unknown_elements = []
no_history_elements = []
legacy_elements = []
content_types = ContentType.objects.get_for_models(
*[v for k, v in klasses.items() if k != 'roles']
)
for element in manifest.get('elements'):
component_type = element['type']
if component_type not in klasses or component_type == 'roles':
continue
klass = klasses[component_type]
component_type = klasses_translation.get(component_type, component_type)
try:
component = klass.objects.get(slug=element['slug'])
except klass.DoesNotExist:
unknown_elements.append(
{
'type': component_type,
'slug': element['slug'],
}
)
continue
elements_qs = ApplicationElement.objects.filter(
application__slug=application_slug,
content_type=content_types[klass],
object_id=component.pk,
)
if not elements_qs.exists():
# object exists, but not linked to the application
legacy_elements.append(
{
'type': component.application_component_type,
'slug': str(component.slug),
# information needed here, Relation objects may not exist yet in hobo
'text': component.label,
'url': reverse(
'api-export-import-component-redirect',
kwargs={
pmarillonnet marked this conversation as resolved Outdated

Niveau parcours applification, l’idée de ce component redirect c’est qu’on invite l’utilisateur à lier l’objet à une application existante ? Ou bien juste on redirige vers la page BO de l’objet sans intention particulière ?

Niveau parcours applification, l’idée de ce component redirect c’est qu’on invite l’utilisateur à lier l’objet à une application existante ? Ou bien juste on redirige vers la page BO de l’objet sans intention particulière ?

C'est pour, depuis hobo, aller visiter l'objet sur la brique qui va bien et vérifier que c'est bien le bon objet. Ce endpoint est appelé par hobo avant la mise à jour d'une application, l'objet peut exister dans la brique mais pas encore dans hobo, où on n'a pas encore les informations nécessaires pour avoir l'url redirect et l'afficher. Donc le endpoint le renvoie par facilité.

C'est pour, depuis hobo, aller visiter l'objet sur la brique qui va bien et vérifier que c'est bien le bon objet. Ce endpoint est appelé par hobo avant la mise à jour d'une application, l'objet peut exister dans la brique mais pas encore dans hobo, où on n'a pas encore les informations nécessaires pour avoir l'url redirect et l'afficher. Donc le endpoint le renvoie par facilité.

Ok, je comprends mieux, merci.

Ok, je comprends mieux, merci.
'slug': str(component.slug),
'component_type': component.application_component_type,
},
),
}
)
continue
snapshot_for_app = (
klass.get_snapshot_model()
.objects.filter(
instance=component,
application_slug=application_slug,
application_version=application_version,
)
.order_by('timestamp')
.last()
)
if not snapshot_for_app:
# no snapshot for this bundle
no_history_elements.append(
{
'type': element['type'],
'slug': element['slug'],
pmarillonnet marked this conversation as resolved Outdated

Ok, un peu surpris de la brévité des informations contenues dans ce no_history_elements, par exemple en comparaison des legacy_elements qui présentent un libellé humainement intelligible et une url pour l’objet. J’imagine que l’usage final n’est pas le même.

Ok, un peu surpris de la brévité des informations contenues dans ce `no_history_elements`, par exemple en comparaison des `legacy_elements` qui présentent un libellé humainement intelligible et une url pour l’objet. J’imagine que l’usage final n’est pas le même.

Comme dit plus haut, un legacy_element existe dans la brique mais pas encore dans hobo, donc on a besoin de renvoyer des informations permettant à hobo d'afficher un joli message et une url redirect.
Un no_history_element existe forcément dans hobo.

Comme dit plus haut, un legacy_element existe dans la brique mais pas encore dans hobo, donc on a besoin de renvoyer des informations permettant à hobo d'afficher un joli message et une url redirect. Un no_history_element existe forcément dans hobo.

Ok, capté, merci.

Ok, capté, merci.
}
)
continue
last_snapshot = (
klass.get_snapshot_model().objects.filter(instance=component).latest('timestamp')
)
if snapshot_for_app.pk != last_snapshot.pk:
differences.append(
{
'type': element['type'],
'slug': element['slug'],
'url': '%s?version1=%s&version2=%s'
% (
request.build_absolute_uri(
reverse(compare_urls[component_type], args=[component.pk])
),
pmarillonnet marked this conversation as resolved Outdated

Pas compris pourquoi on concatène des bouts d’urls au lieu de simplement taper un

request.build_absolute_uri(reverse(compare_urls[], ))
Pas compris pourquoi on concatène des bouts d’urls au lieu de simplement taper un ``` python request.build_absolute_uri(reverse(compare_urls[…], …)) ```

en effet, corrigé

en effet, corrigé
snapshot_for_app.pk,
last_snapshot.pk,
),
}
)
return Response(
{
'data': {
'differences': differences,
'unknown_elements': unknown_elements,
'no_history_elements': no_history_elements,
'legacy_elements': legacy_elements,
}
}
)
bundle_check = BundleCheck.as_view()
@ -223,7 +354,7 @@ class BundleImport(GenericAPIView):
for element in manifest.get('elements'):
component_type = element['type']
if component_type not in klasses or element['type'] == 'roles':
if component_type not in klasses or component_type == 'roles':
continue
component_type = klasses_translation.get(component_type, component_type)
if component_type not in components:
@ -260,6 +391,11 @@ class BundleImport(GenericAPIView):
self.application, existing_component
)
self.application_elements.add(element.content_object)
if self.install is True:
existing_component.take_snapshot(
comment=_('Application (%s)') % self.application,
application=self.application,
)
def unlink_obsolete_objects(self):
known_elements = ApplicationElement.objects.filter(application=self.application)

View File

@ -54,7 +54,38 @@ class InstanceWithSnapshotHistoryView(ListView):
class InstanceWithSnapshotHistoryCompareView(DetailView):
def get_snapshots_from_application(self):
version1 = self.request.GET.get('version1')
version2 = self.request.GET.get('version2')
if not version1 or not version2:
raise Http404
snapshot_for_app1 = (
self.model.get_snapshot_model()
.objects.filter(
instance=self.object,
application_slug=self.request.GET['application'],
application_version=self.request.GET['version1'],
)
.order_by('timestamp')
.last()
)
snapshot_for_app2 = (
self.model.get_snapshot_model()
.objects.filter(
instance=self.object,
application_slug=self.request.GET['application'],
application_version=self.request.GET['version2'],
)
.order_by('timestamp')
.last()
)
return snapshot_for_app1, snapshot_for_app2
def get_snapshots(self):
if 'application' in self.request.GET:
return self.get_snapshots_from_application()
id1 = self.request.GET.get('version1')
id2 = self.request.GET.get('version2')
if not id1 or not id2:

View File

@ -9,6 +9,7 @@ from django.contrib.contenttypes.models import ContentType
from chrono.agendas.models import Agenda, Category, Desk, EventsType, Resource, UnavailabilityCalendar
from chrono.apps.export_import.models import Application, ApplicationElement
from chrono.apps.snapshot.models import AgendaSnapshot
pytestmark = pytest.mark.django_db
@ -374,22 +375,57 @@ def test_redirect(app, user):
redirect_url = f'/api/export-import/agendas/{agenda.slug}/redirect/'
resp = app.get(redirect_url, status=302)
assert resp.location == f'/manage/agendas/{agenda.pk}/'
resp = app.get(redirect_url + '?compare', status=302)
assert resp.location == f'/manage/agendas/{agenda.pk}/'
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
assert (
resp.location
== f'/manage/agendas/{agenda.pk}/history/compare/?version1=bar&version2=bar&application=foo'
)
redirect_url = f'/api/export-import/agendas_categories/{category.slug}/redirect/'
resp = app.get(redirect_url, status=302)
assert resp.location == '/manage/categories/'
resp = app.get(redirect_url + '?compare', status=302)
assert resp.location == '/manage/categories/'
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
assert (
resp.location
== f'/manage/category/{category.pk}/history/compare/?version1=bar&version2=bar&application=foo'
)
redirect_url = f'/api/export-import/resources/{resource.slug}/redirect/'
resp = app.get(redirect_url, status=302)
assert resp.location == f'/manage/resource/{resource.pk}/'
resp = app.get(redirect_url + '?compare', status=302)
assert resp.location == f'/manage/resource/{resource.pk}/'
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
assert (
resp.location
== f'/manage/resource/{resource.pk}/history/compare/?version1=bar&version2=bar&application=foo'
)
redirect_url = f'/api/export-import/events_types/{events_type.slug}/redirect/'
resp = app.get(redirect_url, status=302)
assert resp.location == '/manage/events-types/'
resp = app.get(redirect_url + '?compare', status=302)
assert resp.location == '/manage/events-types/'
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
assert (
resp.location
== f'/manage/events-type/{events_type.pk}/history/compare/?version1=bar&version2=bar&application=foo'
)
redirect_url = f'/api/export-import/unavailability_calendars/{unavailability_calendar.slug}/redirect/'
resp = app.get(redirect_url, status=302)
assert resp.location == f'/manage/unavailability-calendar/{unavailability_calendar.pk}/'
resp = app.get(redirect_url + '?compare', status=302)
assert resp.location == f'/manage/unavailability-calendar/{unavailability_calendar.pk}/'
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
assert (
resp.location
== f'/manage/unavailability-calendar/{unavailability_calendar.pk}/history/compare/?version1=bar&version2=bar&application=foo'
)
def create_bundle(app, admin_user, visible=True, version_number='42.0'):
@ -506,6 +542,12 @@ def test_bundle_import(app, admin_user):
assert application.editable is False
assert application.visible is True
assert ApplicationElement.objects.count() == 8
for model in [Agenda, Category, EventsType, Resource, UnavailabilityCalendar]:
for instance in model.objects.all():
last_snapshot = model.get_snapshot_model().objects.filter(instance=instance).latest('pk')
assert last_snapshot.comment == 'Application (Test)'
assert last_snapshot.application_slug == 'test'
assert last_snapshot.application_version == '42.0'
# check editable flag is kept on install
application.editable = True
@ -535,6 +577,12 @@ def test_bundle_import(app, admin_user):
).exists()
is False
)
for model in [Agenda, Category, EventsType, Resource, UnavailabilityCalendar]:
for instance in model.objects.all():
last_snapshot = model.get_snapshot_model().objects.filter(instance=instance).latest('pk')
assert last_snapshot.comment == 'Application (Test)'
assert last_snapshot.application_slug == 'test'
assert last_snapshot.application_version == '42.1'
def test_bundle_declare(app, admin_user):
@ -632,4 +680,267 @@ def test_bundle_unlink(app, admin_user, bundle):
def test_bundle_check(app, admin_user):
app.authorization = ('Basic', ('admin', 'admin'))
assert app.put('/api/export-import/bundle-check/').json == {'err': 0, 'data': {}}
bundles = []
for version_number in ['42.0', '42.1']:
bundles.append(create_bundle(app, admin_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()
incomplete_bundles = []
for manifest_json in [{'slug': 'test'}, {'version_number': '1.0'}]:
tar_io = io.BytesIO()
with tarfile.open(mode='w', fileobj=tar_io) as tar:
manifest_fd = io.BytesIO(json.dumps(manifest_json, indent=2).encode())
tarinfo = tarfile.TarInfo('manifest.json')
tarinfo.size = len(manifest_fd.getvalue())
tar.addfile(tarinfo, fileobj=manifest_fd)
incomplete_bundles.append(tar_io.getvalue())
# incorrect bundles, missing information
resp = app.put('/api/export-import/bundle-check/', incomplete_bundles[0])
assert resp.json == {'data': {}}
resp = app.put('/api/export-import/bundle-check/', incomplete_bundles[1])
assert resp.json == {'data': {}}
# not yet imported
resp = app.put('/api/export-import/bundle-check/', bundles[0])
assert resp.json == {
'data': {
'differences': [],
'no_history_elements': [],
'unknown_elements': [
{'slug': 'rdv', 'type': 'agendas'},
{'slug': 'foo', 'type': 'categories'},
{'slug': 'foo', 'type': 'resources'},
{'slug': 'foo', 'type': 'unavailability_calendars'},
{'slug': 'evt', 'type': 'agendas'},
{'slug': 'foo', 'type': 'events_types'},
{'slug': 'virt', 'type': 'agendas'},
{'slug': 'sub', 'type': 'agendas'},
],
'legacy_elements': [],
}
}
# import bundle
resp = app.put('/api/export-import/bundle-import/', bundles[0])
assert Application.objects.count() == 1
assert ApplicationElement.objects.count() == 8
# remove application links
Application.objects.all().delete()
resp = app.put('/api/export-import/bundle-check/', bundles[0])
assert resp.json == {
'data': {
'differences': [],
'no_history_elements': [],
'unknown_elements': [],
'legacy_elements': [
{
'slug': 'rdv',
'text': 'Rdv',
'type': 'agendas',
'url': '/api/export-import/agendas/rdv/redirect/',
},
{
'slug': 'foo',
'text': 'Foo',
'type': 'agendas_categories',
'url': '/api/export-import/agendas_categories/foo/redirect/',
},
{
'slug': 'foo',
'text': 'Foo',
'type': 'resources',
'url': '/api/export-import/resources/foo/redirect/',
},
{
'slug': 'foo',
'text': 'Foo',
'type': 'unavailability_calendars',
'url': '/api/export-import/unavailability_calendars/foo/redirect/',
},
{
'slug': 'evt',
'text': 'Evt',
'type': 'agendas',
'url': '/api/export-import/agendas/evt/redirect/',
},
{
'slug': 'foo',
'text': 'Foo',
'type': 'events_types',
'url': '/api/export-import/events_types/foo/redirect/',
},
{
'slug': 'virt',
'text': 'Virt',
'type': 'agendas',
'url': '/api/export-import/agendas/virt/redirect/',
},
{
'slug': 'sub',
'text': 'Sub',
'type': 'agendas',
'url': '/api/export-import/agendas/sub/redirect/',
},
],
}
}
# import bundle again, recreate links
resp = app.put('/api/export-import/bundle-import/', bundles[0])
assert Application.objects.count() == 1
assert ApplicationElement.objects.count() == 8
# no changes since last import
resp = app.put('/api/export-import/bundle-check/', bundles[0])
assert resp.json == {
'data': {
'differences': [],
'unknown_elements': [],
'no_history_elements': [],
'legacy_elements': [],
}
}
# add local changes
snapshots = {}
for model in [Agenda, Category, EventsType, Resource, UnavailabilityCalendar]:
for instance in model.objects.all():
old_snapshot = model.get_snapshot_model().objects.filter(instance=instance).latest('pk')
instance.take_snapshot(comment='local changes')
new_snapshot = model.get_snapshot_model().objects.filter(instance=instance).latest('pk')
assert new_snapshot.pk > old_snapshot.pk
snapshots[f'{instance.application_component_type}:{instance.slug}'] = (
instance.pk,
old_snapshot.pk,
new_snapshot.pk,
)
# and check
resp = app.put('/api/export-import/bundle-check/', bundles[0])
assert resp.json == {
'data': {
'differences': [
{
'slug': 'rdv',
'type': 'agendas',
'url': 'http://testserver/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
% (
snapshots['agendas:rdv'][0],
snapshots['agendas:rdv'][1],
snapshots['agendas:rdv'][2],
),
},
{
'slug': 'foo',
'type': 'agendas_categories',
'url': 'http://testserver/manage/category/%s/history/compare/?version1=%s&version2=%s'
% (
snapshots['agendas_categories:foo'][0],
snapshots['agendas_categories:foo'][1],
snapshots['agendas_categories:foo'][2],
),
},
{
'slug': 'foo',
'type': 'resources',
'url': 'http://testserver/manage/resource/%s/history/compare/?version1=%s&version2=%s'
% (
snapshots['resources:foo'][0],
snapshots['resources:foo'][1],
snapshots['resources:foo'][2],
),
},
{
'slug': 'foo',
'type': 'unavailability_calendars',
'url': 'http://testserver/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s'
% (
snapshots['unavailability_calendars:foo'][0],
snapshots['unavailability_calendars:foo'][1],
snapshots['unavailability_calendars:foo'][2],
),
},
{
'slug': 'evt',
'type': 'agendas',
'url': 'http://testserver/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
% (
snapshots['agendas:evt'][0],
snapshots['agendas:evt'][1],
snapshots['agendas:evt'][2],
),
},
{
'slug': 'foo',
'type': 'events_types',
'url': 'http://testserver/manage/events-type/%s/history/compare/?version1=%s&version2=%s'
% (
snapshots['events_types:foo'][0],
snapshots['events_types:foo'][1],
snapshots['events_types:foo'][2],
),
},
{
'slug': 'virt',
'type': 'agendas',
'url': 'http://testserver/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
% (
snapshots['agendas:virt'][0],
snapshots['agendas:virt'][1],
snapshots['agendas:virt'][2],
),
},
{
'slug': 'sub',
'type': 'agendas',
'url': 'http://testserver/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
% (
snapshots['agendas:sub'][0],
snapshots['agendas:sub'][1],
snapshots['agendas:sub'][2],
),
},
],
'unknown_elements': [],
'no_history_elements': [],
'legacy_elements': [],
}
}
# update bundle
resp = app.put('/api/export-import/bundle-import/', bundles[1])
# and check
resp = app.put('/api/export-import/bundle-check/', bundles[1])
assert resp.json == {
'data': {
'differences': [],
'unknown_elements': [],
'no_history_elements': [],
'legacy_elements': [],
}
}
# snapshots without application info
AgendaSnapshot.objects.update(application_slug=None, application_version=None)
resp = app.put('/api/export-import/bundle-check/', bundles[1])
assert resp.json == {
'data': {
'differences': [],
'unknown_elements': [],
'no_history_elements': [
{'slug': 'rdv', 'type': 'agendas'},
{'slug': 'evt', 'type': 'agendas'},
{'slug': 'virt', 'type': 'agendas'},
{'slug': 'sub', 'type': 'agendas'},
],
'legacy_elements': [],
}
}

View File

@ -28,6 +28,7 @@ def test_agenda_history(settings, app, admin_user):
agenda.description = 'Foo Bar'
agenda.save()
snapshot2 = agenda.take_snapshot()
snapshot2.application_slug = 'foobar'
snapshot2.application_version = '42.0'
snapshot2.save()
assert AgendaSnapshot.objects.count() == 2
@ -68,6 +69,32 @@ def test_agenda_history(settings, app, admin_user):
assert resp.text.count('diff_add') == 16
assert resp.text.count('diff_chg') == 0
# check compare on application version number
snapshot1.application_slug = 'foobar'
snapshot1.application_version = '41.0'
snapshot1.save()
# application not found
resp = app.get(
'/manage/agendas/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0' % agenda.pk
)
assert resp.location == '/manage/agendas/%s/history/' % agenda.pk
# version1 not found
resp = app.get(
'/manage/agendas/%s/history/compare/?application=foobar&version1=40.0&version2=42.0' % agenda.pk
)
assert resp.location == '/manage/agendas/%s/history/' % agenda.pk
# version2 not found
resp = app.get(
'/manage/agendas/%s/history/compare/?application=foobar&version1=41.0&version2=43.0' % agenda.pk
)
assert resp.location == '/manage/agendas/%s/history/' % agenda.pk
# ok
resp = app.get(
'/manage/agendas/%s/history/compare/?application=foobar&version1=41.0&version2=42.0' % agenda.pk
)
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
def test_agenda_history_as_manager(app, manager_user):
agenda = Agenda.objects.create(slug='foo', label='Foo')
@ -101,6 +128,7 @@ def test_category_history(settings, app, admin_user):
category.label = 'Bar'
category.save()
snapshot2 = category.take_snapshot()
snapshot2.application_slug = 'foobar'
snapshot2.application_version = '42.0'
snapshot2.save()
assert CategorySnapshot.objects.count() == 2
@ -141,6 +169,32 @@ def test_category_history(settings, app, admin_user):
assert resp.text.count('diff_add') == 0
assert resp.text.count('diff_chg') == 2
# check compare on application version number
snapshot1.application_slug = 'foobar'
snapshot1.application_version = '41.0'
snapshot1.save()
# application not found
resp = app.get(
'/manage/category/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0' % category.pk
)
assert resp.location == '/manage/category/%s/history/' % category.pk
# version1 not found
resp = app.get(
'/manage/category/%s/history/compare/?application=foobar&version1=40.0&version2=42.0' % category.pk
)
assert resp.location == '/manage/category/%s/history/' % category.pk
# version2 not found
resp = app.get(
'/manage/category/%s/history/compare/?application=foobar&version1=41.0&version2=43.0' % category.pk
)
assert resp.location == '/manage/category/%s/history/' % category.pk
# ok
resp = app.get(
'/manage/category/%s/history/compare/?application=foobar&version1=41.0&version2=42.0' % category.pk
)
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
def test_events_type_history(settings, app, admin_user):
events_type = EventsType.objects.create(slug='foo', label='Foo')
@ -148,6 +202,7 @@ def test_events_type_history(settings, app, admin_user):
events_type.label = 'Bar'
events_type.save()
snapshot2 = events_type.take_snapshot()
snapshot2.application_slug = 'foobar'
snapshot2.application_version = '42.0'
snapshot2.save()
assert EventsTypeSnapshot.objects.count() == 2
@ -188,6 +243,36 @@ def test_events_type_history(settings, app, admin_user):
assert resp.text.count('diff_add') == 0
assert resp.text.count('diff_chg') == 2
# check compare on application version number
snapshot1.application_slug = 'foobar'
snapshot1.application_version = '41.0'
snapshot1.save()
# application not found
resp = app.get(
'/manage/events-type/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0'
% events_type.pk
)
assert resp.location == '/manage/events-type/%s/history/' % events_type.pk
# version1 not found
resp = app.get(
'/manage/events-type/%s/history/compare/?application=foobar&version1=40.0&version2=42.0'
% events_type.pk
)
assert resp.location == '/manage/events-type/%s/history/' % events_type.pk
# version2 not found
resp = app.get(
'/manage/events-type/%s/history/compare/?application=foobar&version1=41.0&version2=43.0'
% events_type.pk
)
assert resp.location == '/manage/events-type/%s/history/' % events_type.pk
# ok
resp = app.get(
'/manage/events-type/%s/history/compare/?application=foobar&version1=41.0&version2=42.0'
% events_type.pk
)
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
def test_resource_history(settings, app, admin_user):
resource = Resource.objects.create(slug='foo', label='Foo')
@ -195,6 +280,7 @@ def test_resource_history(settings, app, admin_user):
resource.label = 'Bar'
resource.save()
snapshot2 = resource.take_snapshot()
snapshot2.application_slug = 'foobar'
snapshot2.application_version = '42.0'
snapshot2.save()
assert ResourceSnapshot.objects.count() == 2
@ -235,6 +321,32 @@ def test_resource_history(settings, app, admin_user):
assert resp.text.count('diff_add') == 0
assert resp.text.count('diff_chg') == 2
# check compare on application version number
snapshot1.application_slug = 'foobar'
snapshot1.application_version = '41.0'
snapshot1.save()
# application not found
resp = app.get(
'/manage/resource/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0' % resource.pk
)
assert resp.location == '/manage/resource/%s/history/' % resource.pk
# version1 not found
resp = app.get(
'/manage/resource/%s/history/compare/?application=foobar&version1=40.0&version2=42.0' % resource.pk
)
assert resp.location == '/manage/resource/%s/history/' % resource.pk
# version2 not found
resp = app.get(
'/manage/resource/%s/history/compare/?application=foobar&version1=41.0&version2=43.0' % resource.pk
)
assert resp.location == '/manage/resource/%s/history/' % resource.pk
# ok
resp = app.get(
'/manage/resource/%s/history/compare/?application=foobar&version1=41.0&version2=42.0' % resource.pk
)
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
def test_unavailability_calendar_history(settings, app, admin_user):
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
@ -242,6 +354,7 @@ def test_unavailability_calendar_history(settings, app, admin_user):
unavailability_calendar.label = 'Bar'
unavailability_calendar.save()
snapshot2 = unavailability_calendar.take_snapshot()
snapshot2.application_slug = 'foobar'
snapshot2.application_version = '42.0'
snapshot2.save()
assert UnavailabilityCalendarSnapshot.objects.count() == 2
@ -282,6 +395,36 @@ def test_unavailability_calendar_history(settings, app, admin_user):
assert resp.text.count('diff_add') == 0
assert resp.text.count('diff_chg') == 2
# check compare on application version number
snapshot1.application_slug = 'foobar'
snapshot1.application_version = '41.0'
snapshot1.save()
# application not found
resp = app.get(
'/manage/unavailability-calendar/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0'
% unavailability_calendar.pk
)
assert resp.location == '/manage/unavailability-calendar/%s/history/' % unavailability_calendar.pk
# version1 not found
resp = app.get(
'/manage/unavailability-calendar/%s/history/compare/?application=foobar&version1=40.0&version2=42.0'
% unavailability_calendar.pk
)
assert resp.location == '/manage/unavailability-calendar/%s/history/' % unavailability_calendar.pk
# version2 not found
resp = app.get(
'/manage/unavailability-calendar/%s/history/compare/?application=foobar&version1=41.0&version2=43.0'
% unavailability_calendar.pk
)
assert resp.location == '/manage/unavailability-calendar/%s/history/' % unavailability_calendar.pk
# ok
resp = app.get(
'/manage/unavailability-calendar/%s/history/compare/?application=foobar&version1=41.0&version2=42.0'
% unavailability_calendar.pk
)
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
def test_unavailability_calendar_history_as_manager(app, manager_user):
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')