diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index 68dff38a..4024dc14 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -585,7 +585,8 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.M
slug = data.pop('slug')
qs_kwargs = {}
if snapshot:
- qs_kwargs = {'snapshot': snapshot}
+ qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
+ data['slug'] = str(uuid.uuid4()) # random slug
else:
qs_kwargs = {'slug': slug}
agenda, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
@@ -3018,7 +3019,8 @@ class EventsType(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, mode
slug = data.pop('slug')
qs_kwargs = {}
if snapshot:
- qs_kwargs = {'snapshot': snapshot}
+ qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
+ data['slug'] = str(uuid.uuid4()) # random slug
else:
qs_kwargs = {'slug': slug}
events_type, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
@@ -3648,7 +3650,8 @@ class Resource(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models
slug = data.pop('slug')
qs_kwargs = {}
if snapshot:
- qs_kwargs = {'snapshot': snapshot}
+ qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
+ data['slug'] = str(uuid.uuid4()) # random slug
else:
qs_kwargs = {'slug': slug}
resource, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
@@ -3708,7 +3711,8 @@ class Category(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models
slug = data.pop('slug')
qs_kwargs = {}
if snapshot:
- qs_kwargs = {'snapshot': snapshot}
+ qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
+ data['slug'] = str(uuid.uuid4()) # random slug
else:
qs_kwargs = {'slug': slug}
category, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
@@ -4106,7 +4110,8 @@ class UnavailabilityCalendar(WithSnapshotMixin, WithApplicationMixin, WithInspec
slug = data.pop('slug')
qs_kwargs = {}
if snapshot:
- qs_kwargs = {'snapshot': snapshot}
+ qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
+ data['slug'] = str(uuid.uuid4()) # random slug
else:
qs_kwargs = {'slug': slug}
unavailability_calendar, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
diff --git a/chrono/apps/snapshot/models.py b/chrono/apps/snapshot/models.py
index 7254a3a0..6f78b811 100644
--- a/chrono/apps/snapshot/models.py
+++ b/chrono/apps/snapshot/models.py
@@ -79,9 +79,11 @@ class AbstractSnapshot(models.Model):
def get_instance(self):
try:
# try reusing existing instance
- return self.get_instance_model().snapshots.get(snapshot=self)
+ instance = self.get_instance_model().snapshots.get(snapshot=self)
except self.get_instance_model().DoesNotExist:
- return self.load_instance(self.serialization, snapshot=self)
+ instance = self.load_instance(self.serialization, snapshot=self)
+ instance.slug = self.serialization['slug'] # restore slug
+ return instance
def load_instance(self, json_instance, snapshot=None):
return self.get_instance_model().import_json(json_instance, snapshot=snapshot)[1]
diff --git a/chrono/apps/snapshot/views.py b/chrono/apps/snapshot/views.py
index 7a8d84ff..78c49b0c 100644
--- a/chrono/apps/snapshot/views.py
+++ b/chrono/apps/snapshot/views.py
@@ -16,13 +16,17 @@
import difflib
import json
+import re
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
+from django.template import loader
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
+from lxml.html.diff import htmldiff
+from pyquery import PyQuery as pq
from chrono.utils.timezone import localtime
@@ -65,7 +69,7 @@ class InstanceWithSnapshotHistoryCompareView(DetailView):
kwargs[self.instance_context_key] = self.object
mode = self.request.GET.get('mode') or 'json'
- if mode not in ['json']:
+ if mode not in ['json', 'inspect']:
raise Http404
snapshot1, snapshot2 = self.get_snapshots()
@@ -90,6 +94,65 @@ class InstanceWithSnapshotHistoryCompareView(DetailView):
return context
return self.render_to_response(context)
+ def get_compare_inspect_context(self, snapshot1, snapshot2):
+ instance1 = snapshot1.get_instance()
+ instance2 = snapshot2.get_instance()
+
+ def get_context(instance):
+ return {
+ 'object': instance,
+ }
+
+ def fix_result(panel_diff):
+ if not panel_diff:
+ return panel_diff
+ panel = pq(panel_diff)
+ # remove "Link" added by htmldiff
+ for link in panel.find('a'):
+ d = pq(link)
+ text = d.html()
+ new_text = re.sub(r' Link: .*$', '', text)
+ d.html(new_text)
+ # remove empty ins and del tags
+ for elem in panel.find('ins, del'):
+ d = pq(elem)
+ if not (d.html() or '').strip():
+ d.remove()
+ # prevent auto-closing behaviour of pyquery .html() method
+ for elem in panel.find('span, ul, div'):
+ d = pq(elem)
+ if not d.html():
+ d.html(' ')
+ return panel.html()
+
+ inspect1 = loader.render_to_string(self.inspect_template_name, get_context(instance1), self.request)
+ d1 = pq(str(inspect1))
+ inspect2 = loader.render_to_string(self.inspect_template_name, get_context(instance2), self.request)
+ d2 = pq(str(inspect2))
+ panels_attrs = [tab.attrib for tab in d1('[role="tabpanel"]')]
+ panels1 = list(d1('[role="tabpanel"]'))
+ panels2 = list(d2('[role="tabpanel"]'))
+
+ # build tab list (merge version 1 and version2)
+ tabs1 = d1.find('[role="tab"]')
+ tabs2 = d2.find('[role="tab"]')
+ tabs_order = [t.get('id') for t in panels_attrs]
+ tabs = {}
+ for tab in tabs1 + tabs2:
+ tab_id = pq(tab).attr('aria-controls')
+ tabs[tab_id] = pq(tab).outer_html()
+ tabs = [tabs[k] for k in tabs_order if k in tabs]
+
+ # build diff of each panel
+ panels_diff = list(map(htmldiff, panels1, panels2))
+ panels_diff = [fix_result(t) for t in panels_diff]
+
+ return {
+ 'tabs': tabs,
+ 'panels': zip(panels_attrs, panels_diff),
+ 'tab_class_names': d1('.pk-tabs').attr('class'),
+ }
+
def get_compare_json_context(self, snapshot1, snapshot2):
s1 = json.dumps(snapshot1.serialization, sort_keys=True, indent=2)
s2 = json.dumps(snapshot2.serialization, sort_keys=True, indent=2)
diff --git a/chrono/manager/static/css/style.scss b/chrono/manager/static/css/style.scss
index 273736a2..9928db6e 100644
--- a/chrono/manager/static/css/style.scss
+++ b/chrono/manager/static/css/style.scss
@@ -966,6 +966,33 @@ p.snapshot-description {
margin: 0;
}
+div.diff {
+ margin: 1em 0;
+ h3 {
+ del, ins {
+ font-weight: bold;
+ background-color: transparent;
+ }
+ del {
+ color: #fbb6c2 !important;
+ }
+ ins {
+ color: #d4fcbc !important;
+ }
+ }
+}
+
+ins {
+ text-decoration: none;
+ background-color: #d4fcbc;
+}
+
+del {
+ text-decoration: line-through;
+ background-color: #fbb6c2;
+ color: #555;
+}
+
table.diff {
background: white;
border: 1px solid #f3f3f3;
diff --git a/chrono/manager/templates/chrono/manager_agenda_history_compare.html b/chrono/manager/templates/chrono/manager_agenda_history_compare.html
index f0982f2a..ec52ff7a 100644
--- a/chrono/manager/templates/chrono/manager_agenda_history_compare.html
+++ b/chrono/manager/templates/chrono/manager_agenda_history_compare.html
@@ -3,6 +3,10 @@
{% block appbar %}
{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})
+
+ {% trans "Compare inspect" %}
+ {% trans "Compare JSON" %}
+
{% endblock %}
{% block breadcrumb %}
diff --git a/chrono/manager/templates/chrono/manager_agenda_inspect.html b/chrono/manager/templates/chrono/manager_agenda_inspect.html
index 46419061..0cbd2e42 100644
--- a/chrono/manager/templates/chrono/manager_agenda_inspect.html
+++ b/chrono/manager/templates/chrono/manager_agenda_inspect.html
@@ -12,314 +12,7 @@
{% block content %}
-
-
-
-
-
- {% if object.kind == 'events' %}
-
-
- {% elif object.kind == 'meetings' %}
-
-
-
- {% elif object.kind == 'virtual' %}
-
-
- {% endif %}
-
-
-
-
-
-
-
-
- {% if object.kind != 'virtual' %}
-
{% trans "Display options" %}
-
- {% for label, value in object.get_display_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endif %}
-
- {% if object.kind == 'events' %}
-
{% trans "Booking check options" %}
-
- {% for label, value in object.get_booking_check_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endif %}
-
- {% if object.kind == 'events' %}
- {% if agenda.partial_bookings %}
-
{% trans "Invoicing options" %}
-
- {% for label, value in object.get_invoicing_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% else %}
-
{% trans "Management notifications" %}
-
- {% for label, value in object.get_notifications_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endif %}
- {% endif %}
-
- {% if object.kind != 'virtual' and not object.partial_bookings %}
-
{% trans "Booking reminders" %}
-
- {% for label, value in object.get_reminder_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endif %}
-
-
{% trans "Booking Delays" %}
-
- {% for label, value in object.get_booking_delays_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
-
-
-
-
-
-
- {% for label, value in object.get_permissions_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
-
-
-
- {% if object.kind == 'events' %}
-
-
-
- {% for event in object.event_set.all %}
-
{{ event }}
-
- {% for label, value in event.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endfor %}
-
-
-
-
-
- {% for desk in object.desk_set.all %}{% if desk.slug == '_exceptions_holder' %}
-
{% trans "Unavailability calendars" %}
-
- {% for unavailability_calendar in desk.unavailability_calendars.all %}
- -
- {{ unavailability_calendar }}
-
- {% endfor %}
-
-
-
{% trans "Exception sources" %}
- {% for source in desk.timeperiodexceptionsource_set.all %}
-
{{ source }}
-
- {% for label, value in source.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endfor %}
-
-
{% trans "Exceptions" %}
- {% for exception in desk.timeperiodexception_set.all %}
-
{{ exception }}
-
- {% for label, value in exception.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endfor %}
- {% endif %}{% endfor %}
-
-
-
- {% elif object.kind == 'meetings' %}
-
-
-
- {% for meeting_type in object.meetingtype_set.all %}
-
{{ meeting_type }}
-
- {% for label, value in meeting_type.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endfor %}
-
-
-
-
-
- {% for desk in object.desk_set.all %}
-
{{ desk }}
-
- {% for label, value in desk.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
-
-
{% trans "Opening hours" %}
- {% for time_period in desk.timeperiod_set.all %}
-
{{ time_period }}
-
- {% for label, value in time_period.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endfor %}
-
-
{% trans "Unavailability calendars" %}
-
- {% for unavailability_calendar in desk.unavailability_calendars.all %}
- -
- {{ unavailability_calendar }}
-
- {% endfor %}
-
-
-
{% trans "Exception sources" %}
- {% for source in desk.timeperiodexceptionsource_set.all %}
-
{{ source }}
-
- {% for label, value in source.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endfor %}
-
-
{% trans "Exceptions" %}
- {% for exception in desk.timeperiodexception_set.all %}
-
{{ exception }}
-
- {% for label, value in exception.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endfor %}
-
- {% endfor %}
-
-
-
-
-
-
- {% for resource in object.resources.all %}
- -
- {{ resource }}
-
- {% endfor %}
-
-
-
-
- {% elif object.kind == "virtual" %}
-
-
-
-
- {% for agenda in object.real_agendas.all %}
- -
- {{ agenda }}
-
- {% endfor %}
-
-
-
-
-
-
- {% for time_period in object.excluded_timeperiods.all %}
-
{{ time_period }}
-
- {% for label, value in time_period.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endfor %}
-
-
-
- {% endif %}
-
-
-
+ {% include 'chrono/manager_agenda_inspect_fragment.html' %}
{% endblock %}
{% block sidebar %}
diff --git a/chrono/manager/templates/chrono/manager_agenda_inspect_fragment.html b/chrono/manager/templates/chrono/manager_agenda_inspect_fragment.html
new file mode 100644
index 00000000..b734a997
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_agenda_inspect_fragment.html
@@ -0,0 +1,309 @@
+{% load i18n %}
+
+
+
+
+
+ {% if object.kind == 'events' %}
+
+
+ {% elif object.kind == 'meetings' %}
+
+
+
+ {% elif object.kind == 'virtual' %}
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+ {% if object.kind != 'virtual' %}
+
{% trans "Display options" %}
+
+ {% for label, value in object.get_display_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% if object.kind == 'events' %}
+
{% trans "Booking check options" %}
+
+ {% for label, value in object.get_booking_check_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% if object.kind == 'events' %}
+ {% if agenda.partial_bookings %}
+
{% trans "Invoicing options" %}
+
+ {% for label, value in object.get_invoicing_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% else %}
+
{% trans "Management notifications" %}
+
+ {% for label, value in object.get_notifications_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endif %}
+ {% endif %}
+
+ {% if object.kind != 'virtual' and not object.partial_bookings %}
+
{% trans "Booking reminders" %}
+
+ {% for label, value in object.get_reminder_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endif %}
+
+
{% trans "Booking Delays" %}
+
+ {% for label, value in object.get_booking_delays_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+
+
+
+
+
+
+ {% for label, value in object.get_permissions_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+
+
+
+ {% if object.kind == 'events' %}
+
+
+
+ {% for event in object.event_set.all %}
+
{{ event }}
+
+ {% for label, value in event.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+ {% for desk in object.desk_set.all %}{% if desk.slug == '_exceptions_holder' %}
+
{% trans "Unavailability calendars" %}
+
+ {% for unavailability_calendar in desk.unavailability_calendars.all %}
+ -
+ {{ unavailability_calendar }}
+
+ {% endfor %}
+
+
+
{% trans "Exception sources" %}
+ {% for source in desk.timeperiodexceptionsource_set.all %}
+
{{ source }}
+
+ {% for label, value in source.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
{% trans "Exceptions" %}
+ {% for exception in desk.timeperiodexception_set.all %}
+
{{ exception }}
+
+ {% for label, value in exception.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endfor %}
+ {% endif %}{% endfor %}
+
+
+
+ {% elif object.kind == 'meetings' %}
+
+
+
+ {% for meeting_type in object.meetingtype_set.all %}
+
{{ meeting_type }}
+
+ {% for label, value in meeting_type.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+ {% for desk in object.desk_set.all %}
+
{{ desk }}
+
+ {% for label, value in desk.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+
+
{% trans "Opening hours" %}
+ {% for time_period in desk.timeperiod_set.all %}
+
{{ time_period }}
+
+ {% for label, value in time_period.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
{% trans "Unavailability calendars" %}
+
+ {% for unavailability_calendar in desk.unavailability_calendars.all %}
+ -
+ {{ unavailability_calendar }}
+
+ {% endfor %}
+
+
+
{% trans "Exception sources" %}
+ {% for source in desk.timeperiodexceptionsource_set.all %}
+
{{ source }}
+
+ {% for label, value in source.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
{% trans "Exceptions" %}
+ {% for exception in desk.timeperiodexception_set.all %}
+
{{ exception }}
+
+ {% for label, value in exception.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+
+ {% for resource in object.resources.all %}
+ -
+ {{ resource }}
+
+ {% endfor %}
+
+
+
+
+ {% elif object.kind == "virtual" %}
+
+
+
+
+ {% for agenda in object.real_agendas.all %}
+ -
+ {{ agenda }}
+
+ {% endfor %}
+
+
+
+
+
+
+ {% for time_period in object.excluded_timeperiods.all %}
+
{{ time_period }}
+
+ {% for label, value in time_period.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+ {% endif %}
+
+
+
diff --git a/chrono/manager/templates/chrono/manager_category_history_compare.html b/chrono/manager/templates/chrono/manager_category_history_compare.html
index fd7027df..2fa68ce9 100644
--- a/chrono/manager/templates/chrono/manager_category_history_compare.html
+++ b/chrono/manager/templates/chrono/manager_category_history_compare.html
@@ -3,6 +3,10 @@
{% block appbar %}
{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})
+
+ {% trans "Compare inspect" %}
+ {% trans "Compare JSON" %}
+
{% endblock %}
{% block breadcrumb %}
diff --git a/chrono/manager/templates/chrono/manager_category_inspect.html b/chrono/manager/templates/chrono/manager_category_inspect.html
index da6584cd..8d5bade6 100644
--- a/chrono/manager/templates/chrono/manager_category_inspect.html
+++ b/chrono/manager/templates/chrono/manager_category_inspect.html
@@ -11,26 +11,7 @@
{% endblock %}
{% block content %}
-
-
-
-
-
-
+ {% include 'chrono/manager_category_inspect_fragment.html' %}
{% endblock %}
{% block sidebar %}
diff --git a/chrono/manager/templates/chrono/manager_category_inspect_fragment.html b/chrono/manager/templates/chrono/manager_category_inspect_fragment.html
new file mode 100644
index 00000000..869c12e2
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_category_inspect_fragment.html
@@ -0,0 +1,21 @@
+{% load i18n %}
+
+
+
+
+
+
diff --git a/chrono/manager/templates/chrono/manager_events_type_history_compare.html b/chrono/manager/templates/chrono/manager_events_type_history_compare.html
index 80065547..4f228377 100644
--- a/chrono/manager/templates/chrono/manager_events_type_history_compare.html
+++ b/chrono/manager/templates/chrono/manager_events_type_history_compare.html
@@ -3,6 +3,10 @@
{% block appbar %}
{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})
+
+ {% trans "Compare inspect" %}
+ {% trans "Compare JSON" %}
+
{% endblock %}
{% block breadcrumb %}
diff --git a/chrono/manager/templates/chrono/manager_events_type_inspect.html b/chrono/manager/templates/chrono/manager_events_type_inspect.html
index 90ce07f4..4970c41c 100644
--- a/chrono/manager/templates/chrono/manager_events_type_inspect.html
+++ b/chrono/manager/templates/chrono/manager_events_type_inspect.html
@@ -11,52 +11,7 @@
{% endblock %}
{% block content %}
-
-
-
-
-
-
-
-
-
-
-
- {% for value in object.get_custom_fields %}
-
{{ value.label }}
-
- -
- {% trans "Field slug:" %}
- {{ value.varname }}
-
- -
- {% trans "Field label:" %}
- {{ value.label }}
-
- -
- {% trans "Field type:" %}
- {% if value.field_type == 'text' %}{% trans "Text" %}{% endif %}
- {% if value.field_type == 'textarea' %}{% trans "Textarea" %}{% endif %}
- {% if value.field_type == 'textbool' %}{% trans "Boolean" %}{% endif %}
-
-
- {% endfor %}
-
-
-
-
-
+ {% include 'chrono/manager_events_type_inspect_fragment.html' %}
{% endblock %}
{% block sidebar %}
diff --git a/chrono/manager/templates/chrono/manager_events_type_inspect_fragment.html b/chrono/manager/templates/chrono/manager_events_type_inspect_fragment.html
new file mode 100644
index 00000000..2ad2facd
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_events_type_inspect_fragment.html
@@ -0,0 +1,47 @@
+{% load i18n %}
+
+
+
+
+
+
+
+
+
+
+
+ {% for value in object.get_custom_fields %}
+
{{ value.label }}
+
+ -
+ {% trans "Field slug:" %}
+ {{ value.varname }}
+
+ -
+ {% trans "Field label:" %}
+ {{ value.label }}
+
+ -
+ {% trans "Field type:" %}
+ {% if value.field_type == 'text' %}{% trans "Text" %}{% endif %}
+ {% if value.field_type == 'textarea' %}{% trans "Textarea" %}{% endif %}
+ {% if value.field_type == 'textbool' %}{% trans "Boolean" %}{% endif %}
+
+
+ {% endfor %}
+
+
+
+
+
diff --git a/chrono/manager/templates/chrono/manager_resource_history_compare.html b/chrono/manager/templates/chrono/manager_resource_history_compare.html
index db7b5433..4b4543e4 100644
--- a/chrono/manager/templates/chrono/manager_resource_history_compare.html
+++ b/chrono/manager/templates/chrono/manager_resource_history_compare.html
@@ -3,6 +3,10 @@
{% block appbar %}
{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})
+
+ {% trans "Compare inspect" %}
+ {% trans "Compare JSON" %}
+
{% endblock %}
{% block breadcrumb %}
diff --git a/chrono/manager/templates/chrono/manager_resource_inspect.html b/chrono/manager/templates/chrono/manager_resource_inspect.html
index 7e42ced0..009b814a 100644
--- a/chrono/manager/templates/chrono/manager_resource_inspect.html
+++ b/chrono/manager/templates/chrono/manager_resource_inspect.html
@@ -11,27 +11,7 @@
{% endblock %}
{% block content %}
-
-
-
-
-
-
+ {% include 'chrono/manager_resource_inspect_fragment.html' %}
{% endblock %}
{% block sidebar %}
diff --git a/chrono/manager/templates/chrono/manager_resource_inspect_fragment.html b/chrono/manager/templates/chrono/manager_resource_inspect_fragment.html
new file mode 100644
index 00000000..db6c45b2
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_resource_inspect_fragment.html
@@ -0,0 +1,22 @@
+{% load i18n %}
+
+
+
+
+
+
diff --git a/chrono/manager/templates/chrono/manager_unavailability_calendar_history_compare.html b/chrono/manager/templates/chrono/manager_unavailability_calendar_history_compare.html
index c9e0550f..d04a18c6 100644
--- a/chrono/manager/templates/chrono/manager_unavailability_calendar_history_compare.html
+++ b/chrono/manager/templates/chrono/manager_unavailability_calendar_history_compare.html
@@ -3,6 +3,10 @@
{% block appbar %}
{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})
+
+ {% trans "Compare inspect" %}
+ {% trans "Compare JSON" %}
+
{% endblock %}
{% block breadcrumb %}
diff --git a/chrono/manager/templates/chrono/manager_unavailability_calendar_inspect.html b/chrono/manager/templates/chrono/manager_unavailability_calendar_inspect.html
index 9f02414d..ba54e363 100644
--- a/chrono/manager/templates/chrono/manager_unavailability_calendar_inspect.html
+++ b/chrono/manager/templates/chrono/manager_unavailability_calendar_inspect.html
@@ -11,58 +11,7 @@
{% endblock %}
{% block content %}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% for label, value in object.get_permissions_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
-
-
-
-
-
- {% for exception in object.timeperiodexception_set.all %}
-
{{ exception }}
-
- {% for label, value in exception.get_inspect_fields %}
- -
- {% blocktrans %}{{ label }}:{% endblocktrans %}
- {{ value }}
-
- {% endfor %}
-
- {% endfor %}
-
-
-
-
-
+ {% include 'chrono/manager_unavailability_calendar_inspect_fragment.html' %}
{% endblock %}
{% block sidebar %}
diff --git a/chrono/manager/templates/chrono/manager_unavailability_calendar_inspect_fragment.html b/chrono/manager/templates/chrono/manager_unavailability_calendar_inspect_fragment.html
new file mode 100644
index 00000000..47c6d46f
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_unavailability_calendar_inspect_fragment.html
@@ -0,0 +1,53 @@
+{% load i18n %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% for label, value in object.get_permissions_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+
+
+
+
+
+ {% for exception in object.timeperiodexception_set.all %}
+
{{ exception }}
+
+ {% for label, value in exception.get_inspect_fields %}
+ -
+ {% blocktrans %}{{ label }}:{% endblocktrans %}
+ {{ value }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index f8f9621f..98ddc745 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -829,6 +829,7 @@ resource_history = ResourceHistoryView.as_view()
class ResourceHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
template_name = 'chrono/manager_resource_history_compare.html'
+ inspect_template_name = 'chrono/manager_resource_inspect_fragment.html'
model = Resource
instance_context_key = 'resource'
history_view = 'chrono-manager-resource-history'
@@ -960,6 +961,7 @@ category_history = CategoryHistoryView.as_view()
class CategoryHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
template_name = 'chrono/manager_category_history_compare.html'
+ inspect_template_name = 'chrono/manager_category_inspect_fragment.html'
model = Category
instance_context_key = 'category'
history_view = 'chrono-manager-category-history'
@@ -1148,6 +1150,7 @@ events_type_history = EventsTypeHistoryView.as_view()
class EventsTypeHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
template_name = 'chrono/manager_events_type_history_compare.html'
+ inspect_template_name = 'chrono/manager_events_type_inspect_fragment.html'
model = EventsType
instance_context_key = 'events_type'
history_view = 'chrono-manager-events-type-history'
@@ -4209,9 +4212,12 @@ class AgendaInspectView(ManagedAgendaMixin, DetailView):
'desk_set',
queryset=Desk.objects.prefetch_related(
'timeperiod_set',
- 'timeperiodexception_set',
'timeperiodexceptionsource_set',
'unavailability_calendars',
+ Prefetch(
+ 'timeperiodexception_set',
+ queryset=TimePeriodException.objects.filter(source__isnull=True),
+ ),
),
),
Prefetch('event_set', queryset=Event.objects.filter(primary_event__isnull=True)),
@@ -4237,6 +4243,7 @@ agenda_history = AgendaHistoryView.as_view()
class AgendaHistoryCompareView(ManagedAgendaMixin, InstanceWithSnapshotHistoryCompareView):
template_name = 'chrono/manager_agenda_history_compare.html'
+ inspect_template_name = 'chrono/manager_agenda_inspect_fragment.html'
model = Agenda
instance_context_key = 'agenda'
history_view = 'chrono-manager-agenda-history'
@@ -4893,6 +4900,7 @@ class UnavailabilityCalendarHistoryCompareView(
ManagedUnavailabilityCalendarMixin, InstanceWithSnapshotHistoryCompareView
):
template_name = 'chrono/manager_unavailability_calendar_history_compare.html'
+ inspect_template_name = 'chrono/manager_unavailability_calendar_inspect_fragment.html'
model = UnavailabilityCalendar
instance_context_key = 'unavailability_calendar'
history_view = 'chrono-manager-unavailability-calendar-history'
diff --git a/debian/control b/debian/control
index d5bbe97b..96e7cc34 100644
--- a/debian/control
+++ b/debian/control
@@ -14,6 +14,7 @@ Package: python3-chrono
Architecture: all
Depends: python3-django (>= 2:3.2),
python3-gadjo,
+ python3-lxml,
python3-publik-django-templatetags,
python3-requests,
python3-uwsgidecorators,
diff --git a/pylint.rc b/pylint.rc
index fe0e5bdd..147abf97 100644
--- a/pylint.rc
+++ b/pylint.rc
@@ -1,6 +1,7 @@
[MASTER]
persistent=yes
ignore=vendor,Bouncers,ezt.py
+extension-pkg-allow-list=lxml
[MESSAGES CONTROL]
disable=
diff --git a/setup.py b/setup.py
index 7d1d869a..bf9c22ea 100644
--- a/setup.py
+++ b/setup.py
@@ -169,6 +169,7 @@ setup(
'workalendar',
'weasyprint',
'sorl-thumbnail',
+ 'lxml',
],
zip_safe=False,
cmdclass={
diff --git a/tests/manager/test_event.py b/tests/manager/test_event.py
index 332159b3..68cdb80c 100644
--- a/tests/manager/test_event.py
+++ b/tests/manager/test_event.py
@@ -1026,7 +1026,7 @@ def test_import_events(app, admin_user):
)
with CaptureQueriesContext(connection) as ctx:
resp = resp.form.submit(status=302)
- assert len(ctx.captured_queries) == 32
+ assert len(ctx.captured_queries) == 31
assert Event.objects.count() == 5
assert set(Event.objects.values_list('slug', flat=True)) == {
'labelb',
diff --git a/tests/manager/test_snapshot.py b/tests/manager/test_snapshot.py
index b38fabfc..621c2fb5 100644
--- a/tests/manager/test_snapshot.py
+++ b/tests/manager/test_snapshot.py
@@ -44,15 +44,20 @@ def test_agenda_history(settings, app, admin_user):
]
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
- resp = app.get(
- '/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
- % (agenda.pk, snapshot1.pk, snapshot2.pk)
- )
- assert 'Snapshot (%s)' % (snapshot1.pk) in resp
- assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
- assert resp.text.count('diff_sub') == 1
- assert resp.text.count('diff_add') == 16
- assert resp.text.count('diff_chg') == 0
+ for mode in ['json', 'inspect', '']:
+ resp = app.get(
+ '/manage/agendas/%s/history/compare/?version1=%s&version2=%s&mode=%s'
+ % (agenda.pk, snapshot1.pk, snapshot2.pk, mode)
+ )
+ assert 'Snapshot (%s)' % (snapshot1.pk) in resp
+ assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
+ if mode == 'inspect':
+ assert resp.text.count('') == 6
+ assert resp.text.count('') == 0
+ else:
+ assert resp.text.count('diff_sub') == 1
+ assert resp.text.count('diff_add') == 16
+ assert resp.text.count('diff_chg') == 0
resp = app.get(
'/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
% (agenda.pk, snapshot2.pk, snapshot1.pk)
@@ -112,15 +117,20 @@ def test_category_history(settings, app, admin_user):
]
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
- resp = app.get(
- '/manage/category/%s/history/compare/?version1=%s&version2=%s'
- % (category.pk, snapshot1.pk, snapshot2.pk)
- )
- assert 'Snapshot (%s)' % (snapshot1.pk) in resp
- assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
- assert resp.text.count('diff_sub') == 0
- assert resp.text.count('diff_add') == 0
- assert resp.text.count('diff_chg') == 2
+ for mode in ['json', 'inspect', '']:
+ resp = app.get(
+ '/manage/category/%s/history/compare/?version1=%s&version2=%s&mode=%s'
+ % (category.pk, snapshot1.pk, snapshot2.pk, mode)
+ )
+ assert 'Snapshot (%s)' % (snapshot1.pk) in resp
+ assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
+ if mode == 'inspect':
+ assert resp.text.count('') == 1
+ assert resp.text.count('') == 1
+ else:
+ assert resp.text.count('diff_sub') == 0
+ assert resp.text.count('diff_add') == 0
+ assert resp.text.count('diff_chg') == 2
resp = app.get(
'/manage/category/%s/history/compare/?version1=%s&version2=%s'
% (category.pk, snapshot2.pk, snapshot1.pk)
@@ -154,15 +164,20 @@ def test_events_type_history(settings, app, admin_user):
]
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
- resp = app.get(
- '/manage/events-type/%s/history/compare/?version1=%s&version2=%s'
- % (events_type.pk, snapshot1.pk, snapshot2.pk)
- )
- assert 'Snapshot (%s)' % (snapshot1.pk) in resp
- assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
- assert resp.text.count('diff_sub') == 0
- assert resp.text.count('diff_add') == 0
- assert resp.text.count('diff_chg') == 2
+ for mode in ['json', 'inspect', '']:
+ resp = app.get(
+ '/manage/events-type/%s/history/compare/?version1=%s&version2=%s&mode=%s'
+ % (events_type.pk, snapshot1.pk, snapshot2.pk, mode)
+ )
+ assert 'Snapshot (%s)' % (snapshot1.pk) in resp
+ assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
+ if mode == 'inspect':
+ assert resp.text.count('') == 1
+ assert resp.text.count('') == 1
+ else:
+ assert resp.text.count('diff_sub') == 0
+ assert resp.text.count('diff_add') == 0
+ assert resp.text.count('diff_chg') == 2
resp = app.get(
'/manage/events-type/%s/history/compare/?version1=%s&version2=%s'
% (events_type.pk, snapshot2.pk, snapshot1.pk)
@@ -196,15 +211,20 @@ def test_resource_history(settings, app, admin_user):
]
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
- resp = app.get(
- '/manage/resource/%s/history/compare/?version1=%s&version2=%s'
- % (resource.pk, snapshot1.pk, snapshot2.pk)
- )
- assert 'Snapshot (%s)' % (snapshot1.pk) in resp
- assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
- assert resp.text.count('diff_sub') == 0
- assert resp.text.count('diff_add') == 0
- assert resp.text.count('diff_chg') == 2
+ for mode in ['json', 'inspect', '']:
+ resp = app.get(
+ '/manage/resource/%s/history/compare/?version1=%s&version2=%s&mode=%s'
+ % (resource.pk, snapshot1.pk, snapshot2.pk, mode)
+ )
+ assert 'Snapshot (%s)' % (snapshot1.pk) in resp
+ assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
+ if mode == 'inspect':
+ assert resp.text.count('') == 1
+ assert resp.text.count('') == 1
+ else:
+ assert resp.text.count('diff_sub') == 0
+ assert resp.text.count('diff_add') == 0
+ assert resp.text.count('diff_chg') == 2
resp = app.get(
'/manage/resource/%s/history/compare/?version1=%s&version2=%s'
% (resource.pk, snapshot2.pk, snapshot1.pk)
@@ -238,15 +258,20 @@ def test_unavailability_calendar_history(settings, app, admin_user):
]
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
- resp = app.get(
- '/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s'
- % (unavailability_calendar.pk, snapshot1.pk, snapshot2.pk)
- )
- assert 'Snapshot (%s)' % (snapshot1.pk) in resp
- assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
- assert resp.text.count('diff_sub') == 0
- assert resp.text.count('diff_add') == 0
- assert resp.text.count('diff_chg') == 2
+ for mode in ['json', 'inspect', '']:
+ resp = app.get(
+ '/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s&mode=%s'
+ % (unavailability_calendar.pk, snapshot1.pk, snapshot2.pk, mode)
+ )
+ assert 'Snapshot (%s)' % (snapshot1.pk) in resp
+ assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
+ if mode == 'inspect':
+ assert resp.text.count('') == 1
+ assert resp.text.count('') == 1
+ else:
+ assert resp.text.count('diff_sub') == 0
+ assert resp.text.count('diff_add') == 0
+ assert resp.text.count('diff_chg') == 2
resp = app.get(
'/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s'
% (unavailability_calendar.pk, snapshot2.pk, snapshot1.pk)
diff --git a/tox.ini b/tox.ini
index b0aed5b9..efaa8edb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -67,6 +67,7 @@ deps =
psycopg2-binary<2.9
git+https://git.entrouvert.org/publik-django-templatetags.git
responses
+ lxml
commands =
./getlasso3.sh
pylint: ./pylint.sh chrono/ tests/