mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-12-23 15:33:45 +00:00
Merge branch 'complete_detailed_history' into 'dev'
Finish replacing old history view See merge request re2o/re2o!522
This commit is contained in:
commit
29b36bb346
10 changed files with 163 additions and 248 deletions
|
@ -92,6 +92,10 @@ class ActionsSearch:
|
||||||
return classes
|
return classes
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Machine history search #
|
||||||
|
############################
|
||||||
|
|
||||||
class MachineHistorySearchEvent:
|
class MachineHistorySearchEvent:
|
||||||
def __init__(self, user, machine, interface, start=None, end=None):
|
def __init__(self, user, machine, interface, start=None, end=None):
|
||||||
"""
|
"""
|
||||||
|
@ -280,16 +284,25 @@ class MachineHistorySearch:
|
||||||
return self.events
|
return self.events
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Generic history classes #
|
||||||
|
############################
|
||||||
|
|
||||||
class RelatedHistory:
|
class RelatedHistory:
|
||||||
def __init__(self, name, model_name, object_id):
|
def __init__(self, version):
|
||||||
"""
|
"""
|
||||||
:param name: Name of this instance
|
:param name: Name of this instance
|
||||||
:param model_name: Name of the related model (e.g. "user")
|
:param model_name: Name of the related model (e.g. "user")
|
||||||
:param object_id: ID of the related object
|
:param object_id: ID of the related object
|
||||||
"""
|
"""
|
||||||
self.name = "{} (id = {})".format(name, object_id)
|
self.version = version
|
||||||
self.model_name = model_name
|
self.app_name = version.content_type.app_label
|
||||||
self.object_id = object_id
|
self.model_name = version.content_type.model
|
||||||
|
self.object_id = version.object_id
|
||||||
|
self.name = version.object_repr
|
||||||
|
|
||||||
|
if self.model_name:
|
||||||
|
self.name = "{}: {}".format(self.model_name.title(), self.name)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return (
|
return (
|
||||||
|
@ -380,6 +393,7 @@ class History:
|
||||||
if self._last_version is None:
|
if self._last_version is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
self.name = self._last_version.object_repr
|
||||||
return self.events[::-1]
|
return self.events[::-1]
|
||||||
|
|
||||||
def _compute_diff(self, v1, v2, ignoring=[]):
|
def _compute_diff(self, v1, v2, ignoring=[]):
|
||||||
|
@ -417,6 +431,10 @@ class History:
|
||||||
self._last_version = version
|
self._last_version = version
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Revision history #
|
||||||
|
############################
|
||||||
|
|
||||||
class VersionAction(HistoryEvent):
|
class VersionAction(HistoryEvent):
|
||||||
def __init__(self, version):
|
def __init__(self, version):
|
||||||
self.version = version
|
self.version = version
|
||||||
|
@ -496,6 +514,10 @@ class RevisionAction:
|
||||||
return self.revision.get_comment()
|
return self.revision.get_comment()
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
|
# Class-specific history #
|
||||||
|
############################
|
||||||
|
|
||||||
class UserHistoryEvent(HistoryEvent):
|
class UserHistoryEvent(HistoryEvent):
|
||||||
def _repr(self, name, value):
|
def _repr(self, name, value):
|
||||||
"""
|
"""
|
||||||
|
@ -588,7 +610,7 @@ class UserHistory(History):
|
||||||
super(UserHistory, self).__init__()
|
super(UserHistory, self).__init__()
|
||||||
self.event_type = UserHistoryEvent
|
self.event_type = UserHistoryEvent
|
||||||
|
|
||||||
def get(self, user_id):
|
def get(self, user_id, model):
|
||||||
"""
|
"""
|
||||||
:param user_id: int, the id of the user to lookup
|
:param user_id: int, the id of the user to lookup
|
||||||
:return: list or None, a list of UserHistoryEvent, in reverse chronological order
|
:return: list or None, a list of UserHistoryEvent, in reverse chronological order
|
||||||
|
@ -624,17 +646,14 @@ class UserHistory(History):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Add in "related" elements the list of Machine objects
|
# Add in "related" elements the list of objects
|
||||||
# that were once owned by this user
|
# that were once owned by this user
|
||||||
self.related = (
|
self.related = (
|
||||||
Version.objects.get_for_model(Machine)
|
Version.objects.all()
|
||||||
.filter(serialized_data__contains='"user": {}'.format(user_id))
|
.filter(serialized_data__contains='"user": {}'.format(user_id))
|
||||||
.order_by("-revision__date_created")
|
.order_by("content_type__model")
|
||||||
)
|
)
|
||||||
self.related = [RelatedHistory(
|
self.related = [RelatedHistory(v) for v in self.related]
|
||||||
m.field_dict["name"] or _("None"),
|
|
||||||
"machine",
|
|
||||||
m.field_dict["id"]) for m in self.related]
|
|
||||||
self.related = list(dict.fromkeys(self.related))
|
self.related = list(dict.fromkeys(self.related))
|
||||||
|
|
||||||
# Get all the versions for this user, with the oldest first
|
# Get all the versions for this user, with the oldest first
|
||||||
|
@ -716,28 +735,18 @@ class MachineHistory(History):
|
||||||
super(MachineHistory, self).__init__()
|
super(MachineHistory, self).__init__()
|
||||||
self.event_type = MachineHistoryEvent
|
self.event_type = MachineHistoryEvent
|
||||||
|
|
||||||
def get(self, machine_id):
|
def get(self, machine_id, model):
|
||||||
# Add as "related" histories the list of Interface objects
|
self.related = (
|
||||||
# that were once assigned to this machine
|
|
||||||
self.related = list(
|
|
||||||
Version.objects.get_for_model(Interface)
|
Version.objects.get_for_model(Interface)
|
||||||
.filter(serialized_data__contains='"machine": {}'.format(machine_id))
|
.filter(serialized_data__contains='"machine": {}'.format(machine_id))
|
||||||
.order_by("-revision__date_created")
|
.order_by("content_type__model")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create RelatedHistory objects and remove duplicates
|
# Create RelatedHistory objects and remove duplicates
|
||||||
self.related = [RelatedHistory(
|
self.related = [RelatedHistory(v) for v in self.related]
|
||||||
i.field_dict["mac_address"] or _("None"),
|
|
||||||
"interface",
|
|
||||||
i.field_dict["id"]) for i in self.related]
|
|
||||||
self.related = list(dict.fromkeys(self.related))
|
self.related = list(dict.fromkeys(self.related))
|
||||||
|
|
||||||
events = super(MachineHistory, self).get(machine_id, Machine)
|
return super(MachineHistory, self).get(machine_id, Machine)
|
||||||
|
|
||||||
# Update name
|
|
||||||
self.name = self._last_version.field_dict["name"]
|
|
||||||
|
|
||||||
return events
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceHistoryEvent(HistoryEvent):
|
class InterfaceHistoryEvent(HistoryEvent):
|
||||||
|
@ -782,10 +791,29 @@ class InterfaceHistory(History):
|
||||||
super(InterfaceHistory, self).__init__()
|
super(InterfaceHistory, self).__init__()
|
||||||
self.event_type = InterfaceHistoryEvent
|
self.event_type = InterfaceHistoryEvent
|
||||||
|
|
||||||
def get(self, interface_id):
|
def get(self, interface_id, model):
|
||||||
events = super(InterfaceHistory, self).get(interface_id, Interface)
|
return super(InterfaceHistory, self).get(interface_id, Interface)
|
||||||
|
|
||||||
# Update name
|
|
||||||
self.name = self._last_version.field_dict["mac_address"]
|
|
||||||
|
|
||||||
return events
|
############################
|
||||||
|
# History auto-detect #
|
||||||
|
############################
|
||||||
|
|
||||||
|
HISTORY_CLASS_MAPPING = {
|
||||||
|
User: UserHistory,
|
||||||
|
Machine: MachineHistory,
|
||||||
|
Interface: InterfaceHistory,
|
||||||
|
"default": History
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_history_class(model):
|
||||||
|
"""
|
||||||
|
Find the mos appropriate History subclass to represent
|
||||||
|
the given model's history
|
||||||
|
:model: class
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return HISTORY_CLASS_MAPPING[model]()
|
||||||
|
except KeyError:
|
||||||
|
return HISTORY_CLASS_MAPPING["default"]()
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
{% extends 'logs/sidebar.html' %}
|
|
||||||
{% comment %}
|
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
|
||||||
se veut agnostique au réseau considéré, de manière à être installable en
|
|
||||||
quelques clics.
|
|
||||||
|
|
||||||
Copyright © 2020 Jean-Romain Garnier
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 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 General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along
|
|
||||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
{% load bootstrap3 %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load logs_extra %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "History" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>{% blocktrans %}History of {{ title }}{% endblocktrans %}</h2>
|
|
||||||
|
|
||||||
{% if events %}
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Date" %}</th>
|
|
||||||
<th>{% trans "Performed by" %}</th>
|
|
||||||
<th>{% trans "Edited" %}</th>
|
|
||||||
<th>{% trans "Comment" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
{% for event in events %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ event.date }}</td>
|
|
||||||
<td>
|
|
||||||
{% if event.performed_by %}
|
|
||||||
<a href="{% url 'users:profil' userid=event.performed_by.id %}" title=tr_view_the_profile>
|
|
||||||
{{ event.performed_by }}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
{% trans "Unknown" %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% for edit in event.edits %}
|
|
||||||
{% if edit.1 is None and edit.2 is None %}
|
|
||||||
<strong>{{ edit.0 }}</strong><br/>
|
|
||||||
{% elif edit.1 is None %}
|
|
||||||
<strong>{{ edit.0 }}:</strong>
|
|
||||||
<i class="text-success"> {{ edit.2 }}</i><br/>
|
|
||||||
{% else %}
|
|
||||||
<strong>{{ edit.0 }}:</strong>
|
|
||||||
<i class="text-danger"> {{ edit.1 }} </i>
|
|
||||||
➔ <i class="text-success">{{ edit.2 }}</i><br/>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
<td>{{ event.comment }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% include 'pagination.html' with list=events %}
|
|
||||||
{% else %}
|
|
||||||
<h3>{% trans "No event" %}</h3>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if related_history %}
|
|
||||||
|
|
||||||
<h2>{% blocktrans %}Related elements{% endblocktrans %}</h2>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{% for related in related_history %}
|
|
||||||
<li>
|
|
||||||
<a title="{% trans "History" %}" href="{% url 'logs:detailed-history' related.model_name related.object_id %}">{{ related.model_name }} - {{ related.name }}</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -42,7 +42,7 @@ def is_facture(baseinvoice):
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag("buttons/history.html")
|
@register.inclusion_tag("buttons/history.html")
|
||||||
def history_button(instance, text=False, detailed=False, html_class=True):
|
def history_button(instance, text=False, html_class=True):
|
||||||
"""Creates the correct history button for an instance.
|
"""Creates the correct history button for an instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -57,6 +57,5 @@ def history_button(instance, text=False, detailed=False, html_class=True):
|
||||||
"name": instance._meta.model_name,
|
"name": instance._meta.model_name,
|
||||||
"id": instance.id,
|
"id": instance.id,
|
||||||
"text": text,
|
"text": text,
|
||||||
"detailed": detailed,
|
|
||||||
"class": html_class,
|
"class": html_class,
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,11 +42,6 @@ urlpatterns = [
|
||||||
views.history,
|
views.history,
|
||||||
name="history",
|
name="history",
|
||||||
),
|
),
|
||||||
url(
|
|
||||||
r"(?P<object_name>\w+)/(?P<object_id>[0-9]+)$",
|
|
||||||
views.detailed_history,
|
|
||||||
name="detailed-history",
|
|
||||||
),
|
|
||||||
url(r"^stats_general/$", views.stats_general, name="stats-general"),
|
url(r"^stats_general/$", views.stats_general, name="stats-general"),
|
||||||
url(r"^stats_models/$", views.stats_models, name="stats-models"),
|
url(r"^stats_models/$", views.stats_models, name="stats-models"),
|
||||||
url(r"^stats_users/$", views.stats_users, name="stats-users"),
|
url(r"^stats_users/$", views.stats_users, name="stats-users"),
|
||||||
|
|
106
logs/views.py
106
logs/views.py
|
@ -37,7 +37,6 @@ nombre d'objets par models, nombre d'actions par user, etc
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
|
@ -105,9 +104,7 @@ from .models import (
|
||||||
ActionsSearch,
|
ActionsSearch,
|
||||||
RevisionAction,
|
RevisionAction,
|
||||||
MachineHistorySearch,
|
MachineHistorySearch,
|
||||||
UserHistory,
|
get_history_class
|
||||||
MachineHistory,
|
|
||||||
InterfaceHistory
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
|
@ -526,33 +523,24 @@ def stats_search_machine_history(request):
|
||||||
return render(request, "logs/search_machine_history.html", {"history_form": history_form})
|
return render(request, "logs/search_machine_history.html", {"history_form": history_form})
|
||||||
|
|
||||||
|
|
||||||
def get_history_object(request, model, object_name, object_id, allow_deleted=False):
|
def get_history_object(request, model, object_name, object_id):
|
||||||
"""Get the objet of type model with the given object_id
|
"""Get the objet of type model with the given object_id
|
||||||
Handles permissions and DoesNotExist errors
|
Handles permissions and DoesNotExist errors
|
||||||
"""
|
"""
|
||||||
is_deleted = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
object_name_id = object_name + "id"
|
object_name_id = object_name + "id"
|
||||||
kwargs = {object_name_id: object_id}
|
kwargs = {object_name_id: object_id}
|
||||||
instance = model.get_instance(**kwargs)
|
instance = model.get_instance(**kwargs)
|
||||||
except model.DoesNotExist:
|
except model.DoesNotExist:
|
||||||
is_deleted = True
|
|
||||||
instance = None
|
instance = None
|
||||||
|
|
||||||
if is_deleted and not allow_deleted:
|
if instance is None:
|
||||||
messages.error(request, _("Nonexistent entry."))
|
authorized = can_view_app("logs")
|
||||||
return False, redirect(
|
|
||||||
reverse("users:profil", kwargs={"userid": str(request.user.id)})
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_deleted:
|
|
||||||
can_view = can_view_app("logs")
|
|
||||||
msg = None
|
msg = None
|
||||||
else:
|
else:
|
||||||
can_view, msg, _permissions = instance.can_view(request.user)
|
authorized, msg, _permissions = instance.can_view(request.user)
|
||||||
|
|
||||||
if not can_view:
|
if not authorized:
|
||||||
messages.error(
|
messages.error(
|
||||||
request, msg or _("You don't have the right to access this menu.")
|
request, msg or _("You don't have the right to access this menu.")
|
||||||
)
|
)
|
||||||
|
@ -563,61 +551,14 @@ def get_history_object(request, model, object_name, object_id, allow_deleted=Fal
|
||||||
return True, instance
|
return True, instance
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def detailed_history(request, object_name, object_id):
|
|
||||||
"""Render a detailed history for a model.
|
|
||||||
Permissions are handled by get_history_object.
|
|
||||||
"""
|
|
||||||
# Only handle objects for which a detailed view exists
|
|
||||||
if object_name == "user":
|
|
||||||
model = User
|
|
||||||
history = UserHistory()
|
|
||||||
elif object_name == "machine":
|
|
||||||
model = Machine
|
|
||||||
history = MachineHistory()
|
|
||||||
elif object_name == "interface":
|
|
||||||
model = Interface
|
|
||||||
history = InterfaceHistory()
|
|
||||||
else:
|
|
||||||
raise Http404(_("No model found."))
|
|
||||||
|
|
||||||
# Get instance and check permissions
|
|
||||||
can_view, instance = get_history_object(request, model, object_name, object_id, allow_deleted=True)
|
|
||||||
if not can_view:
|
|
||||||
return instance
|
|
||||||
|
|
||||||
# Generate the pagination with the objects
|
|
||||||
max_result = GeneralOption.get_cached_value("pagination_number")
|
|
||||||
events = history.get(int(object_id))
|
|
||||||
|
|
||||||
# Events is None if object wasn't found
|
|
||||||
if events is None:
|
|
||||||
messages.error(request, _("Nonexistent entry."))
|
|
||||||
return redirect(
|
|
||||||
reverse("users:profil", kwargs={"userid": str(request.user.id)})
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add the paginator in case there are many results
|
|
||||||
events = re2o_paginator(request, events, max_result)
|
|
||||||
|
|
||||||
# Add a title in case the object was deleted
|
|
||||||
title = instance or "{} ({})".format(history.name, _("Deleted"))
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request,
|
|
||||||
"logs/detailed_history.html",
|
|
||||||
{"title": title, "events": events, "related_history": history.related},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def history(request, application, object_name, object_id):
|
def history(request, application, object_name, object_id):
|
||||||
"""Render history for a model.
|
"""Render history for a model.
|
||||||
|
|
||||||
The model is determined using the `HISTORY_BIND` dictionnary if none is
|
The model is determined using the `HISTORY_BIND` dictionnary if none is
|
||||||
found, raises a Http404. The view checks if the user is allowed to see the
|
found, raises a Http404. The view checks if the user is allowed to see the
|
||||||
history using the `can_view` method of the model.
|
history using the `can_view` method of the model, or the generic
|
||||||
Permissions are handled by get_history_object.
|
`can_view_app("logs")` for deleted objects (see `get_history_object`).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: The request sent by the user.
|
request: The request sent by the user.
|
||||||
|
@ -637,16 +578,29 @@ def history(request, application, object_name, object_id):
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise Http404(_("No model found."))
|
raise Http404(_("No model found."))
|
||||||
|
|
||||||
can_view, instance = get_history_object(request, model, object_name, object_id)
|
authorized, instance = get_history_object(request, model, object_name, object_id)
|
||||||
if not can_view:
|
if not can_view:
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
pagination_number = GeneralOption.get_cached_value("pagination_number")
|
history = get_history_class(model)
|
||||||
reversions = Version.objects.get_for_object(instance)
|
events = history.get(int(object_id), model)
|
||||||
if hasattr(instance, "linked_objects"):
|
|
||||||
for related_object in chain(instance.linked_objects()):
|
# Events is None if object wasn't found
|
||||||
reversions = reversions | Version.objects.get_for_object(related_object)
|
if events is None:
|
||||||
reversions = re2o_paginator(request, reversions, pagination_number)
|
messages.error(request, _("Nonexistent entry."))
|
||||||
return render(
|
return redirect(
|
||||||
request, "re2o/history.html", {"reversions": reversions, "object": instance}
|
reverse("users:profil", kwargs={"userid": str(request.user.id)})
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate the pagination with the objects
|
||||||
|
max_result = GeneralOption.get_cached_value("pagination_number")
|
||||||
|
events = re2o_paginator(request, events, max_result)
|
||||||
|
|
||||||
|
# Add a default title in case the object was deleted
|
||||||
|
title = instance or "{} ({})".format(history.name, _("Deleted"))
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"re2o/history.html",
|
||||||
|
{"title": title, "events": events, "related_history": history.related},
|
||||||
)
|
)
|
||||||
|
|
|
@ -67,7 +67,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% trans "Create an interface" as tr_create_an_interface %}
|
{% trans "Create an interface" as tr_create_an_interface %}
|
||||||
{% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc=tr_create_an_interface %}
|
{% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc=tr_create_an_interface %}
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% history_button machine detailed=True %}
|
{% history_button machine %}
|
||||||
{% can_delete machine %}
|
{% can_delete machine %}
|
||||||
{% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %}
|
{% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %}
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
|
@ -161,7 +161,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% history_button interface detailed=True %}
|
{% history_button interface %}
|
||||||
{% can_delete interface %}
|
{% can_delete interface %}
|
||||||
{% include 'buttons/suppr.html' with href='machines:del-interface' id=interface.id %}
|
{% include 'buttons/suppr.html' with href='machines:del-interface' id=interface.id %}
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
|
|
|
@ -6,6 +6,7 @@ quelques clics.
|
||||||
Copyright © 2017 Gabriel Détraz
|
Copyright © 2017 Gabriel Détraz
|
||||||
Copyright © 2017 Lara Kermarec
|
Copyright © 2017 Lara Kermarec
|
||||||
Copyright © 2017 Augustin Lemesle
|
Copyright © 2017 Augustin Lemesle
|
||||||
|
Copyright © 2020 Jean-Romain Garnier
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -23,36 +24,73 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load logs_extra %}
|
||||||
|
|
||||||
{% if reversions.paginator %}
|
{% if events %}
|
||||||
<ul class="pagination nav navbar-nav">
|
|
||||||
{% if reversions.has_previous %}
|
|
||||||
<li><a href="?page={{ reversions.previous_page_number }}">{% trans "Next" %}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% for page in reversions.paginator.page_range %}
|
|
||||||
<li class="{% if reversions.number == page %}active{% endif %}"><a href="?page={{page }}">{{ page }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if reversions.has_next %}
|
|
||||||
<li> <a href="?page={{ reversions.next_page_number }}">{% trans "Previous" %}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Date" %}</th>
|
<th>{% trans "Date" %}</th>
|
||||||
<th>{% trans "Performed by" %}</th>
|
<th>{% trans "Performed by" %}</th>
|
||||||
|
<th>{% trans "Edited" %}</th>
|
||||||
<th>{% trans "Comment" %}</th>
|
<th>{% trans "Comment" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for rev in reversions %}
|
{% for event in events %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ rev.revision.date_created }}</td>
|
<td>{{ event.date }}</td>
|
||||||
<td>{{ rev.revision.user }}</td>
|
<td>
|
||||||
<td>{{ rev.revision.comment }}</td>
|
{% if event.performed_by %}
|
||||||
|
<a href="{% url 'users:profil' userid=event.performed_by.id %}" title=tr_view_the_profile>
|
||||||
|
{{ event.performed_by }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{% trans "Unknown" %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for edit in event.edits %}
|
||||||
|
{% if edit.1 is None and edit.2 is None %}
|
||||||
|
<strong>{{ edit.0 }}</strong><br/>
|
||||||
|
{% elif edit.1 is None %}
|
||||||
|
<strong>{{ edit.0 }}:</strong>
|
||||||
|
<i class="text-success"> {{ edit.2 }}</i><br/>
|
||||||
|
{% else %}
|
||||||
|
<strong>{{ edit.0 }}:</strong>
|
||||||
|
<i class="text-danger"> {{ edit.1 }} </i>
|
||||||
|
➔ <i class="text-success">{{ edit.2 }}</i><br/>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>{{ event.comment }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
{% include 'pagination.html' with list=events %}
|
||||||
|
{% else %}
|
||||||
|
<h3>{% trans "No event" %}</h3>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if related_history %}
|
||||||
|
|
||||||
|
<h2>{% blocktrans %}Related elements{% endblocktrans %}</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for related in related_history %}
|
||||||
|
<li>
|
||||||
|
{% if related.object_id %}
|
||||||
|
<a href="{% url 'logs:history' related.app_name related.model_name related.object_id %}" title="{% trans "History" %}">
|
||||||
|
{{ related.name }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ related.name }}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ quelques clics.
|
||||||
Copyright © 2017 Gabriel Détraz
|
Copyright © 2017 Gabriel Détraz
|
||||||
Copyright © 2017 Lara Kermarec
|
Copyright © 2017 Lara Kermarec
|
||||||
Copyright © 2017 Augustin Lemesle
|
Copyright © 2017 Augustin Lemesle
|
||||||
|
Copyright © 2020 Jean-Romain Garnier
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -29,8 +30,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% block title %}{% trans "History" %}{% endblock %}
|
{% block title %}{% trans "History" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% blocktrans %}History of {{ object }}{% endblocktrans %}</h2>
|
<h2>{% blocktrans %}History of {{ title }}{% endblocktrans %}</h2>
|
||||||
{% include 're2o/aff_history.html' with reversions=reversions %}
|
{% include 're2o/aff_history.html' with events=events related_history=related_history %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -24,13 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% if detailed %}
|
|
||||||
<a {% if class%}class="btn btn-info btn-sm"{% endif %} role="button" title="{% trans "History" %}" href="{% url 'logs:detailed-history' name id %}">
|
|
||||||
<i class="fa fa-history"></i> {% if text %}{% trans "History" %}{% endif %}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<a {% if class%}class="btn btn-info btn-sm"{% endif %} role="button" title="{% trans "History" %}" href="{% url 'logs:history' application name id %}">
|
<a {% if class%}class="btn btn-info btn-sm"{% endif %} role="button" title="{% trans "History" %}" href="{% url 'logs:history' application name id %}">
|
||||||
<i class="fa fa-history"></i> {% if text %}{% trans "History" %}{% endif %}
|
<i class="fa fa-history"></i> {% if text %}{% trans "History" %}{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
|
@ -176,7 +176,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% trans "Edit the groups" %}
|
{% trans "Edit the groups" %}
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% history_button users text=True detailed=True %}
|
{% history_button users text=True %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
|
Loading…
Reference in a new issue