8
0
Fork 0
mirror of https://gitlab.federez.net/re2o/re2o synced 2024-05-17 08:06:21 +00:00

Start implementing detailed history for event class

This commit is contained in:
Jean-Romain Garnier 2020-04-25 12:31:18 +02:00 committed by Jean-Romain Garnier
parent 63f09d7867
commit 4190f4f39e
8 changed files with 83 additions and 98 deletions

View file

@ -92,6 +92,10 @@ class ActionsSearch:
return classes
############################
# Machine history search #
############################
class MachineHistorySearchEvent:
def __init__(self, user, machine, interface, start=None, end=None):
"""
@ -280,14 +284,19 @@ class MachineHistorySearch:
return self.events
############################
# Generic history classes #
############################
class RelatedHistory:
def __init__(self, name, model_name, object_id):
def __init__(self, name, app_name, model_name, object_id):
"""
:param name: Name of this instance
:param model_name: Name of the related model (e.g. "user")
:param object_id: ID of the related object
"""
self.name = "{} (id = {})".format(name, object_id)
self.app_name = app_name
self.model_name = model_name
self.object_id = object_id
@ -417,6 +426,10 @@ class History:
self._last_version = version
############################
# Revision history #
############################
class VersionAction(HistoryEvent):
def __init__(self, version):
self.version = version
@ -496,6 +509,10 @@ class RevisionAction:
return self.revision.get_comment()
############################
# Class-specific history #
############################
class UserHistoryEvent(HistoryEvent):
def _repr(self, name, value):
"""
@ -588,7 +605,7 @@ class UserHistory(History):
super(UserHistory, self).__init__()
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
:return: list or None, a list of UserHistoryEvent, in reverse chronological order
@ -633,7 +650,8 @@ class UserHistory(History):
)
self.related = [RelatedHistory(
m.field_dict["name"] or _("None"),
"machine",
"machines"
"Machine",
m.field_dict["id"]) for m in self.related]
self.related = list(dict.fromkeys(self.related))
@ -716,7 +734,7 @@ class MachineHistory(History):
super(MachineHistory, self).__init__()
self.event_type = MachineHistoryEvent
def get(self, machine_id):
def get(self, machine_id, model):
# Add as "related" histories the list of Interface objects
# that were once assigned to this machine
self.related = list(
@ -728,7 +746,8 @@ class MachineHistory(History):
# Create RelatedHistory objects and remove duplicates
self.related = [RelatedHistory(
i.field_dict["mac_address"] or _("None"),
"interface",
"machines",
"Interface",
i.field_dict["id"]) for i in self.related]
self.related = list(dict.fromkeys(self.related))
@ -782,10 +801,34 @@ class InterfaceHistory(History):
super(InterfaceHistory, self).__init__()
self.event_type = InterfaceHistoryEvent
def get(self, interface_id):
def get(self, interface_id, model):
events = 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"]()

View file

@ -82,7 +82,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<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>
<a title="{% trans "History" %}" href="{% url 'logs:history' related.app_name related.model_name related.object_id %}">{{ related.model_name }} - {{ related.name }}</a>
</li>
{% endfor %}
</ul>

View file

@ -42,7 +42,7 @@ def is_facture(baseinvoice):
@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.
Args:
@ -57,6 +57,5 @@ def history_button(instance, text=False, detailed=False, html_class=True):
"name": instance._meta.model_name,
"id": instance.id,
"text": text,
"detailed": detailed,
"class": html_class,
}

View file

@ -42,11 +42,6 @@ urlpatterns = [
views.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_models/$", views.stats_models, name="stats-models"),
url(r"^stats_users/$", views.stats_users, name="stats-users"),

View file

@ -37,7 +37,6 @@ nombre d'objets par models, nombre d'actions par user, etc
"""
from __future__ import unicode_literals
from itertools import chain
from django.urls import reverse
from django.shortcuts import render, redirect
@ -105,9 +104,7 @@ from .models import (
ActionsSearch,
RevisionAction,
MachineHistorySearch,
UserHistory,
MachineHistory,
InterfaceHistory
get_history_class
)
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})
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
Handles permissions and DoesNotExist errors
"""
is_deleted = False
try:
object_name_id = object_name + "id"
kwargs = {object_name_id: object_id}
instance = model.get_instance(**kwargs)
except model.DoesNotExist:
is_deleted = True
instance = None
if is_deleted and not allow_deleted:
messages.error(request, _("Nonexistent entry."))
return False, redirect(
reverse("users:profil", kwargs={"userid": str(request.user.id)})
)
if is_deleted:
can_view = can_view_app("logs")
if instance is None:
authorized = can_view_app("logs")
msg = None
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(
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
@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
def history(request, application, object_name, object_id):
"""Render history for a model.
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
history using the `can_view` method of the model.
Permissions are handled by get_history_object.
history using the `can_view` method of the model, or the generic
`can_view_app("logs")` for deleted objects (see `get_history_object`).
Args:
request: The request sent by the user.
@ -637,16 +578,29 @@ def history(request, application, object_name, object_id):
except LookupError:
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:
return instance
pagination_number = GeneralOption.get_cached_value("pagination_number")
reversions = Version.objects.get_for_object(instance)
if hasattr(instance, "linked_objects"):
for related_object in chain(instance.linked_objects()):
reversions = reversions | Version.objects.get_for_object(related_object)
reversions = re2o_paginator(request, reversions, pagination_number)
history = get_history_class(model)
events = history.get(int(object_id), model)
# 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)})
)
# 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", {"reversions": reversions, "object": instance}
request,
"logs/detailed_history.html",
{"title": title, "events": events, "related_history": history.related},
)

View file

@ -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 %}
{% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc=tr_create_an_interface %}
{% acl_end %}
{% history_button machine detailed=True %}
{% history_button machine %}
{% can_delete machine %}
{% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %}
{% acl_end %}
@ -161,7 +161,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% acl_end %}
</ul>
</div>
{% history_button interface detailed=True %}
{% history_button interface %}
{% can_delete interface %}
{% include 'buttons/suppr.html' with href='machines:del-interface' id=interface.id %}
{% acl_end %}

View file

@ -24,13 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% 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 %}">
<i class="fa fa-history"></i> {% if text %}{% trans "History" %}{% endif %}
</a>
{% endif %}

View file

@ -176,7 +176,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Edit the groups" %}
</a>
{% acl_end %}
{% history_button users text=True detailed=True %}
{% history_button users text=True %}
</ul>
</div>
<div class="panel-body">