diff --git a/logs/models.py b/logs/models.py index acf8771b..112d536f 100644 --- a/logs/models.py +++ b/logs/models.py @@ -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"]() diff --git a/logs/templates/logs/detailed_history.html b/logs/templates/logs/detailed_history.html index 7aaf67a0..8ffe5d68 100644 --- a/logs/templates/logs/detailed_history.html +++ b/logs/templates/logs/detailed_history.html @@ -82,7 +82,7 @@ with this program; if not, write to the Free Software Foundation, Inc., diff --git a/logs/templatetags/logs_extra.py b/logs/templatetags/logs_extra.py index 2e58cb67..c436c1fa 100644 --- a/logs/templatetags/logs_extra.py +++ b/logs/templatetags/logs_extra.py @@ -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, } diff --git a/logs/urls.py b/logs/urls.py index 914761bf..d70cc4a8 100644 --- a/logs/urls.py +++ b/logs/urls.py @@ -42,11 +42,6 @@ urlpatterns = [ views.history, name="history", ), - url( - r"(?P\w+)/(?P[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"), diff --git a/logs/views.py b/logs/views.py index b71187aa..69102b31 100644 --- a/logs/views.py +++ b/logs/views.py @@ -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}, ) diff --git a/machines/templates/machines/aff_machines.html b/machines/templates/machines/aff_machines.html index 857d2a3a..77b65546 100644 --- a/machines/templates/machines/aff_machines.html +++ b/machines/templates/machines/aff_machines.html @@ -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 %} - {% 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 %} diff --git a/templates/buttons/history.html b/templates/buttons/history.html index 2495dec8..71c2c71d 100644 --- a/templates/buttons/history.html +++ b/templates/buttons/history.html @@ -24,13 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load i18n %} -{% if detailed %} - - {% if text %}{% trans "History" %}{% endif %} - -{% else %} {% if text %}{% trans "History" %}{% endif %} -{% endif %} diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 4fa780a7..4fec3c18 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -176,7 +176,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Edit the groups" %} {% acl_end %} - {% history_button users text=True detailed=True %} + {% history_button users text=True %}