agendas: object history and compare (#87316)
This commit is contained in:
parent
0ea056dcd5
commit
efbf46c7d7
|
@ -40,7 +40,7 @@ class WithSnapshotMixin:
|
|||
return cls._meta.get_field('snapshot').related_model
|
||||
|
||||
def take_snapshot(self, *args, **kwargs):
|
||||
self.get_snapshot_model().take(self, *args, **kwargs)
|
||||
return self.get_snapshot_model().take(self, *args, **kwargs)
|
||||
|
||||
|
||||
class AbstractSnapshot(models.Model):
|
||||
|
@ -74,6 +74,7 @@ class AbstractSnapshot(models.Model):
|
|||
snapshot.application_slug = application.slug
|
||||
snapshot.application_version = application.version_number
|
||||
snapshot.save()
|
||||
return snapshot
|
||||
|
||||
def get_instance(self):
|
||||
try:
|
||||
|
@ -85,6 +86,54 @@ class AbstractSnapshot(models.Model):
|
|||
def load_instance(self, json_instance, snapshot=None):
|
||||
return self.get_instance_model().import_json(json_instance, snapshot=snapshot)[1]
|
||||
|
||||
def load_history(self):
|
||||
if self.instance is None:
|
||||
self._history = []
|
||||
return
|
||||
history = type(self).objects.filter(instance=self.instance)
|
||||
self._history = [s.id for s in history]
|
||||
|
||||
@property
|
||||
def previous(self):
|
||||
if not hasattr(self, '_history'):
|
||||
self.load_history()
|
||||
|
||||
try:
|
||||
idx = self._history.index(self.id)
|
||||
except ValueError:
|
||||
return None
|
||||
if idx == 0:
|
||||
return None
|
||||
return self._history[idx - 1]
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
if not hasattr(self, '_history'):
|
||||
self.load_history()
|
||||
|
||||
try:
|
||||
idx = self._history.index(self.id)
|
||||
except ValueError:
|
||||
return None
|
||||
try:
|
||||
return self._history[idx + 1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def first(self):
|
||||
if not hasattr(self, '_history'):
|
||||
self.load_history()
|
||||
|
||||
return self._history[0]
|
||||
|
||||
@property
|
||||
def last(self):
|
||||
if not hasattr(self, '_history'):
|
||||
self.load_history()
|
||||
|
||||
return self._history[-1]
|
||||
|
||||
|
||||
class AgendaSnapshot(AbstractSnapshot):
|
||||
instance = models.ForeignKey(
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
# chrono - agendas system
|
||||
# Copyright (C) 2016-2024 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import difflib
|
||||
import json
|
||||
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
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 chrono.utils.timezone import localtime
|
||||
|
||||
|
||||
class InstanceWithSnapshotHistoryView(ListView):
|
||||
def get_queryset(self):
|
||||
self.instance = get_object_or_404(self.model.get_instance_model(), pk=self.kwargs['pk'])
|
||||
return self.instance.instance_snapshots.all().select_related('user')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs[self.instance_context_key] = self.instance
|
||||
kwargs['object'] = self.instance
|
||||
current_date = None
|
||||
context = super().get_context_data(**kwargs)
|
||||
day_snapshot = None
|
||||
for snapshot in context['object_list']:
|
||||
if snapshot.timestamp.date() != current_date:
|
||||
current_date = snapshot.timestamp.date()
|
||||
snapshot.new_day = True
|
||||
snapshot.day_other_count = 0
|
||||
day_snapshot = snapshot
|
||||
else:
|
||||
day_snapshot.day_other_count += 1
|
||||
return context
|
||||
|
||||
|
||||
class InstanceWithSnapshotHistoryCompareView(DetailView):
|
||||
def get_snapshots(self):
|
||||
id1 = self.request.GET.get('version1')
|
||||
id2 = self.request.GET.get('version2')
|
||||
if not id1 or not id2:
|
||||
raise Http404
|
||||
|
||||
snapshot1 = get_object_or_404(self.model.get_snapshot_model(), pk=id1, instance=self.object)
|
||||
snapshot2 = get_object_or_404(self.model.get_snapshot_model(), pk=id2, instance=self.object)
|
||||
|
||||
return snapshot1, snapshot2
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs[self.instance_context_key] = self.object
|
||||
|
||||
mode = self.request.GET.get('mode') or 'json'
|
||||
if mode not in ['json']:
|
||||
raise Http404
|
||||
|
||||
snapshot1, snapshot2 = self.get_snapshots()
|
||||
if not snapshot1 or not snapshot2:
|
||||
return redirect(reverse(self.history_view, args=[self.object.pk]))
|
||||
if snapshot1.timestamp > snapshot2.timestamp:
|
||||
snapshot1, snapshot2 = snapshot2, snapshot1
|
||||
|
||||
kwargs['mode'] = mode
|
||||
kwargs['snapshot1'] = snapshot1
|
||||
kwargs['snapshot2'] = snapshot2
|
||||
kwargs['fromdesc'] = self.get_snapshot_desc(snapshot1)
|
||||
kwargs['todesc'] = self.get_snapshot_desc(snapshot2)
|
||||
kwargs.update(getattr(self, 'get_compare_%s_context' % mode)(snapshot1, snapshot2))
|
||||
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
context = self.get_context_data(object=self.object)
|
||||
if isinstance(context, HttpResponseRedirect):
|
||||
return context
|
||||
return self.render_to_response(context)
|
||||
|
||||
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_serialization = difflib.HtmlDiff(wrapcolumn=160).make_table(
|
||||
fromlines=s1.splitlines(True),
|
||||
tolines=s2.splitlines(True),
|
||||
)
|
||||
|
||||
return {
|
||||
'diff_serialization': diff_serialization,
|
||||
}
|
||||
|
||||
def get_snapshot_desc(self, snapshot):
|
||||
label_or_comment = ''
|
||||
if snapshot.label:
|
||||
label_or_comment = snapshot.label
|
||||
elif snapshot.comment:
|
||||
label_or_comment = snapshot.comment
|
||||
if snapshot.application_version:
|
||||
label_or_comment += ' (%s)' % _('Version %s') % snapshot.application_version
|
||||
return '{name} ({pk}) - {label_or_comment} ({user}{timestamp})'.format(
|
||||
name=_('Snapshot'),
|
||||
pk=snapshot.id,
|
||||
label_or_comment=label_or_comment,
|
||||
user='%s ' % snapshot.user if snapshot.user_id else '',
|
||||
timestamp=date_format(localtime(snapshot.timestamp), format='DATETIME_FORMAT'),
|
||||
)
|
|
@ -956,3 +956,54 @@ a.button.button-paragraph {
|
|||
.application-logo, .application-icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.snapshots-list .collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
p.snapshot-description {
|
||||
font-size: 80%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table.diff {
|
||||
background: white;
|
||||
border: 1px solid #f3f3f3;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
colgroup, thead, tbody, td {
|
||||
border: 1px solid #f3f3f3;
|
||||
}
|
||||
tbody tr:nth-child(even) {
|
||||
background: #fdfdfd;
|
||||
}
|
||||
th, td {
|
||||
max-width: 30vw;
|
||||
/* it will not actually limit width as the table is set to
|
||||
* expand to 100% but it will prevent one side getting wider
|
||||
*/
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
}
|
||||
.diff_header {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
td.diff_header {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
color: #606060;
|
||||
}
|
||||
.diff_next {
|
||||
display: none;
|
||||
}
|
||||
.diff_add {
|
||||
background-color: #aaffaa;
|
||||
}
|
||||
.diff_chg {
|
||||
background-color: #ffff77;
|
||||
}
|
||||
.diff_sub {
|
||||
background-color: #ffaaaa;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<p class="snapshot-description">{{ fromdesc|safe }} ➔ {{ todesc|safe }}</p>
|
||||
<div class="diff">
|
||||
{% if mode == 'json' %}
|
||||
{{ diff_serialization|safe }}
|
||||
{% else %}
|
||||
<div class="{{ tab_class_names }}">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
{% for tab in tabs %}{{ tab|safe }}{% endfor %}
|
||||
{{ tab_list|safe }}
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
{% for attrs, panel in panels %}
|
||||
<div{% for k, v in attrs.items %} {{ k }}="{{ v }}"{% endfor %}>
|
||||
{{ panel|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
|
@ -0,0 +1,63 @@
|
|||
{% load i18n %}
|
||||
<div>
|
||||
<form action="{{ compare_url }}" method="get">
|
||||
{% if object_list|length > 1 %}
|
||||
<p><button>{% trans "Show differences" %}</button></p>
|
||||
{% endif %}
|
||||
<table class="main">
|
||||
<thead>
|
||||
<th>{% trans 'Identifier' %}</th>
|
||||
<th>{% trans 'Compare' %}</th>
|
||||
<th>{% trans 'Date' %}</th>
|
||||
<th>{% trans 'Description' %}</th>
|
||||
<th>{% trans 'User' %}</th>
|
||||
<th>{% trans 'Actions' %}</th>
|
||||
</thead>
|
||||
<tbody class="snapshots-list">
|
||||
{% for snapshot in object_list %}
|
||||
<tr data-day="{{ snapshot.timestamp|date:"Y-m-d" }}" class="{% if snapshot.new_day %}new-day{% else %}collapsed{% endif %}">
|
||||
<td><span class="counter">#{{ snapshot.pk }}</span></td>
|
||||
<td>
|
||||
{% if object_list|length > 1 %}
|
||||
{% if not forloop.last %}<input type="radio" name="version1" value="{{ snapshot.pk }}" {% if forloop.first %}checked="checked"{% endif %} />{% else %} {% endif %}
|
||||
{% if not forloop.first %}<input type="radio" name="version2" value="{{ snapshot.pk }}" {% if forloop.counter == 2 %}checked="checked"{% endif %}/>{% else %} {% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ snapshot.timestamp }}
|
||||
{% if snapshot.new_day and snapshot.day_other_count %} — <a class="reveal" href="#day-{{ snapshot.timestamp|date:"Y-m-d"}}">
|
||||
{% if snapshot.day_other_count >= 50 %}<strong>{% endif %}
|
||||
{% blocktrans trimmed count counter=snapshot.day_other_count %}
|
||||
1 other this day
|
||||
{% plural %}
|
||||
{{ counter }} others
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if snapshot.label %}
|
||||
<strong>{{ snapshot.label }}</strong>
|
||||
{% elif snapshot.comment %}
|
||||
{{ snapshot.comment }}
|
||||
{% endif %}
|
||||
{% if snapshot.application_version %}({% blocktrans with version=snapshot.application_version %}Version {{ version }}{% endblocktrans %}){% endif %}
|
||||
</td>
|
||||
<td>{% if snapshot.user %} {{ snapshot.user.get_full_name }}{% endif %}</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$('tr.new-day a.reveal').on('click', function() {
|
||||
var day = $(this).parents('tr.new-day').data('day');
|
||||
$('.snapshots-list tr[data-day="' + day + '"]:not(.new-day)').toggleClass('collapsed');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_agenda_settings.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Agenda history' %} - {{ agenda }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-agenda-history' pk=agenda.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-agenda-history-compare' pk=agenda.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "chrono/manager_agenda_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-agenda-history-compare' pk=agenda.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -120,6 +120,10 @@
|
|||
<a class="button button-paragraph" rel="popup" class="action-duplicate" href="{% url 'chrono-manager-agenda-duplicate' pk=object.pk %}">{% trans 'Duplicate' %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% if show_history %}
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="{% url 'chrono-manager-agenda-history' pk=agenda.pk %}">{% trans 'History' %}</a>
|
||||
{% endif %}
|
||||
{% block agenda-extra-navigation-actions %}{% endblock %}
|
||||
|
||||
{% url 'chrono-manager-homepage' as object_list_url %}
|
||||
|
|
|
@ -20,6 +20,11 @@
|
|||
{% else %}
|
||||
<h2>{% trans "New Category" %}</h2>
|
||||
{% endif %}
|
||||
{% if show_history and category %}
|
||||
<span class="actions">
|
||||
<a href="{% url 'chrono-manager-category-history' pk=category.pk %}">{% trans 'History' %}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_category_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Category history' %} - {{ category }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-category-history' pk=category.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-category-history-compare' pk=category.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "chrono/manager_category_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-category-history-compare' pk=category.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
{% block agenda-extra-navigation-actions %}
|
||||
{% with lingo_url=object.get_lingo_url %}{% if lingo_url %}
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
{% if not show_history %}
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
{% endif %}
|
||||
<a class="button button-paragraph" href="{{ lingo_url }}">{% trans 'Pricing' context 'pricing' %}</a>
|
||||
{% endif %}{% endwith %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -20,6 +20,11 @@
|
|||
{% else %}
|
||||
<h2>{% trans "New events type" %}</h2>
|
||||
{% endif %}
|
||||
{% if show_history and object.pk %}
|
||||
<span class="actions">
|
||||
<a href="{% url 'chrono-manager-events-type-history' pk=object.pk %}">{% trans 'History' %}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_events_type_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Events type history' %} - {{ events_type }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-events-type-history' pk=events_type.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-events-type-history-compare' pk=events_type.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "chrono/manager_events_type_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-events-type-history-compare' pk=events_type.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -63,6 +63,11 @@
|
|||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-resource-edit' pk=resource.pk %}">{% trans 'Edit' %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% if show_history %}
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="{% url 'chrono-manager-resource-history' pk=resource.pk %}">{% trans 'History' %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% url 'chrono-manager-resource-list' as object_list_url %}
|
||||
{% include 'chrono/includes/application_detail_fragment.html' %}
|
||||
</aside>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_resource_detail.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Resource history' %} - {{ resource }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-resource-history' pk=resource.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-resource-history-compare' pk=resource.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "chrono/manager_resource_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-resource-history-compare' pk=resource.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_unavailability_calendar_settings.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'UnavailabilityCalendarSnapshot calendar history' %} - {{ unavailability_calendar }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-history' pk=unavailability_calendar.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-unavailability-calendar-history-compare' pk=unavailability_calendar.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "chrono/manager_unavailability_calendar_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-history-compare' pk=unavailability_calendar.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -54,6 +54,11 @@
|
|||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-unavailability-calendar-delete' pk=unavailability_calendar.id %}">{% trans 'Delete' %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% if show_history %}
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="{% url 'chrono-manager-unavailability-calendar-history' pk=unavailability_calendar.pk %}">{% trans 'History' %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% url 'chrono-manager-unavailability-calendar-list' as object_list_url %}
|
||||
{% include 'chrono/includes/application_detail_fragment.html' %}
|
||||
</aside>
|
||||
|
|
|
@ -65,6 +65,16 @@ urlpatterns = [
|
|||
views.unavailability_calendar_import_unavailabilities,
|
||||
name='chrono-manager-unavailability-calendar-import-unavailabilities',
|
||||
),
|
||||
path(
|
||||
'unavailability-calendar/<int:pk>/history/',
|
||||
views.unavailability_calendar_history,
|
||||
name='chrono-manager-unavailability-calendar-history',
|
||||
),
|
||||
path(
|
||||
'unavailability-calendar/<int:pk>/history/compare/',
|
||||
views.unavailability_calendar_history_compare,
|
||||
name='chrono-manager-unavailability-calendar-history-compare',
|
||||
),
|
||||
path('resources/', views.resource_list, name='chrono-manager-resource-list'),
|
||||
path('resource/add/', views.resource_add, name='chrono-manager-resource-add'),
|
||||
path('resource/<int:pk>/', views.resource_view, name='chrono-manager-resource-view'),
|
||||
|
@ -90,10 +100,22 @@ urlpatterns = [
|
|||
),
|
||||
path('resource/<int:pk>/edit/', views.resource_edit, name='chrono-manager-resource-edit'),
|
||||
path('resource/<int:pk>/delete/', views.resource_delete, name='chrono-manager-resource-delete'),
|
||||
path('resource/<int:pk>/history/', views.resource_history, name='chrono-manager-resource-history'),
|
||||
path(
|
||||
'resource/<int:pk>/history/compare/',
|
||||
views.resource_history_compare,
|
||||
name='chrono-manager-resource-history-compare',
|
||||
),
|
||||
path('categories/', views.category_list, name='chrono-manager-category-list'),
|
||||
path('category/add/', views.category_add, name='chrono-manager-category-add'),
|
||||
path('category/<int:pk>/edit/', views.category_edit, name='chrono-manager-category-edit'),
|
||||
path('category/<int:pk>/delete/', views.category_delete, name='chrono-manager-category-delete'),
|
||||
path('category/<int:pk>/history/', views.category_history, name='chrono-manager-category-history'),
|
||||
path(
|
||||
'category/<int:pk>/history/compare/',
|
||||
views.category_history_compare,
|
||||
name='chrono-manager-category-history-compare',
|
||||
),
|
||||
path('events-types/', views.events_type_list, name='chrono-manager-events-type-list'),
|
||||
path('events-type/add/', views.events_type_add, name='chrono-manager-events-type-add'),
|
||||
path('events-type/<int:pk>/edit/', views.events_type_edit, name='chrono-manager-events-type-edit'),
|
||||
|
@ -102,6 +124,14 @@ urlpatterns = [
|
|||
views.events_type_delete,
|
||||
name='chrono-manager-events-type-delete',
|
||||
),
|
||||
path(
|
||||
'events-type/<int:pk>/history/', views.events_type_history, name='chrono-manager-events-type-history'
|
||||
),
|
||||
path(
|
||||
'events-type/<int:pk>/history/compare/',
|
||||
views.events_type_history_compare,
|
||||
name='chrono-manager-events-type-history-compare',
|
||||
),
|
||||
path('agendas/add/', views.agenda_add, name='chrono-manager-agenda-add'),
|
||||
path('agendas/import/', views.agendas_import, name='chrono-manager-agendas-import'),
|
||||
path('agendas/export/', views.agendas_export, name='chrono-manager-agendas-export'),
|
||||
|
@ -449,6 +479,12 @@ urlpatterns = [
|
|||
views.agenda_import_events_sample_csv,
|
||||
name='chrono-manager-sample-events-csv',
|
||||
),
|
||||
path('agendas/<int:pk>/history/', views.agenda_history, name='chrono-manager-agenda-history'),
|
||||
path(
|
||||
'agendas/<int:pk>/history/compare/',
|
||||
views.agenda_history_compare,
|
||||
name='chrono-manager-agenda-history-compare',
|
||||
),
|
||||
path(
|
||||
'shared-custody/settings/',
|
||||
views.shared_custody_settings,
|
||||
|
|
|
@ -92,6 +92,14 @@ from chrono.agendas.models import (
|
|||
VirtualMember,
|
||||
)
|
||||
from chrono.apps.export_import.models import Application
|
||||
from chrono.apps.snapshot.models import (
|
||||
AgendaSnapshot,
|
||||
CategorySnapshot,
|
||||
EventsTypeSnapshot,
|
||||
ResourceSnapshot,
|
||||
UnavailabilityCalendarSnapshot,
|
||||
)
|
||||
from chrono.apps.snapshot.views import InstanceWithSnapshotHistoryCompareView, InstanceWithSnapshotHistoryView
|
||||
from chrono.utils.date import get_weekday_index
|
||||
from chrono.utils.timezone import localtime, make_aware, make_naive, now
|
||||
|
||||
|
@ -288,6 +296,7 @@ class ResourceDetailView(DetailView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['resource'] = self.object
|
||||
context['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
return context
|
||||
|
||||
|
||||
|
@ -791,6 +800,35 @@ class ResourceDeleteView(DeleteView):
|
|||
resource_delete = ResourceDeleteView.as_view()
|
||||
|
||||
|
||||
class ResourceHistoryView(InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_resource_history.html'
|
||||
model = ResourceSnapshot
|
||||
instance_context_key = 'resource'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
resource_history = ResourceHistoryView.as_view()
|
||||
|
||||
|
||||
class ResourceHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
|
||||
template_name = 'chrono/manager_resource_history_compare.html'
|
||||
model = Resource
|
||||
instance_context_key = 'resource'
|
||||
history_view = 'chrono-manager-resource-history'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
resource_history_compare = ResourceHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class CategoryListView(WithApplicationsMixin, ListView):
|
||||
template_name = 'chrono/manager_category_list.html'
|
||||
model = Category
|
||||
|
@ -852,6 +890,10 @@ class CategoryEditView(UpdateView):
|
|||
self.object.take_snapshot(request=self.request)
|
||||
return response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
category_edit = CategoryEditView.as_view()
|
||||
|
||||
|
@ -876,6 +918,35 @@ class CategoryDeleteView(DeleteView):
|
|||
category_delete = CategoryDeleteView.as_view()
|
||||
|
||||
|
||||
class CategoryHistoryView(InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_category_history.html'
|
||||
model = CategorySnapshot
|
||||
instance_context_key = 'category'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
category_history = CategoryHistoryView.as_view()
|
||||
|
||||
|
||||
class CategoryHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
|
||||
template_name = 'chrono/manager_category_history_compare.html'
|
||||
model = Category
|
||||
instance_context_key = 'category'
|
||||
history_view = 'chrono-manager-category-history'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
category_history_compare = CategoryHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class EventsTypeListView(WithApplicationsMixin, ListView):
|
||||
template_name = 'chrono/manager_events_type_list.html'
|
||||
model = EventsType
|
||||
|
@ -934,6 +1005,7 @@ class EventsTypeEditView(UpdateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
data = None
|
||||
if self.request.method == 'POST':
|
||||
data = self.request.POST
|
||||
|
@ -1020,6 +1092,35 @@ class EventsTypeDeleteView(DeleteView):
|
|||
events_type_delete = EventsTypeDeleteView.as_view()
|
||||
|
||||
|
||||
class EventsTypeHistoryView(InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_events_type_history.html'
|
||||
model = EventsTypeSnapshot
|
||||
instance_context_key = 'events_type'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
events_type_history = EventsTypeHistoryView.as_view()
|
||||
|
||||
|
||||
class EventsTypeHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
|
||||
template_name = 'chrono/manager_events_type_history_compare.html'
|
||||
model = EventsType
|
||||
instance_context_key = 'events_type'
|
||||
history_view = 'chrono-manager-events-type-history'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
events_type_history_compare = EventsTypeHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class AgendaAddView(CreateView):
|
||||
template_name = 'chrono/manager_agenda_add_form.html'
|
||||
model = Agenda
|
||||
|
@ -2430,6 +2531,7 @@ class AgendaSettings(ManagedAgendaMixin, DetailView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
if self.agenda.accept_meetings():
|
||||
context['meeting_types'] = self.object.iter_meetingtypes()
|
||||
if self.agenda.kind == 'virtual':
|
||||
|
@ -4052,6 +4154,35 @@ class TimePeriodExceptionSourceRefreshView(ManagedTimePeriodExceptionMixin, Deta
|
|||
time_period_exception_source_refresh = TimePeriodExceptionSourceRefreshView.as_view()
|
||||
|
||||
|
||||
class AgendaHistoryView(InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_agenda_history.html'
|
||||
model = AgendaSnapshot
|
||||
instance_context_key = 'agenda'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
agenda_history = AgendaHistoryView.as_view()
|
||||
|
||||
|
||||
class AgendaHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
|
||||
template_name = 'chrono/manager_agenda_history_compare.html'
|
||||
model = Agenda
|
||||
instance_context_key = 'agenda'
|
||||
history_view = 'chrono-manager-agenda-history'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
agenda_history_compare = AgendaHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class BookingCancelView(ViewableAgendaMixin, UpdateView):
|
||||
template_name = 'chrono/manager_confirm_booking_cancellation.html'
|
||||
model = Booking
|
||||
|
@ -4560,6 +4691,7 @@ class UnavailabilityCalendarSettings(ManagedUnavailabilityCalendarMixin, DetailV
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['unavailability_calendar'] = self.object
|
||||
context['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
return context
|
||||
|
||||
|
||||
|
@ -4669,6 +4801,35 @@ class UnavailabilityCalendarImportUnavailabilitiesView(ManagedUnavailabilityCale
|
|||
unavailability_calendar_import_unavailabilities = UnavailabilityCalendarImportUnavailabilitiesView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarHistoryView(InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_history.html'
|
||||
model = UnavailabilityCalendarSnapshot
|
||||
instance_context_key = 'unavailability_calendar'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
unavailability_calendar_history = UnavailabilityCalendarHistoryView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_history_compare.html'
|
||||
model = UnavailabilityCalendar
|
||||
instance_context_key = 'unavailability_calendar'
|
||||
history_view = 'chrono-manager-unavailability-calendar-history'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
unavailability_calendar_history_compare = UnavailabilityCalendarHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class SharedCustodyAgendaMixin:
|
||||
agenda = None
|
||||
tab_anchor = None
|
||||
|
|
|
@ -206,6 +206,7 @@ REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'chrono.api.utils.exception_handler'}
|
|||
|
||||
SHARED_CUSTODY_ENABLED = False
|
||||
PARTIAL_BOOKINGS_ENABLED = False
|
||||
SNAPSHOTS_ENABLED = False
|
||||
|
||||
CHRONO_ANTS_HUB_URL = None
|
||||
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
import datetime
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
|
||||
from chrono.agendas.models import Agenda, Category, Desk, Event, EventsType, Resource, UnavailabilityCalendar
|
||||
from chrono.apps.snapshot.models import (
|
||||
AgendaSnapshot,
|
||||
CategorySnapshot,
|
||||
EventsTypeSnapshot,
|
||||
ResourceSnapshot,
|
||||
UnavailabilityCalendarSnapshot,
|
||||
)
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_agenda_history(settings, app, admin_user):
|
||||
agenda = Agenda.objects.create(slug='foo', label='Foo')
|
||||
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
||||
snapshot1 = agenda.take_snapshot()
|
||||
Event.objects.create(
|
||||
agenda=agenda,
|
||||
places=1,
|
||||
start_datetime=now() - datetime.timedelta(days=60),
|
||||
)
|
||||
agenda.description = 'Foo Bar'
|
||||
agenda.save()
|
||||
snapshot2 = agenda.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert AgendaSnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
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
|
||||
resp = app.get(
|
||||
'/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (agenda.pk, snapshot2.pk, snapshot1.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
|
||||
|
||||
|
||||
def test_category_history(settings, app, admin_user):
|
||||
category = Category.objects.create(slug='foo', label='Foo')
|
||||
snapshot1 = category.take_snapshot()
|
||||
category.label = 'Bar'
|
||||
category.save()
|
||||
snapshot2 = category.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert CategorySnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/category/%s/edit/' % category.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/category/%s/edit/' % category.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
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
|
||||
resp = app.get(
|
||||
'/manage/category/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (category.pk, snapshot2.pk, snapshot1.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
|
||||
|
||||
|
||||
def test_events_type_history(settings, app, admin_user):
|
||||
events_type = EventsType.objects.create(slug='foo', label='Foo')
|
||||
snapshot1 = events_type.take_snapshot()
|
||||
events_type.label = 'Bar'
|
||||
events_type.save()
|
||||
snapshot2 = events_type.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert EventsTypeSnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/events-type/%s/edit/' % events_type.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/events-type/%s/edit/' % events_type.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
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
|
||||
resp = app.get(
|
||||
'/manage/events-type/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (events_type.pk, snapshot2.pk, snapshot1.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
|
||||
|
||||
|
||||
def test_resource_history(settings, app, admin_user):
|
||||
resource = Resource.objects.create(slug='foo', label='Foo')
|
||||
snapshot1 = resource.take_snapshot()
|
||||
resource.label = 'Bar'
|
||||
resource.save()
|
||||
snapshot2 = resource.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert ResourceSnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/resource/%s/' % resource.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/resource/%s/' % resource.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
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
|
||||
resp = app.get(
|
||||
'/manage/resource/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (resource.pk, snapshot2.pk, snapshot1.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
|
||||
|
||||
|
||||
def test_unavailability_calendar_history(settings, app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
|
||||
snapshot1 = unavailability_calendar.take_snapshot()
|
||||
unavailability_calendar.label = 'Bar'
|
||||
unavailability_calendar.save()
|
||||
snapshot2 = unavailability_calendar.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert UnavailabilityCalendarSnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
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
|
||||
resp = app.get(
|
||||
'/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (unavailability_calendar.pk, snapshot2.pk, snapshot1.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
|
Loading…
Reference in New Issue