From b9e633cf25e9d4dbb3503534d0872da02a38226a Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 15:37:05 +0200 Subject: [PATCH 01/25] Add view to filter event logs --- logs/forms.py | 55 +++++++++++++++++++++- logs/models.py | 36 +++++++++++++- logs/templates/logs/search_stats_logs.html | 46 ++++++++++++++++++ logs/views.py | 37 +++++++++------ 4 files changed, 157 insertions(+), 17 deletions(-) create mode 100644 logs/templates/logs/search_stats_logs.html diff --git a/logs/forms.py b/logs/forms.py index 07e421c9..1ebfdcd4 100644 --- a/logs/forms.py +++ b/logs/forms.py @@ -25,15 +25,68 @@ from django.forms import Form from django.utils.translation import ugettext_lazy as _ from re2o.base import get_input_formats_help_text +import inspect + +# Import all models in which there are classes to be filtered on +import machines.models +import preferences.models +import tickets.models +import topologie.models +import users.models + + +def get_classes(module): + classes = [] + + for name, obj in inspect.getmembers(module): + if inspect.isclass(obj): + classes.append((obj, name)) + + return classes + + +# Get the list of all imported classes +modules = [machines.models, preferences.models, tickets.models, topologie.models, users.models] +classes = sum([get_classes(m) for m in modules]) + +CHOICES_ACTION_TYPE = classes + CHOICES_TYPE = ( ("ip", _("IPv4")), ("mac", _("MAC address")), ) +class ActionsSearchForm(Form): + """The form for a simple search""" + u = forms.CharField( + label=_("Performed by"), + max_length=100, + required=False, + queryset=users.models.User.objects.all() + ) + t = forms.MultipleChoiceField( + label=_("Action type"), + required=False, + widget=forms.CheckboxSelectMultiple, + choices=CHOICES_ACTION_TYPE, + initial=[i[0] for i in CHOICES_ACTION_TYPE], + ) + s = forms.DateField(required=False, label=_("Start date")) + e = forms.DateField(required=False, label=_("End date")) + + def __init__(self, *args, **kwargs): + super(MachineHistorySearchForm, self).__init__(*args, **kwargs) + self.fields["s"].help_text = get_input_formats_help_text( + self.fields["s"].input_formats + ) + self.fields["e"].help_text = get_input_formats_help_text( + self.fields["e"].input_formats + ) + + class MachineHistorySearchForm(Form): """The form for a simple search""" - q = forms.CharField( label=_("Search"), max_length=100, diff --git a/logs/models.py b/logs/models.py index b8789db8..cc261411 100644 --- a/logs/models.py +++ b/logs/models.py @@ -21,9 +21,10 @@ """logs.models The models definitions for the logs app """ -from reversion.models import Version +from reversion.models import Version, Revision from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import Group +from django.db.models import Q from machines.models import IpList from machines.models import Interface @@ -36,6 +37,39 @@ from topologie.models import Room from topologie.models import Port +class ActionsSearch: + def get(self, params): + """ + :param params: dict built by the search view + :return: QuerySet of Revision objects + """ + user = params.get("u", None) + start = params.get("s", None) + end = params.get("e", None) + actions_type = params.get("t", None) + + query = Q() + + if user: + query &= Q(user=user) + + if start: + query &= Q(date_created__geq=start) + + if end: + query &= Q(date_created__leq=end) + + if actions_type: + query &= Q(version_set__object__in=actions_type) + + return ( + Revision.objects.all() + .filter(query) + .select_related("user") + .prefetch_related("version_set__object") + ) + + class MachineHistorySearchEvent: def __init__(self, user, machine, interface, start=None, end=None): """ diff --git a/logs/templates/logs/search_stats_logs.html b/logs/templates/logs/search_stats_logs.html new file mode 100644 index 00000000..d0b56ea6 --- /dev/null +++ b/logs/templates/logs/search_stats_logs.html @@ -0,0 +1,46 @@ +{% 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 %} + +{% block title %}{% trans "Search events" %}{% endblock %} + +{% block content %} + +
+

{% trans "Search events" %}

+ + {% bootstrap_field actions_form.u %} + {% bootstrap_field actions_form.t %} + {% bootstrap_field actions_form.s %} + {% bootstrap_field actions_form.e %} + {% trans "Search" as tr_search %} + {% bootstrap_button tr_search button_type="submit" icon="search" %} +
+
+
+
+
+
+{% endblock %} diff --git a/logs/views.py b/logs/views.py index ad78bd23..6075d928 100644 --- a/logs/views.py +++ b/logs/views.py @@ -102,13 +102,17 @@ from re2o.base import re2o_paginator, SortTable from re2o.acl import can_view_all, can_view_app, can_edit_history, can_view from .models import ( + ActionsSearch, MachineHistorySearch, UserHistory, MachineHistory, InterfaceHistory ) -from .forms import MachineHistorySearchForm +from .forms import ( + ActionsSearchForm, + MachineHistorySearchForm +) @login_required @@ -158,20 +162,23 @@ def index(request): def stats_logs(request): """Affiche l'ensemble des logs et des modifications sur les objets, classés par date croissante, en vrac""" - pagination_number = GeneralOption.get_cached_value("pagination_number") - revisions = ( - Revision.objects.all() - .select_related("user") - .prefetch_related("version_set__object") - ) - revisions = SortTable.sort( - revisions, - request.GET.get("col"), - request.GET.get("order"), - SortTable.LOGS_STATS_LOGS, - ) - revisions = re2o_paginator(request, revisions, pagination_number) - return render(request, "logs/stats_logs.html", {"revisions_list": revisions}) + actions_form = ActionsSearchForm(request.GET or None) + + if actions_form.is_valid(): + actions = ActionsSearch() + revisions = actions.get(actions_form.cleaned_data) + revisions = SortTable.sort( + revisions, + request.GET.get("col"), + request.GET.get("order"), + SortTable.LOGS_STATS_LOGS, + ) + + pagination_number = GeneralOption.get_cached_value("pagination_number") + revisions = re2o_paginator(request, revisions, pagination_number) + return render(request, "logs/stats_logs.html", {"revisions_list": revisions}) + + return render(request, "logs/search_stats_logs.html", {"actions_form": actions_form}) @login_required From 9e38ec0faa092af8e39e0606d494979200b9d44a Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 13:56:02 +0000 Subject: [PATCH 02/25] Fix actions filtering --- logs/forms.py | 11 ++++++----- logs/models.py | 2 +- logs/templates/logs/search_stats_logs.html | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/logs/forms.py b/logs/forms.py index 1ebfdcd4..72212b0c 100644 --- a/logs/forms.py +++ b/logs/forms.py @@ -40,16 +40,18 @@ def get_classes(module): for name, obj in inspect.getmembers(module): if inspect.isclass(obj): - classes.append((obj, name)) + classes.append(name) return classes # Get the list of all imported classes modules = [machines.models, preferences.models, tickets.models, topologie.models, users.models] -classes = sum([get_classes(m) for m in modules]) +classes = sum([get_classes(m) for m in modules], []) -CHOICES_ACTION_TYPE = classes +CHOICES_ACTION_TYPE = [ + (str(i), classes[i]) for i in range(len(classes)) +] CHOICES_TYPE = ( ("ip", _("IPv4")), @@ -63,7 +65,6 @@ class ActionsSearchForm(Form): label=_("Performed by"), max_length=100, required=False, - queryset=users.models.User.objects.all() ) t = forms.MultipleChoiceField( label=_("Action type"), @@ -76,7 +77,7 @@ class ActionsSearchForm(Form): e = forms.DateField(required=False, label=_("End date")) def __init__(self, *args, **kwargs): - super(MachineHistorySearchForm, self).__init__(*args, **kwargs) + super(ActionsSearchForm, self).__init__(*args, **kwargs) self.fields["s"].help_text = get_input_formats_help_text( self.fields["s"].input_formats ) diff --git a/logs/models.py b/logs/models.py index cc261411..1ce117ff 100644 --- a/logs/models.py +++ b/logs/models.py @@ -60,7 +60,7 @@ class ActionsSearch: query &= Q(date_created__leq=end) if actions_type: - query &= Q(version_set__object__in=actions_type) + query &= Q(version__content_type__in=actions_type) return ( Revision.objects.all() diff --git a/logs/templates/logs/search_stats_logs.html b/logs/templates/logs/search_stats_logs.html index d0b56ea6..5faa8066 100644 --- a/logs/templates/logs/search_stats_logs.html +++ b/logs/templates/logs/search_stats_logs.html @@ -32,7 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

{% trans "Search events" %}

{% bootstrap_field actions_form.u %} - {% bootstrap_field actions_form.t %} + {% include 'buttons/multiple_checkbox_alt.html' with field=actions_form.t %} {% bootstrap_field actions_form.s %} {% bootstrap_field actions_form.e %} {% trans "Search" as tr_search %} From 5b9dec619aef889daadaf8465aef22a438961a57 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 16:17:20 +0200 Subject: [PATCH 03/25] Make event logs filter more user friendly --- logs/forms.py | 58 ++++++++++++++++++++++++++++++++++++++++---------- logs/models.py | 24 ++++++++++++++++++--- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/logs/forms.py b/logs/forms.py index 72212b0c..ffae8e52 100644 --- a/logs/forms.py +++ b/logs/forms.py @@ -28,6 +28,7 @@ from re2o.base import get_input_formats_help_text import inspect # Import all models in which there are classes to be filtered on +import cotisations.models import machines.models import preferences.models import tickets.models @@ -35,7 +36,23 @@ import topologie.models import users.models -def get_classes(module): +CHOICES_ACTION_TYPE = ( + ("users", _("Users")), + ("machines", _("Machines")), + ("subscriptions", _("Subscription")), + ("whitelists", _("Whitelists")), + ("bans", _("Bans")), + ("topology", _("Topology")), + ("all", _("All")), +) + +CHOICES_TYPE = ( + ("ip", _("IPv4")), + ("mac", _("MAC address")), +) + + +def all_classes(module): classes = [] for name, obj in inspect.getmembers(module): @@ -45,18 +62,37 @@ def get_classes(module): return classes -# Get the list of all imported classes -modules = [machines.models, preferences.models, tickets.models, topologie.models, users.models] -classes = sum([get_classes(m) for m in modules], []) +def classes_for_action_type(action_type): + """Return the list of class names to be displayed for a + given actions type filter""" + if action_type == "users": + return [ + users.models.User.__name__, + users.models.Adherent.__name__, + users.models.Club.__name__, + users.models.EMailAddress.__name__ + ] -CHOICES_ACTION_TYPE = [ - (str(i), classes[i]) for i in range(len(classes)) -] + if action_type == "machines": + return [ + machines.models.Machine.__name__, + machines.models.Interface.__name__ + ] -CHOICES_TYPE = ( - ("ip", _("IPv4")), - ("mac", _("MAC address")), -) + if action_type == "subscriptions": + return all_classes(cotisations.models) + + if action_type == "whitelists": + return [users.models.Whitelist.__name__] + + if action_type == "ban": + return [users.models.Ban.__name__] + + if action_type == "topology": + return all_classes(topologie.models) + + # "all" is a special case, just return None + return None class ActionsSearchForm(Form): diff --git a/logs/models.py b/logs/models.py index 1ce117ff..6528ea23 100644 --- a/logs/models.py +++ b/logs/models.py @@ -36,6 +36,7 @@ from users.models import Club from topologie.models import Room from topologie.models import Port +from .forms import classes_for_action_type class ActionsSearch: def get(self, params): @@ -46,7 +47,7 @@ class ActionsSearch: user = params.get("u", None) start = params.get("s", None) end = params.get("e", None) - actions_type = params.get("t", None) + action_types = params.get("t", None) query = Q() @@ -59,8 +60,9 @@ class ActionsSearch: if end: query &= Q(date_created__leq=end) - if actions_type: - query &= Q(version__content_type__in=actions_type) + action_classes = self.classes_for_action_types(action_types) + if action_classes: + query &= Q(object__classname=action_classes) return ( Revision.objects.all() @@ -69,6 +71,22 @@ class ActionsSearch: .prefetch_related("version_set__object") ) + def classes_for_action_types(self, action_types): + if action_types is None: + return None + + classes = [] + for action_type in action_types: + c = classes_for_action_type(action_type) + + # Selecting "all" removes the filter + if c is None: + return None + + classes += c + + return classes + class MachineHistorySearchEvent: def __init__(self, user, machine, interface, start=None, end=None): From ba7e0080aaec8cfbe97da6fea05c93c608548eaf Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 14:34:36 +0000 Subject: [PATCH 04/25] Fix filtering action logs by type --- logs/forms.py | 2 +- logs/models.py | 8 ++++---- logs/templates/logs/aff_stats_logs.html | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/logs/forms.py b/logs/forms.py index ffae8e52..ae281e98 100644 --- a/logs/forms.py +++ b/logs/forms.py @@ -85,7 +85,7 @@ def classes_for_action_type(action_type): if action_type == "whitelists": return [users.models.Whitelist.__name__] - if action_type == "ban": + if action_type == "bans": return [users.models.Ban.__name__] if action_type == "topology": diff --git a/logs/models.py b/logs/models.py index 6528ea23..ce2b3602 100644 --- a/logs/models.py +++ b/logs/models.py @@ -60,9 +60,9 @@ class ActionsSearch: if end: query &= Q(date_created__leq=end) - action_classes = self.classes_for_action_types(action_types) - if action_classes: - query &= Q(object__classname=action_classes) + action_models = self.models_for_action_types(action_types) + if action_models: + query &= Q(version__content_type__model__in=action_models) return ( Revision.objects.all() @@ -71,7 +71,7 @@ class ActionsSearch: .prefetch_related("version_set__object") ) - def classes_for_action_types(self, action_types): + def models_for_action_types(self, action_types): if action_types is None: return None diff --git a/logs/templates/logs/aff_stats_logs.html b/logs/templates/logs/aff_stats_logs.html index adccc95f..ab12a02f 100644 --- a/logs/templates/logs/aff_stats_logs.html +++ b/logs/templates/logs/aff_stats_logs.html @@ -47,7 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% for reversion in revision.version_set.all %} {{ reversion.object|truncatechars:20 }} - {{ reversion.object|classname }} + {{ reversion.content_type.model|truncatechars:20 }} {{ revision.user }} {{ revision.date_created }} {{ revision.comment }} From 8cfc5d66eee1980c0708ca64cb8c788b3188f3c9 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 16:43:11 +0200 Subject: [PATCH 05/25] Fix date filter in event logs --- logs/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/logs/models.py b/logs/models.py index ce2b3602..6ef7ada7 100644 --- a/logs/models.py +++ b/logs/models.py @@ -38,6 +38,7 @@ from topologie.models import Port from .forms import classes_for_action_type + class ActionsSearch: def get(self, params): """ @@ -55,10 +56,10 @@ class ActionsSearch: query &= Q(user=user) if start: - query &= Q(date_created__geq=start) + query &= Q(date_created__gte=start) if end: - query &= Q(date_created__leq=end) + query &= Q(date_created__lte=end) action_models = self.models_for_action_types(action_types) if action_models: From b5b2e281a8a5e7aa233521342aa462e567ded861 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 16:50:57 +0200 Subject: [PATCH 06/25] Fix user filter in event logs --- logs/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logs/models.py b/logs/models.py index 6ef7ada7..50471375 100644 --- a/logs/models.py +++ b/logs/models.py @@ -53,7 +53,7 @@ class ActionsSearch: query = Q() if user: - query &= Q(user=user) + query &= Q(user__pseudo=user) if start: query &= Q(date_created__gte=start) From f54eaaeb0ddc9ca58d625500a214ebfa7ab1bca4 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 17:35:48 +0200 Subject: [PATCH 07/25] Rework event logs view --- logs/models.py | 32 ++++++++++++++++++++++++- logs/templates/logs/aff_stats_logs.html | 16 +++++++++---- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/logs/models.py b/logs/models.py index 50471375..17146389 100644 --- a/logs/models.py +++ b/logs/models.py @@ -25,6 +25,7 @@ from reversion.models import Version, Revision from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import Group from django.db.models import Q +from django.apps import apps from machines.models import IpList from machines.models import Interface @@ -39,6 +40,34 @@ from topologie.models import Port from .forms import classes_for_action_type +class VersionAction: + def __init__(self, version): + self.version = version + + def name(self): + return self.version.object_repr + + def application(self): + return self.version.content_type.app_label + + def model_name(self): + return self.version.content_type.model + + def object_id(self): + return self.version.object_id + + def object_type(self): + return apps.get_model(self.application(), self.model_name()) + + +class RevisionAction: + """A Revision may group multiple Version objects together""" + def __init__(self, revision): + self.performed_by = revision.user + self.revision = revision + self.versions = [VersionAction(v) for v in revision.version_set.all()] + + class ActionsSearch: def get(self, params): """ @@ -65,7 +94,8 @@ class ActionsSearch: if action_models: query &= Q(version__content_type__model__in=action_models) - return ( + return map( + RevisionAction, Revision.objects.all() .filter(query) .select_related("user") diff --git a/logs/templates/logs/aff_stats_logs.html b/logs/templates/logs/aff_stats_logs.html index ab12a02f..c161cdbd 100644 --- a/logs/templates/logs/aff_stats_logs.html +++ b/logs/templates/logs/aff_stats_logs.html @@ -34,7 +34,6 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Edited object" %} - {% trans "Object type" %} {% trans "Edited by" as tr_edited_by %} {% include 'buttons/sort.html' with prefix='logs' col='author' text=tr_edited_by %} {% trans "Date of editing" as tr_date_of_editing %} @@ -44,11 +43,18 @@ with this program; if not, write to the Free Software Foundation, Inc., {% for revision in revisions_list %} - {% for reversion in revision.version_set.all %} + {% for version in revision.versions %} - {{ reversion.object|truncatechars:20 }} - {{ reversion.content_type.model|truncatechars:20 }} - {{ revision.user }} + + + {{ version.name }} + + + + + {{ revision.user }} + + {{ revision.date_created }} {{ revision.comment }} {% can_edit_history %} From db9e2d7c1fcab0f70aa46667b0e577712623b19e Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 17:47:09 +0200 Subject: [PATCH 08/25] Fix links in event logs view --- logs/models.py | 12 ++++++++++-- logs/templates/logs/aff_stats_logs.html | 6 +++--- logs/views.py | 2 ++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/logs/models.py b/logs/models.py index 17146389..1def1c01 100644 --- a/logs/models.py +++ b/logs/models.py @@ -67,6 +67,15 @@ class RevisionAction: self.revision = revision self.versions = [VersionAction(v) for v in revision.version_set.all()] + def id(self): + return self.revision.id + + def date_created(self): + return self.revision.date_created + + def comment(self): + return self.revision.get_comment() + class ActionsSearch: def get(self, params): @@ -94,8 +103,7 @@ class ActionsSearch: if action_models: query &= Q(version__content_type__model__in=action_models) - return map( - RevisionAction, + return ( Revision.objects.all() .filter(query) .select_related("user") diff --git a/logs/templates/logs/aff_stats_logs.html b/logs/templates/logs/aff_stats_logs.html index c161cdbd..9fb6a496 100644 --- a/logs/templates/logs/aff_stats_logs.html +++ b/logs/templates/logs/aff_stats_logs.html @@ -46,13 +46,13 @@ with this program; if not, write to the Free Software Foundation, Inc., {% for version in revision.versions %} - + {{ version.name }} - - {{ revision.user }} + + {{ revision.performed_by }} {{ revision.date_created }} diff --git a/logs/views.py b/logs/views.py index 6075d928..26cab9c5 100644 --- a/logs/views.py +++ b/logs/views.py @@ -103,6 +103,7 @@ from re2o.acl import can_view_all, can_view_app, can_edit_history, can_view from .models import ( ActionsSearch, + RevisionAction, MachineHistorySearch, UserHistory, MachineHistory, @@ -176,6 +177,7 @@ def stats_logs(request): pagination_number = GeneralOption.get_cached_value("pagination_number") revisions = re2o_paginator(request, revisions, pagination_number) + revisions = map(RevisionAction, revisions) return render(request, "logs/stats_logs.html", {"revisions_list": revisions}) return render(request, "logs/search_stats_logs.html", {"actions_form": actions_form}) From f350a24a42fc21d8451a165df6905a860a194d84 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 17:48:48 +0200 Subject: [PATCH 09/25] Handle empty objects in event logs view --- logs/templates/logs/aff_stats_logs.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/logs/templates/logs/aff_stats_logs.html b/logs/templates/logs/aff_stats_logs.html index 9fb6a496..a6dd613f 100644 --- a/logs/templates/logs/aff_stats_logs.html +++ b/logs/templates/logs/aff_stats_logs.html @@ -46,14 +46,22 @@ with this program; if not, write to the Free Software Foundation, Inc., {% for version in revision.versions %} + {% if version.object_id %} {{ version.name }} + {% else %} + {{ version.name }} + {% endif %} + {% if revision.performed_by %} {{ revision.performed_by }} + {% else %} + {{ revision.performed_by }} + {% endif %} {{ revision.date_created }} {{ revision.comment }} From 2a155032ef6c056c21409e2d54295231c35349a5 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 18:15:53 +0200 Subject: [PATCH 10/25] Attempt to add diff view in event logs --- logs/models.py | 109 ++++++++++++++++-------- logs/templates/logs/aff_stats_logs.html | 15 ++++ 2 files changed, 87 insertions(+), 37 deletions(-) diff --git a/logs/models.py b/logs/models.py index 1def1c01..bb61101f 100644 --- a/logs/models.py +++ b/logs/models.py @@ -40,43 +40,6 @@ from topologie.models import Port from .forms import classes_for_action_type -class VersionAction: - def __init__(self, version): - self.version = version - - def name(self): - return self.version.object_repr - - def application(self): - return self.version.content_type.app_label - - def model_name(self): - return self.version.content_type.model - - def object_id(self): - return self.version.object_id - - def object_type(self): - return apps.get_model(self.application(), self.model_name()) - - -class RevisionAction: - """A Revision may group multiple Version objects together""" - def __init__(self, revision): - self.performed_by = revision.user - self.revision = revision - self.versions = [VersionAction(v) for v in revision.version_set.all()] - - def id(self): - return self.revision.id - - def date_created(self): - return self.revision.date_created - - def comment(self): - return self.revision.get_comment() - - class ActionsSearch: def get(self, params): """ @@ -441,6 +404,78 @@ class History: self._last_version = version +class VersionAction(HistoryEvent): + def __init__(self, version): + self.version = version + + def is_useful(self): + # Some versions are duplicates, and don't have a reference + # to any object, so ignore them + return self.version.object_id is not None + + def name(self): + return "{} {}".format(self.model_name().title(), self.version.object_repr) + + def application(self): + return self.version.content_type.app_label + + def model_name(self): + return self.version.content_type.model + + def object_id(self): + return self.version.object_id + + def object_type(self): + return apps.get_model(self.application(), self.model_name()) + + def edits(self, hide=["password", "pwd_ntlm", "gpg_fingerprint"]): + self.previous_version = self._previous_version() + self.edited_fields = self._compute_diff(self.version, self.previous_version) + return super(VersionAction, self).edits(hide) + + def _previous_version(self): + model = self.object_type() + return next( + filter( + lambda x: x.field_dict["id"] == self.object_id() and x.revision.date_created < self.version.revision.date_created, + Version.objects.get_for_model(model).order_by("-revision__date_created") + ) + ) + + def _compute_diff(self, v1, v2): + """ + Find the edited field between two versions + :param v1: Version + :param v2: Version + :param ignoring: List, a list of fields to ignore + :return: List of field names + """ + fields = [] + + for key in v1.field_dict.keys(): + if v1.field_dict[key] != v2.field_dict[key]: + fields.append(key) + + return fields + + +class RevisionAction: + """A Revision may group multiple Version objects together""" + def __init__(self, revision): + self.performed_by = revision.user + self.revision = revision + self.versions = [VersionAction(v) for v in revision.version_set.all() if v.is_useful()] + + def id(self): + return self.revision.id + + def date_created(self): + return self.revision.date_created + + def comment(self): + return self.revision.get_comment() + + class UserHistoryEvent(HistoryEvent): def _repr(self, name, value): """ diff --git a/logs/templates/logs/aff_stats_logs.html b/logs/templates/logs/aff_stats_logs.html index a6dd613f..ffb296ca 100644 --- a/logs/templates/logs/aff_stats_logs.html +++ b/logs/templates/logs/aff_stats_logs.html @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% include 'buttons/sort.html' with prefix='logs' col='author' text=tr_edited_by %} {% trans "Date of editing" as tr_date_of_editing %} {% include 'buttons/sort.html' with prefix='logs' col='date' text=tr_date_of_editing %} + {% trans "Edited" %} {% trans "Comment" %} @@ -64,6 +65,20 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} {{ revision.date_created }} + + {% for edit in version.edits %} + {% if edit.1 is None and edit.2 is None %} + {{ edit.0 }}
+ {% elif edit.1 is None %} + {{ edit.0 }}: + {{ edit.2 }}
+ {% else %} + {{ edit.0 }}: + {{ edit.1 }} + ➔ {{ edit.2 }}
+ {% endif %} + {% endfor %} + {{ revision.comment }} {% can_edit_history %} From 3935a37c24a595bf02a30df41458971944f72b2b Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 18:23:52 +0200 Subject: [PATCH 11/25] Handle object creation in event logs --- logs/models.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/logs/models.py b/logs/models.py index bb61101f..820c7646 100644 --- a/logs/models.py +++ b/logs/models.py @@ -430,17 +430,24 @@ class VersionAction(HistoryEvent): def edits(self, hide=["password", "pwd_ntlm", "gpg_fingerprint"]): self.previous_version = self._previous_version() + + if self.previous_version is None: + return None, None, None + self.edited_fields = self._compute_diff(self.version, self.previous_version) return super(VersionAction, self).edits(hide) def _previous_version(self): model = self.object_type() - return next( - filter( - lambda x: x.field_dict["id"] == self.object_id() and x.revision.date_created < self.version.revision.date_created, - Version.objects.get_for_model(model).order_by("-revision__date_created") + try: + return next( + filter( + lambda x: x.field_dict["id"] == self.object_id() and x.revision.date_created < self.version.revision.date_created, + Version.objects.get_for_model(model).order_by("-revision__date_created") + ) ) - ) + except StopIteration: + return None def _compute_diff(self, v1, v2): """ From 42a885c599300ee08e508a6efe07fa7da9ed137e Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 18:32:39 +0200 Subject: [PATCH 12/25] Fix diff computation in event logs --- logs/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/logs/models.py b/logs/models.py index 820c7646..4cb9ba39 100644 --- a/logs/models.py +++ b/logs/models.py @@ -449,7 +449,7 @@ class VersionAction(HistoryEvent): except StopIteration: return None - def _compute_diff(self, v1, v2): + def _compute_diff(self, v1, v2, ignoring=["pwd_ntlm"]): """ Find the edited field between two versions :param v1: Version @@ -460,7 +460,7 @@ class VersionAction(HistoryEvent): fields = [] for key in v1.field_dict.keys(): - if v1.field_dict[key] != v2.field_dict[key]: + if key not in ignoring and v1.field_dict[key] != v2.field_dict[key]: fields.append(key) return fields @@ -471,7 +471,8 @@ class RevisionAction: def __init__(self, revision): self.performed_by = revision.user self.revision = revision - self.versions = [VersionAction(v) for v in revision.version_set.all() if v.is_useful()] + self.versions = [VersionAction(v) for v in revision.version_set.all()] + self.versions = filter(lambda v: v.is_useful(), self.versions) def id(self): return self.revision.id From 889a16d2ce444959747801c0f513a021c1d51808 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 16:45:37 +0000 Subject: [PATCH 13/25] Limit length of displayed diff --- logs/templates/logs/aff_stats_logs.html | 6 +++--- logs/views.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/logs/templates/logs/aff_stats_logs.html b/logs/templates/logs/aff_stats_logs.html index ffb296ca..3ba37958 100644 --- a/logs/templates/logs/aff_stats_logs.html +++ b/logs/templates/logs/aff_stats_logs.html @@ -71,11 +71,11 @@ with this program; if not, write to the Free Software Foundation, Inc., {{ edit.0 }}
{% elif edit.1 is None %} {{ edit.0 }}: - {{ edit.2 }}
+ {{ edit.2|truncatechars:50 }}
{% else %} {{ edit.0 }}: - {{ edit.1 }} - ➔ {{ edit.2 }}
+ {{ edit.1|truncatechars:50 }} + ➔ {{ edit.2|truncatechars:50 }}
{% endif %} {% endfor %} diff --git a/logs/views.py b/logs/views.py index 26cab9c5..b71187aa 100644 --- a/logs/views.py +++ b/logs/views.py @@ -177,7 +177,10 @@ def stats_logs(request): pagination_number = GeneralOption.get_cached_value("pagination_number") revisions = re2o_paginator(request, revisions, pagination_number) - revisions = map(RevisionAction, revisions) + + # Only do this now so it's not applied to objects which aren't displayed + # It can take a bit of time because it has to compute the diff of each version + revisions.object_list = [RevisionAction(r) for r in revisions.object_list] return render(request, "logs/stats_logs.html", {"revisions_list": revisions}) return render(request, "logs/search_stats_logs.html", {"actions_form": actions_form}) From e537143efcfe5690db52a5ee854511b22563442e Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 16:50:50 +0000 Subject: [PATCH 14/25] Add missing translations --- api/locale/fr/LC_MESSAGES/django.po | 2 +- cotisations/locale/fr/LC_MESSAGES/django.po | 2 +- logs/locale/fr/LC_MESSAGES/django.po | 194 ++++++++++++-------- machines/locale/fr/LC_MESSAGES/django.po | 2 +- multi_op/locale/fr/LC_MESSAGES/django.po | 2 +- preferences/locale/fr/LC_MESSAGES/django.po | 2 +- re2o/locale/fr/LC_MESSAGES/django.po | 2 +- search/locale/fr/LC_MESSAGES/django.po | 2 +- templates/locale/fr/LC_MESSAGES/django.po | 2 +- tickets/locale/fr/LC_MESSAGES/django.po | 2 +- topologie/locale/fr/LC_MESSAGES/django.po | 2 +- users/locale/fr/LC_MESSAGES/django.po | 2 +- 12 files changed, 125 insertions(+), 91 deletions(-) diff --git a/api/locale/fr/LC_MESSAGES/django.po b/api/locale/fr/LC_MESSAGES/django.po index 51857377..34694672 100644 --- a/api/locale/fr/LC_MESSAGES/django.po +++ b/api/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2019-01-07 01:37+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/cotisations/locale/fr/LC_MESSAGES/django.po b/cotisations/locale/fr/LC_MESSAGES/django.po index 782c72b7..2a420c4c 100644 --- a/cotisations/locale/fr/LC_MESSAGES/django.po +++ b/cotisations/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language: fr_FR\n" diff --git a/logs/locale/fr/LC_MESSAGES/django.po b/logs/locale/fr/LC_MESSAGES/django.po index 039ba799..0ca22d28 100644 --- a/logs/locale/fr/LC_MESSAGES/django.po +++ b/logs/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2018-06-23 16:01+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -34,48 +34,87 @@ msgstr "" msgid "You don't have the right to view this application." msgstr "Vous n'avez pas le droit de voir cette application." -#: logs/forms.py:29 logs/templates/logs/machine_history.html:35 +#: logs/forms.py:40 logs/templates/logs/sidebar.html:53 +msgid "Users" +msgstr "Utilisateurs" + +#: logs/forms.py:41 +msgid "Machines" +msgstr "Machines" + +#: logs/forms.py:42 +msgid "Subscription" +msgstr "Cotisations" + +#: logs/forms.py:43 +msgid "Whitelists" +msgstr "Accès gracieux" + +#: logs/forms.py:44 +msgid "Bans" +msgstr "Bannissements" + +#: logs/forms.py:45 logs/views.py:424 +msgid "Topology" +msgstr "Topologie" + +#: logs/forms.py:46 +msgid "All" +msgstr "Tous" + +#: logs/forms.py:50 logs/templates/logs/machine_history.html:35 msgid "IPv4" msgstr "IPv4" -#: logs/forms.py:30 logs/templates/logs/machine_history.html:36 +#: logs/forms.py:51 logs/templates/logs/machine_history.html:36 msgid "MAC address" msgstr "Adresse MAC" -#: logs/forms.py:38 logs/templates/logs/search_machine_history.html:38 -msgid "Search" -msgstr "Rechercher" +#: logs/forms.py:101 logs/templates/logs/detailed_history.html:38 +msgid "Performed by" +msgstr "Effectué(e) par" -#: logs/forms.py:42 -msgid "Search type" -msgstr "Type de recherche" +#: logs/forms.py:106 +msgid "Action type" +msgstr "Type d'action" -#: logs/forms.py:45 logs/templates/logs/machine_history.html:37 +#: logs/forms.py:112 logs/forms.py:135 +#: logs/templates/logs/machine_history.html:37 msgid "Start date" msgstr "Date de début" -#: logs/forms.py:46 logs/templates/logs/machine_history.html:38 +#: logs/forms.py:113 logs/forms.py:136 +#: logs/templates/logs/machine_history.html:38 msgid "End date" msgstr "Date de fin" -#: logs/models.py:260 logs/models.py:364 logs/models.py:397 logs/models.py:480 -#: logs/models.py:572 logs/models.py:610 +#: logs/forms.py:128 logs/templates/logs/search_machine_history.html:38 +#: logs/templates/logs/search_stats_logs.html:38 +msgid "Search" +msgstr "Rechercher" + +#: logs/forms.py:132 +msgid "Search type" +msgstr "Type de recherche" + +#: logs/models.py:314 logs/models.py:498 logs/models.py:531 logs/models.py:614 +#: logs/models.py:706 logs/models.py:744 msgid "None" msgstr "Aucun(e)" -#: logs/models.py:374 logs/models.py:393 logs/models.py:407 logs/models.py:552 -#: logs/models.py:597 logs/models.py:602 logs/models.py:607 logs/models.py:617 -#: logs/views.py:592 +#: logs/models.py:508 logs/models.py:527 logs/models.py:541 logs/models.py:686 +#: logs/models.py:731 logs/models.py:736 logs/models.py:741 logs/models.py:751 +#: logs/views.py:604 msgid "Deleted" msgstr "Supprimé(e)" -#: logs/models.py:381 logs/models.py:386 +#: logs/models.py:515 logs/models.py:520 #: logs/templates/logs/detailed_history.html:52 #: logs/templates/logs/machine_history.html:55 msgid "Unknown" msgstr "Inconnu(e)" -#: logs/models.py:605 +#: logs/models.py:739 msgid "No name" msgstr "Sans nom" @@ -84,25 +123,31 @@ msgid "Edited object" msgstr "Objet modifié" #: logs/templates/logs/aff_stats_logs.html:37 -#: logs/templates/logs/aff_stats_models.html:32 -msgid "Object type" -msgstr "Type d'objet" - -#: logs/templates/logs/aff_stats_logs.html:38 msgid "Edited by" msgstr "Modifié par" -#: logs/templates/logs/aff_stats_logs.html:40 +#: logs/templates/logs/aff_stats_logs.html:39 msgid "Date of editing" msgstr "Date de modification" +#: logs/templates/logs/aff_stats_logs.html:41 +#: logs/templates/logs/detailed_history.html:39 +msgid "Edited" +msgstr "Modifié" + #: logs/templates/logs/aff_stats_logs.html:42 #: logs/templates/logs/detailed_history.html:40 #: logs/templates/logs/machine_history.html:39 msgid "Comment" msgstr "Commentaire" -#: logs/templates/logs/aff_stats_logs.html:58 +#: logs/templates/logs/aff_stats_logs.html:51 +#: logs/templates/logs/detailed_history.html:28 +#: logs/templates/logs/detailed_history.html:85 +msgid "History" +msgstr "Historique" + +#: logs/templates/logs/aff_stats_logs.html:87 #: logs/templates/logs/aff_summary.html:62 #: logs/templates/logs/aff_summary.html:85 #: logs/templates/logs/aff_summary.html:104 @@ -116,6 +161,10 @@ msgstr "Annuler" msgid "Statistics of the set %(key)s" msgstr "Statistiques de l'ensemble %(key)s" +#: logs/templates/logs/aff_stats_models.html:32 +msgid "Object type" +msgstr "Type d'objet" + #: logs/templates/logs/aff_stats_models.html:33 msgid "Number of stored entries" msgstr "Nombre d'entrées enregistrées" @@ -199,23 +248,11 @@ msgstr "" msgid "Confirm" msgstr "Confirmer" -#: logs/templates/logs/detailed_history.html:28 -#: logs/templates/logs/detailed_history.html:85 -msgid "History" -msgstr "Historique" - #: logs/templates/logs/detailed_history.html:31 +#, python-format msgid "History of %(title)s" msgstr "Historique de %(title)s" -#: logs/templates/logs/detailed_history.html:38 -msgid "Performed by" -msgstr "Effectué(e) par" - -#: logs/templates/logs/detailed_history.html:39 -msgid "Edited" -msgstr "Modifié" - #: logs/templates/logs/detailed_history.html:75 msgid "No event" msgstr "Aucun évènement" @@ -232,7 +269,7 @@ msgid "Statistics" msgstr "Statistiques" #: logs/templates/logs/index.html:32 logs/templates/logs/stats_logs.html:32 -#: logs/views.py:427 +#: logs/views.py:439 msgid "Actions performed" msgstr "Actions effectuées" @@ -257,6 +294,11 @@ msgstr "Aucun résultat" msgid "Search machine history" msgstr "Rechercher l'historique des machines" +#: logs/templates/logs/search_stats_logs.html:27 +#: logs/templates/logs/search_stats_logs.html:32 +msgid "Search events" +msgstr "Recherche les évènements" + #: logs/templates/logs/sidebar.html:33 msgid "Summary" msgstr "Résumé" @@ -277,10 +319,6 @@ msgstr "Base de données" msgid "Wiring actions" msgstr "Actions de câblage" -#: logs/templates/logs/sidebar.html:53 -msgid "Users" -msgstr "Utilisateurs" - #: logs/templates/logs/sidebar.html:57 msgid "Machine history" msgstr "Historique des machines" @@ -297,138 +335,134 @@ msgstr "Statistiques sur la base de données" msgid "Statistics about users" msgstr "Statistiques sur les utilisateurs" -#: logs/views.py:184 +#: logs/views.py:196 msgid "Nonexistent revision." msgstr "Révision inexistante." -#: logs/views.py:187 +#: logs/views.py:199 msgid "The action was deleted." msgstr "L'action a été supprimée." -#: logs/views.py:228 +#: logs/views.py:240 msgid "Category" msgstr "Catégorie" -#: logs/views.py:229 +#: logs/views.py:241 msgid "Number of users (members and clubs)" msgstr "Nombre d'utilisateurs (adhérents et clubs)" -#: logs/views.py:230 +#: logs/views.py:242 msgid "Number of members" msgstr "Nombre d'adhérents" -#: logs/views.py:231 +#: logs/views.py:243 msgid "Number of clubs" msgstr "Nombre de clubs" -#: logs/views.py:235 +#: logs/views.py:247 msgid "Activated users" msgstr "Utilisateurs activés" -#: logs/views.py:241 +#: logs/views.py:253 msgid "Disabled users" msgstr "Utilisateurs désactivés" -#: logs/views.py:247 +#: logs/views.py:259 msgid "Archived users" msgstr "Utilisateurs archivés" -#: logs/views.py:253 +#: logs/views.py:265 msgid "Fully archived users" msgstr "Utilisateurs complètement archivés" -#: logs/views.py:263 +#: logs/views.py:275 msgid "Not yet active users" msgstr "Utilisateurs pas encore actifs" -#: logs/views.py:273 +#: logs/views.py:285 msgid "Contributing members" msgstr "Adhérents cotisants" -#: logs/views.py:279 +#: logs/views.py:291 msgid "Users benefiting from a connection" msgstr "Utilisateurs bénéficiant d'une connexion" -#: logs/views.py:285 +#: logs/views.py:297 msgid "Banned users" msgstr "Utilisateurs bannis" -#: logs/views.py:291 +#: logs/views.py:303 msgid "Users benefiting from a free connection" msgstr "Utilisateurs bénéficiant d'une connexion gratuite" -#: logs/views.py:297 +#: logs/views.py:309 msgid "Users with a confirmed email" msgstr "Utilisateurs ayant un mail confirmé" -#: logs/views.py:303 +#: logs/views.py:315 msgid "Users with an unconfirmed email" msgstr "Utilisateurs ayant un mail non confirmé" -#: logs/views.py:309 +#: logs/views.py:321 msgid "Users pending email confirmation" msgstr "Utilisateurs en attente de confirmation du mail" -#: logs/views.py:315 +#: logs/views.py:327 msgid "Active interfaces (with access to the network)" msgstr "Interfaces actives (ayant accès au réseau)" -#: logs/views.py:329 +#: logs/views.py:341 msgid "Active interfaces assigned IPv4" msgstr "Interfaces actives assignées IPv4" -#: logs/views.py:346 +#: logs/views.py:358 msgid "IP range" msgstr "Plage d'IP" -#: logs/views.py:347 +#: logs/views.py:359 msgid "VLAN" msgstr "VLAN" -#: logs/views.py:348 +#: logs/views.py:360 msgid "Total number of IP addresses" msgstr "Nombre total d'adresses IP" -#: logs/views.py:349 +#: logs/views.py:361 msgid "Number of assigned IP addresses" msgstr "Nombre d'adresses IP assignées" -#: logs/views.py:350 +#: logs/views.py:362 msgid "Number of IP address assigned to an activated machine" msgstr "Nombre d'adresses IP assignées à une machine activée" -#: logs/views.py:351 +#: logs/views.py:363 msgid "Number of unassigned IP addresses" msgstr "Nombre d'adresses IP non assignées" -#: logs/views.py:366 +#: logs/views.py:378 msgid "Users (members and clubs)" msgstr "Utilisateurs (adhérents et clubs)" -#: logs/views.py:412 -msgid "Topology" -msgstr "Topologie" - -#: logs/views.py:428 +#: logs/views.py:440 msgid "Number of actions" msgstr "Nombre d'actions" -#: logs/views.py:453 +#: logs/views.py:465 msgid "rights" msgstr "droits" -#: logs/views.py:482 +#: logs/views.py:494 msgid "actions" msgstr "actions" -#: logs/views.py:532 logs/views.py:583 +#: logs/views.py:544 logs/views.py:595 msgid "Nonexistent entry." msgstr "Entrée inexistante." -#: logs/views.py:545 +#: logs/views.py:557 msgid "You don't have the right to access this menu." msgstr "Vous n'avez pas le droit d'accéder à ce menu." -#: logs/views.py:570 logs/views.py:626 +#: logs/views.py:582 logs/views.py:638 msgid "No model found." msgstr "Aucun modèle trouvé." diff --git a/machines/locale/fr/LC_MESSAGES/django.po b/machines/locale/fr/LC_MESSAGES/django.po index e8abd822..a5ebf1e1 100644 --- a/machines/locale/fr/LC_MESSAGES/django.po +++ b/machines/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2018-06-23 16:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/multi_op/locale/fr/LC_MESSAGES/django.po b/multi_op/locale/fr/LC_MESSAGES/django.po index 3439441d..ea4fe4c9 100644 --- a/multi_op/locale/fr/LC_MESSAGES/django.po +++ b/multi_op/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2019-11-16 00:22+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index 8423e0cb..9f6a2172 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2018-06-24 15:54+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/re2o/locale/fr/LC_MESSAGES/django.po b/re2o/locale/fr/LC_MESSAGES/django.po index 7bba1fe6..5f757710 100644 --- a/re2o/locale/fr/LC_MESSAGES/django.po +++ b/re2o/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po index c69e0148..ec263386 100644 --- a/search/locale/fr/LC_MESSAGES/django.po +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2018-06-24 20:10+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/templates/locale/fr/LC_MESSAGES/django.po b/templates/locale/fr/LC_MESSAGES/django.po index 5e7b31d0..8d5b6ecb 100644 --- a/templates/locale/fr/LC_MESSAGES/django.po +++ b/templates/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/tickets/locale/fr/LC_MESSAGES/django.po b/tickets/locale/fr/LC_MESSAGES/django.po index 42c96960..aee6fcff 100644 --- a/tickets/locale/fr/LC_MESSAGES/django.po +++ b/tickets/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2019-11-16 00:35+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/topologie/locale/fr/LC_MESSAGES/django.po b/topologie/locale/fr/LC_MESSAGES/django.po index 5bba5b46..43a96241 100644 --- a/topologie/locale/fr/LC_MESSAGES/django.po +++ b/topologie/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2018-06-25 14:53+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 6b57f34b..f1a9177e 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 21:25+0200\n" +"POT-Creation-Date: 2020-04-24 18:45+0200\n" "PO-Revision-Date: 2018-06-27 23:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" From acb4dea1b4196444fa06027f47d10ab172a2cf65 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 20:09:18 +0200 Subject: [PATCH 15/25] Work on improving performance when filtering logs --- logs/models.py | 97 +++++++++++++++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/logs/models.py b/logs/models.py index 4cb9ba39..49404690 100644 --- a/logs/models.py +++ b/logs/models.py @@ -198,9 +198,10 @@ class MachineHistorySearch: except IpList.DoesNotExist: return [] - return filter( - lambda x: x.field_dict["ipv4_id"] == ip_id, - Version.objects.get_for_model(Interface).order_by("revision__date_created") + return ( + Version.objects.get_for_model(Interface) + .filter(serialized_data__icontains='"ipv4": {}'.format(ip_id)) + .order_by("revision__date_created") ) def _get_interfaces_for_mac(self, mac): @@ -209,9 +210,10 @@ class MachineHistorySearch: :return: An iterable object with the Version objects of Interfaces with the given MAC address """ - return filter( - lambda x: str(x.field_dict["mac_address"]) == mac, - Version.objects.get_for_model(Interface).order_by("revision__date_created") + return ( + Version.objects.get_for_model(Interface) + .filter(serialized_data__icontains='"mac_address": "{}"'.format(mac)) + .order_by("revision__date_created") ) def _get_machines_for_interface(self, interface): @@ -221,9 +223,10 @@ class MachineHistorySearch: which the given interface was attributed """ machine_id = interface.field_dict["machine_id"] - return filter( - lambda x: x.field_dict["id"] == machine_id, - Version.objects.get_for_model(Machine).order_by("revision__date_created") + return ( + Version.objects.get_for_model(Machine) + .filter(serialized_data__icontains='"pk": {}'.format(machine_id)) + .order_by("revision__date_created") ) def _get_user_for_machine(self, machine): @@ -355,9 +358,10 @@ class History: # Get all the versions for this instance, with the oldest first self._last_version = None - interface_versions = filter( - lambda x: x.field_dict["id"] == instance_id, - Version.objects.get_for_model(model).order_by("revision__date_created") + interface_versions = ( + Version.objects.get_for_model(model) + .filter(serialized_data__icontains='"pk": {}'.format(instance_id)) + .order_by("revision__date_created") ) for version in interface_versions: @@ -440,11 +444,18 @@ class VersionAction(HistoryEvent): def _previous_version(self): model = self.object_type() try: - return next( - filter( - lambda x: x.field_dict["id"] == self.object_id() and x.revision.date_created < self.version.revision.date_created, - Version.objects.get_for_model(model).order_by("-revision__date_created") + query = ( + Q( + serialized_data__icontains='"pk": {}'.format(self.object_id()) ) + & Q( + revision__date_created_lt=self.version.revision.date_created + ) + ) + return next( + Version.objects.get_for_model(model) + .filter(query) + .order_by("-revision__date_created") ) except StopIteration: return None @@ -584,21 +595,29 @@ class UserHistory(History): self.events = [] # Try to find an Adherent object - adherents = filter( - lambda x: x.field_dict["user_ptr_id"] == user_id, + # If it exists, its id will be the same as the user's + adherents = ( Version.objects.get_for_model(Adherent) + .filter(serialized_data__icontains='"pk": {}'.format(user_id)) ) - obj = next(adherents, None) - model = Adherent + try: + obj = adherents[0] + model = Adherent + except IndexError: + obj = None # Fallback on a Club if obj is None: - clubs = filter( - lambda x: x.field_dict["user_ptr_id"] == user_id, + clubs = ( Version.objects.get_for_model(Club) + .filter(serialized_data__icontains='"pk": {}'.format(user_id)) ) - obj = next(clubs, None) - model = Club + + try: + obj = clubs[0] + model = Club + except IndexError: + obj = None # If nothing was found, abort if obj is None: @@ -606,9 +625,10 @@ class UserHistory(History): # Add in "related" elements the list of Machine objects # that were once owned by this user - self.related = filter( - lambda x: x.field_dict["user_id"] == user_id, - Version.objects.get_for_model(Machine).order_by("-revision__date_created") + self.related = ( + Version.objects.get_for_model(Machine) + .filter(serialized_data__icontains='"user": {}'.format(user_id)) + .order_by("-revision__date_created") ) self.related = [RelatedHistory( m.field_dict["name"] or _("None"), @@ -618,9 +638,10 @@ class UserHistory(History): # Get all the versions for this user, with the oldest first self._last_version = None - user_versions = filter( - lambda x: x.field_dict["id"] == user_id, - Version.objects.get_for_model(User).order_by("revision__date_created") + user_versions = ( + Version.objects.get_for_model(User) + .filter(serialized_data__icontains='"pk": {}'.format(user_id)) + .order_by("revision__date_created") ) for version in user_versions: @@ -631,9 +652,10 @@ class UserHistory(History): # Do the same thing for the Adherent of Club self._last_version = None - obj_versions = filter( - lambda x: x.field_dict["id"] == user_id, - Version.objects.get_for_model(model).order_by("revision__date_created") + obj_versions = ( + Version.objects.get_for_model(model) + .filter(serialized_data__icontains='"pk": {}'.format(user_id)) + .order_by("revision__date_created") ) for version in obj_versions: @@ -696,10 +718,11 @@ class MachineHistory(History): def get(self, machine_id): # Add as "related" histories the list of Interface objects # that were once assigned to this machine - self.related = list(filter( - lambda x: x.field_dict["machine_id"] == machine_id, - Version.objects.get_for_model(Interface).order_by("-revision__date_created") - )) + self.related = list( + Version.objects.get_for_model(Interface) + .filter(serialized_data__icontains='"machine": {}'.format(machine_id)) + .order_by("-revision__date_created") + ) # Create RelatedHistory objects and remove duplicates self.related = [RelatedHistory( From ec59487e7253be5146c6f2f49008035b1d3b3303 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 18:15:34 +0000 Subject: [PATCH 16/25] Fix bugs introduced while improving performance --- logs/models.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/logs/models.py b/logs/models.py index 49404690..f5796f93 100644 --- a/logs/models.py +++ b/logs/models.py @@ -449,15 +449,13 @@ class VersionAction(HistoryEvent): serialized_data__icontains='"pk": {}'.format(self.object_id()) ) & Q( - revision__date_created_lt=self.version.revision.date_created + revision__date_created__lt=self.version.revision.date_created ) ) - return next( - Version.objects.get_for_model(model) + return (Version.objects.get_for_model(model) .filter(query) - .order_by("-revision__date_created") - ) - except StopIteration: + .order_by("-revision__date_created")[0]) + except Exception as e: return None def _compute_diff(self, v1, v2, ignoring=["pwd_ntlm"]): From cbc26abdaff8b2203d23e8709ce2244deab7c539 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 23:00:16 +0200 Subject: [PATCH 17/25] Make logs queries more efficient --- logs/models.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/logs/models.py b/logs/models.py index f5796f93..942a76a4 100644 --- a/logs/models.py +++ b/logs/models.py @@ -200,7 +200,7 @@ class MachineHistorySearch: return ( Version.objects.get_for_model(Interface) - .filter(serialized_data__icontains='"ipv4": {}'.format(ip_id)) + .filter(serialized_data__contains='"ipv4": {}'.format(ip_id)) .order_by("revision__date_created") ) @@ -212,7 +212,7 @@ class MachineHistorySearch: """ return ( Version.objects.get_for_model(Interface) - .filter(serialized_data__icontains='"mac_address": "{}"'.format(mac)) + .filter(serialized_data__contains='"mac_address": "{}"'.format(mac)) .order_by("revision__date_created") ) @@ -225,7 +225,7 @@ class MachineHistorySearch: machine_id = interface.field_dict["machine_id"] return ( Version.objects.get_for_model(Machine) - .filter(serialized_data__icontains='"pk": {}'.format(machine_id)) + .filter(serialized_data__contains='"pk": {}'.format(machine_id)) .order_by("revision__date_created") ) @@ -360,7 +360,7 @@ class History: self._last_version = None interface_versions = ( Version.objects.get_for_model(model) - .filter(serialized_data__icontains='"pk": {}'.format(instance_id)) + .filter(serialized_data__contains='"pk": {}'.format(instance_id)) .order_by("revision__date_created") ) @@ -446,7 +446,7 @@ class VersionAction(HistoryEvent): try: query = ( Q( - serialized_data__icontains='"pk": {}'.format(self.object_id()) + serialized_data__contains='"pk": {}'.format(self.object_id()) ) & Q( revision__date_created__lt=self.version.revision.date_created @@ -596,7 +596,7 @@ class UserHistory(History): # If it exists, its id will be the same as the user's adherents = ( Version.objects.get_for_model(Adherent) - .filter(serialized_data__icontains='"pk": {}'.format(user_id)) + .filter(serialized_data__contains='"pk": {}'.format(user_id)) ) try: obj = adherents[0] @@ -608,7 +608,7 @@ class UserHistory(History): if obj is None: clubs = ( Version.objects.get_for_model(Club) - .filter(serialized_data__icontains='"pk": {}'.format(user_id)) + .filter(serialized_data__contains='"pk": {}'.format(user_id)) ) try: @@ -625,7 +625,7 @@ class UserHistory(History): # that were once owned by this user self.related = ( Version.objects.get_for_model(Machine) - .filter(serialized_data__icontains='"user": {}'.format(user_id)) + .filter(serialized_data__contains='"user": {}'.format(user_id)) .order_by("-revision__date_created") ) self.related = [RelatedHistory( @@ -638,7 +638,7 @@ class UserHistory(History): self._last_version = None user_versions = ( Version.objects.get_for_model(User) - .filter(serialized_data__icontains='"pk": {}'.format(user_id)) + .filter(serialized_data__contains='"pk": {}'.format(user_id)) .order_by("revision__date_created") ) @@ -652,7 +652,7 @@ class UserHistory(History): self._last_version = None obj_versions = ( Version.objects.get_for_model(model) - .filter(serialized_data__icontains='"pk": {}'.format(user_id)) + .filter(serialized_data__contains='"pk": {}'.format(user_id)) .order_by("revision__date_created") ) @@ -718,7 +718,7 @@ class MachineHistory(History): # that were once assigned to this machine self.related = list( Version.objects.get_for_model(Interface) - .filter(serialized_data__icontains='"machine": {}'.format(machine_id)) + .filter(serialized_data__contains='"machine": {}'.format(machine_id)) .order_by("-revision__date_created") ) From 364f89d3d41fd2b9882057851e5e289b7efdfa7f Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 23:02:55 +0200 Subject: [PATCH 18/25] Auto format MAC addresses inmachine history search --- logs/models.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/logs/models.py b/logs/models.py index 942a76a4..2641ed24 100644 --- a/logs/models.py +++ b/logs/models.py @@ -26,6 +26,7 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import Group from django.db.models import Q from django.apps import apps +from netaddr import EUI from machines.models import IpList from machines.models import Interface @@ -149,9 +150,16 @@ class MachineHistorySearch: self.events = [] if search_type == "ip": - return self._get_by_ip(search)[::-1] + try: + return self._get_by_ip(search)[::-1] + except: + pass elif search_type == "mac": - return self._get_by_mac(search)[::-1] + try: + search = EUI(search) + return self._get_by_mac(search)[::-1] + except: + pass return None From a659e2fcbdbcdd178c8323d414c7b85bd68d7fbe Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 23:09:15 +0200 Subject: [PATCH 19/25] Fix searching for malformated IP or MAC addresses in machine history search --- logs/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/logs/models.py b/logs/models.py index 2641ed24..d139b9da 100644 --- a/logs/models.py +++ b/logs/models.py @@ -27,6 +27,7 @@ from django.contrib.auth.models import Group from django.db.models import Q from django.apps import apps from netaddr import EUI +macaddress.fields import default_dialect from machines.models import IpList from machines.models import Interface @@ -156,12 +157,12 @@ class MachineHistorySearch: pass elif search_type == "mac": try: - search = EUI(search) + search = EUI(search, dialect=default_dialect()) return self._get_by_mac(search)[::-1] except: pass - return None + return [] def _add_revision(self, user, machine, interface): """ From 46822c1169499fc8077c8ae8154958c683b46a43 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 23:14:08 +0200 Subject: [PATCH 20/25] Fix import error in logs/models.py --- logs/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logs/models.py b/logs/models.py index d139b9da..8446b329 100644 --- a/logs/models.py +++ b/logs/models.py @@ -27,7 +27,7 @@ from django.contrib.auth.models import Group from django.db.models import Q from django.apps import apps from netaddr import EUI -macaddress.fields import default_dialect +from macaddress.fields import default_dialect from machines.models import IpList from machines.models import Interface From 9bd080f85378403413141bbf5a19336c830dd239 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 23:34:40 +0200 Subject: [PATCH 21/25] Fix event type filter in postgre --- logs/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logs/models.py b/logs/models.py index 8446b329..b5be183d 100644 --- a/logs/models.py +++ b/logs/models.py @@ -87,7 +87,7 @@ class ActionsSearch: if c is None: return None - classes += c + classes += c.lower() return classes From 0c7689adde4c4e98918f3cf33b48d7f9b507b26a Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 23:36:50 +0200 Subject: [PATCH 22/25] Fix fix event type filter in postgre --- logs/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logs/models.py b/logs/models.py index b5be183d..7af5c11c 100644 --- a/logs/models.py +++ b/logs/models.py @@ -87,7 +87,7 @@ class ActionsSearch: if c is None: return None - classes += c.lower() + classes += list(map(str.lower, c)) return classes From 301a8f0182f8289211b2b2ba232ddb9764fb5c21 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 23:51:57 +0200 Subject: [PATCH 23/25] Don't assume an event's userfulness when showing logs --- logs/models.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/logs/models.py b/logs/models.py index 7af5c11c..292eead1 100644 --- a/logs/models.py +++ b/logs/models.py @@ -421,11 +421,6 @@ class VersionAction(HistoryEvent): def __init__(self, version): self.version = version - def is_useful(self): - # Some versions are duplicates, and don't have a reference - # to any object, so ignore them - return self.version.object_id is not None - def name(self): return "{} {}".format(self.model_name().title(), self.version.object_repr) @@ -490,7 +485,6 @@ class RevisionAction: self.performed_by = revision.user self.revision = revision self.versions = [VersionAction(v) for v in revision.version_set.all()] - self.versions = filter(lambda v: v.is_useful(), self.versions) def id(self): return self.revision.id From 00b3604aa378220a45c1b08da7a4f029beb58c0f Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sat, 25 Apr 2020 00:13:00 +0200 Subject: [PATCH 24/25] Improve description for events with None objects --- logs/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logs/models.py b/logs/models.py index 292eead1..acf8771b 100644 --- a/logs/models.py +++ b/logs/models.py @@ -422,7 +422,7 @@ class VersionAction(HistoryEvent): self.version = version def name(self): - return "{} {}".format(self.model_name().title(), self.version.object_repr) + return self.version._object_cache or self.version.object_repr def application(self): return self.version.content_type.app_label From f7c5c801d5709765d6ac84cac8e56e69f1ae24ca Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Fri, 24 Apr 2020 22:40:21 +0000 Subject: [PATCH 25/25] Add username autocompletion in event filter view --- logs/forms.py | 4 ++-- logs/templates/logs/search_stats_logs.html | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/logs/forms.py b/logs/forms.py index ae281e98..30780d4a 100644 --- a/logs/forms.py +++ b/logs/forms.py @@ -97,9 +97,9 @@ def classes_for_action_type(action_type): class ActionsSearchForm(Form): """The form for a simple search""" - u = forms.CharField( + u = forms.ModelChoiceField( label=_("Performed by"), - max_length=100, + queryset=users.models.User.objects.all(), required=False, ) t = forms.MultipleChoiceField( diff --git a/logs/templates/logs/search_stats_logs.html b/logs/templates/logs/search_stats_logs.html index 5faa8066..5211d336 100644 --- a/logs/templates/logs/search_stats_logs.html +++ b/logs/templates/logs/search_stats_logs.html @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load massive_bootstrap_form %} {% load i18n %} {% block title %}{% trans "Search events" %}{% endblock %} @@ -31,10 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

{% trans "Search events" %}

- {% bootstrap_field actions_form.u %} - {% include 'buttons/multiple_checkbox_alt.html' with field=actions_form.t %} - {% bootstrap_field actions_form.s %} - {% bootstrap_field actions_form.e %} + {% massive_bootstrap_form actions_form 'u' %} {% trans "Search" as tr_search %} {% bootstrap_button tr_search button_type="submit" icon="search" %}