8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-23 15:33:45 +00:00

Merge branch 'filter_event_logs' into 'dev'

Add ability to filter event logs and make them nicer

See merge request re2o/re2o!519
This commit is contained in:
chirac 2020-04-25 01:50:37 +02:00
commit 63f09d7867
17 changed files with 516 additions and 149 deletions

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2019-01-07 01:37+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language: fr_FR\n" "Language: fr_FR\n"

View file

@ -25,15 +25,105 @@ from django.forms import Form
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from re2o.base import get_input_formats_help_text 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
import topologie.models
import users.models
CHOICES_ACTION_TYPE = (
("users", _("Users")),
("machines", _("Machines")),
("subscriptions", _("Subscription")),
("whitelists", _("Whitelists")),
("bans", _("Bans")),
("topology", _("Topology")),
("all", _("All")),
)
CHOICES_TYPE = ( CHOICES_TYPE = (
("ip", _("IPv4")), ("ip", _("IPv4")),
("mac", _("MAC address")), ("mac", _("MAC address")),
) )
def all_classes(module):
classes = []
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj):
classes.append(name)
return classes
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__
]
if action_type == "machines":
return [
machines.models.Machine.__name__,
machines.models.Interface.__name__
]
if action_type == "subscriptions":
return all_classes(cotisations.models)
if action_type == "whitelists":
return [users.models.Whitelist.__name__]
if action_type == "bans":
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):
"""The form for a simple search"""
u = forms.ModelChoiceField(
label=_("Performed by"),
queryset=users.models.User.objects.all(),
required=False,
)
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(ActionsSearchForm, 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): class MachineHistorySearchForm(Form):
"""The form for a simple search""" """The form for a simple search"""
q = forms.CharField( q = forms.CharField(
label=_("Search"), label=_("Search"),
max_length=100, max_length=100,

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-06-23 16:01+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"
@ -34,48 +34,87 @@ msgstr ""
msgid "You don't have the right to view this application." msgid "You don't have the right to view this application."
msgstr "Vous n'avez pas le droit de voir cette 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" msgid "IPv4"
msgstr "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" msgid "MAC address"
msgstr "Adresse MAC" msgstr "Adresse MAC"
#: logs/forms.py:38 logs/templates/logs/search_machine_history.html:38 #: logs/forms.py:101 logs/templates/logs/detailed_history.html:38
msgid "Search" msgid "Performed by"
msgstr "Rechercher" msgstr "Effectué(e) par"
#: logs/forms.py:42 #: logs/forms.py:106
msgid "Search type" msgid "Action type"
msgstr "Type de recherche" 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" msgid "Start date"
msgstr "Date de début" 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" msgid "End date"
msgstr "Date de fin" msgstr "Date de fin"
#: logs/models.py:260 logs/models.py:364 logs/models.py:397 logs/models.py:480 #: logs/forms.py:128 logs/templates/logs/search_machine_history.html:38
#: logs/models.py:572 logs/models.py:610 #: 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" msgid "None"
msgstr "Aucun(e)" msgstr "Aucun(e)"
#: logs/models.py:374 logs/models.py:393 logs/models.py:407 logs/models.py:552 #: logs/models.py:508 logs/models.py:527 logs/models.py:541 logs/models.py:686
#: logs/models.py:597 logs/models.py:602 logs/models.py:607 logs/models.py:617 #: logs/models.py:731 logs/models.py:736 logs/models.py:741 logs/models.py:751
#: logs/views.py:592 #: logs/views.py:604
msgid "Deleted" msgid "Deleted"
msgstr "Supprimé(e)" 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/detailed_history.html:52
#: logs/templates/logs/machine_history.html:55 #: logs/templates/logs/machine_history.html:55
msgid "Unknown" msgid "Unknown"
msgstr "Inconnu(e)" msgstr "Inconnu(e)"
#: logs/models.py:605 #: logs/models.py:739
msgid "No name" msgid "No name"
msgstr "Sans nom" msgstr "Sans nom"
@ -84,25 +123,31 @@ msgid "Edited object"
msgstr "Objet modifié" msgstr "Objet modifié"
#: logs/templates/logs/aff_stats_logs.html:37 #: 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" msgid "Edited by"
msgstr "Modifié par" msgstr "Modifié par"
#: logs/templates/logs/aff_stats_logs.html:40 #: logs/templates/logs/aff_stats_logs.html:39
msgid "Date of editing" msgid "Date of editing"
msgstr "Date de modification" 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/aff_stats_logs.html:42
#: logs/templates/logs/detailed_history.html:40 #: logs/templates/logs/detailed_history.html:40
#: logs/templates/logs/machine_history.html:39 #: logs/templates/logs/machine_history.html:39
msgid "Comment" msgid "Comment"
msgstr "Commentaire" 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:62
#: logs/templates/logs/aff_summary.html:85 #: logs/templates/logs/aff_summary.html:85
#: logs/templates/logs/aff_summary.html:104 #: logs/templates/logs/aff_summary.html:104
@ -116,6 +161,10 @@ msgstr "Annuler"
msgid "Statistics of the set %(key)s" msgid "Statistics of the set %(key)s"
msgstr "Statistiques de l'ensemble %(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 #: logs/templates/logs/aff_stats_models.html:33
msgid "Number of stored entries" msgid "Number of stored entries"
msgstr "Nombre d'entrées enregistrées" msgstr "Nombre d'entrées enregistrées"
@ -199,23 +248,11 @@ msgstr ""
msgid "Confirm" msgid "Confirm"
msgstr "Confirmer" 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 #: logs/templates/logs/detailed_history.html:31
#, python-format
msgid "History of %(title)s" msgid "History of %(title)s"
msgstr "Historique de %(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 #: logs/templates/logs/detailed_history.html:75
msgid "No event" msgid "No event"
msgstr "Aucun évènement" msgstr "Aucun évènement"
@ -232,7 +269,7 @@ msgid "Statistics"
msgstr "Statistiques" msgstr "Statistiques"
#: logs/templates/logs/index.html:32 logs/templates/logs/stats_logs.html:32 #: logs/templates/logs/index.html:32 logs/templates/logs/stats_logs.html:32
#: logs/views.py:427 #: logs/views.py:439
msgid "Actions performed" msgid "Actions performed"
msgstr "Actions effectuées" msgstr "Actions effectuées"
@ -257,6 +294,11 @@ msgstr "Aucun résultat"
msgid "Search machine history" msgid "Search machine history"
msgstr "Rechercher l'historique des machines" 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 #: logs/templates/logs/sidebar.html:33
msgid "Summary" msgid "Summary"
msgstr "Résumé" msgstr "Résumé"
@ -277,10 +319,6 @@ msgstr "Base de données"
msgid "Wiring actions" msgid "Wiring actions"
msgstr "Actions de câblage" msgstr "Actions de câblage"
#: logs/templates/logs/sidebar.html:53
msgid "Users"
msgstr "Utilisateurs"
#: logs/templates/logs/sidebar.html:57 #: logs/templates/logs/sidebar.html:57
msgid "Machine history" msgid "Machine history"
msgstr "Historique des machines" msgstr "Historique des machines"
@ -297,138 +335,134 @@ msgstr "Statistiques sur la base de données"
msgid "Statistics about users" msgid "Statistics about users"
msgstr "Statistiques sur les utilisateurs" msgstr "Statistiques sur les utilisateurs"
#: logs/views.py:184 #: logs/views.py:196
msgid "Nonexistent revision." msgid "Nonexistent revision."
msgstr "Révision inexistante." msgstr "Révision inexistante."
#: logs/views.py:187 #: logs/views.py:199
msgid "The action was deleted." msgid "The action was deleted."
msgstr "L'action a été supprimée." msgstr "L'action a été supprimée."
#: logs/views.py:228 #: logs/views.py:240
msgid "Category" msgid "Category"
msgstr "Catégorie" msgstr "Catégorie"
#: logs/views.py:229 #: logs/views.py:241
msgid "Number of users (members and clubs)" msgid "Number of users (members and clubs)"
msgstr "Nombre d'utilisateurs (adhérents et clubs)" msgstr "Nombre d'utilisateurs (adhérents et clubs)"
#: logs/views.py:230 #: logs/views.py:242
msgid "Number of members" msgid "Number of members"
msgstr "Nombre d'adhérents" msgstr "Nombre d'adhérents"
#: logs/views.py:231 #: logs/views.py:243
msgid "Number of clubs" msgid "Number of clubs"
msgstr "Nombre de clubs" msgstr "Nombre de clubs"
#: logs/views.py:235 #: logs/views.py:247
msgid "Activated users" msgid "Activated users"
msgstr "Utilisateurs activés" msgstr "Utilisateurs activés"
#: logs/views.py:241 #: logs/views.py:253
msgid "Disabled users" msgid "Disabled users"
msgstr "Utilisateurs désactivés" msgstr "Utilisateurs désactivés"
#: logs/views.py:247 #: logs/views.py:259
msgid "Archived users" msgid "Archived users"
msgstr "Utilisateurs archivés" msgstr "Utilisateurs archivés"
#: logs/views.py:253 #: logs/views.py:265
msgid "Fully archived users" msgid "Fully archived users"
msgstr "Utilisateurs complètement archivés" msgstr "Utilisateurs complètement archivés"
#: logs/views.py:263 #: logs/views.py:275
msgid "Not yet active users" msgid "Not yet active users"
msgstr "Utilisateurs pas encore actifs" msgstr "Utilisateurs pas encore actifs"
#: logs/views.py:273 #: logs/views.py:285
msgid "Contributing members" msgid "Contributing members"
msgstr "Adhérents cotisants" msgstr "Adhérents cotisants"
#: logs/views.py:279 #: logs/views.py:291
msgid "Users benefiting from a connection" msgid "Users benefiting from a connection"
msgstr "Utilisateurs bénéficiant d'une connexion" msgstr "Utilisateurs bénéficiant d'une connexion"
#: logs/views.py:285 #: logs/views.py:297
msgid "Banned users" msgid "Banned users"
msgstr "Utilisateurs bannis" msgstr "Utilisateurs bannis"
#: logs/views.py:291 #: logs/views.py:303
msgid "Users benefiting from a free connection" msgid "Users benefiting from a free connection"
msgstr "Utilisateurs bénéficiant d'une connexion gratuite" msgstr "Utilisateurs bénéficiant d'une connexion gratuite"
#: logs/views.py:297 #: logs/views.py:309
msgid "Users with a confirmed email" msgid "Users with a confirmed email"
msgstr "Utilisateurs ayant un mail confirmé" msgstr "Utilisateurs ayant un mail confirmé"
#: logs/views.py:303 #: logs/views.py:315
msgid "Users with an unconfirmed email" msgid "Users with an unconfirmed email"
msgstr "Utilisateurs ayant un mail non confirmé" msgstr "Utilisateurs ayant un mail non confirmé"
#: logs/views.py:309 #: logs/views.py:321
msgid "Users pending email confirmation" msgid "Users pending email confirmation"
msgstr "Utilisateurs en attente de confirmation du mail" msgstr "Utilisateurs en attente de confirmation du mail"
#: logs/views.py:315 #: logs/views.py:327
msgid "Active interfaces (with access to the network)" msgid "Active interfaces (with access to the network)"
msgstr "Interfaces actives (ayant accès au réseau)" msgstr "Interfaces actives (ayant accès au réseau)"
#: logs/views.py:329 #: logs/views.py:341
msgid "Active interfaces assigned IPv4" msgid "Active interfaces assigned IPv4"
msgstr "Interfaces actives assignées IPv4" msgstr "Interfaces actives assignées IPv4"
#: logs/views.py:346 #: logs/views.py:358
msgid "IP range" msgid "IP range"
msgstr "Plage d'IP" msgstr "Plage d'IP"
#: logs/views.py:347 #: logs/views.py:359
msgid "VLAN" msgid "VLAN"
msgstr "VLAN" msgstr "VLAN"
#: logs/views.py:348 #: logs/views.py:360
msgid "Total number of IP addresses" msgid "Total number of IP addresses"
msgstr "Nombre total d'adresses IP" msgstr "Nombre total d'adresses IP"
#: logs/views.py:349 #: logs/views.py:361
msgid "Number of assigned IP addresses" msgid "Number of assigned IP addresses"
msgstr "Nombre d'adresses IP assignées" 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" msgid "Number of IP address assigned to an activated machine"
msgstr "Nombre d'adresses IP assignées à une machine activée" 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" msgid "Number of unassigned IP addresses"
msgstr "Nombre d'adresses IP non assignées" msgstr "Nombre d'adresses IP non assignées"
#: logs/views.py:366 #: logs/views.py:378
msgid "Users (members and clubs)" msgid "Users (members and clubs)"
msgstr "Utilisateurs (adhérents et clubs)" msgstr "Utilisateurs (adhérents et clubs)"
#: logs/views.py:412 #: logs/views.py:440
msgid "Topology"
msgstr "Topologie"
#: logs/views.py:428
msgid "Number of actions" msgid "Number of actions"
msgstr "Nombre d'actions" msgstr "Nombre d'actions"
#: logs/views.py:453 #: logs/views.py:465
msgid "rights" msgid "rights"
msgstr "droits" msgstr "droits"
#: logs/views.py:482 #: logs/views.py:494
msgid "actions" msgid "actions"
msgstr "actions" msgstr "actions"
#: logs/views.py:532 logs/views.py:583 #: logs/views.py:544 logs/views.py:595
msgid "Nonexistent entry." msgid "Nonexistent entry."
msgstr "Entrée inexistante." msgstr "Entrée inexistante."
#: logs/views.py:545 #: logs/views.py:557
msgid "You don't have the right to access this menu." msgid "You don't have the right to access this menu."
msgstr "Vous n'avez pas le droit d'accéder à ce 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." msgid "No model found."
msgstr "Aucun modèle trouvé." msgstr "Aucun modèle trouvé."

View file

@ -21,9 +21,13 @@
"""logs.models """logs.models
The models definitions for the logs app 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.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.db.models import Q
from django.apps import apps
from netaddr import EUI
from macaddress.fields import default_dialect
from machines.models import IpList from machines.models import IpList
from machines.models import Interface from machines.models import Interface
@ -35,6 +39,58 @@ from users.models import Club
from topologie.models import Room from topologie.models import Room
from topologie.models import Port from topologie.models import Port
from .forms import classes_for_action_type
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)
action_types = params.get("t", None)
query = Q()
if user:
query &= Q(user__pseudo=user)
if start:
query &= Q(date_created__gte=start)
if end:
query &= Q(date_created__lte=end)
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()
.filter(query)
.select_related("user")
.prefetch_related("version_set__object")
)
def models_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 += list(map(str.lower, c))
return classes
class MachineHistorySearchEvent: class MachineHistorySearchEvent:
def __init__(self, user, machine, interface, start=None, end=None): def __init__(self, user, machine, interface, start=None, end=None):
@ -95,11 +151,18 @@ class MachineHistorySearch:
self.events = [] self.events = []
if search_type == "ip": 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": elif search_type == "mac":
return self._get_by_mac(search)[::-1] try:
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): def _add_revision(self, user, machine, interface):
""" """
@ -144,9 +207,10 @@ class MachineHistorySearch:
except IpList.DoesNotExist: except IpList.DoesNotExist:
return [] return []
return filter( return (
lambda x: x.field_dict["ipv4_id"] == ip_id, Version.objects.get_for_model(Interface)
Version.objects.get_for_model(Interface).order_by("revision__date_created") .filter(serialized_data__contains='"ipv4": {}'.format(ip_id))
.order_by("revision__date_created")
) )
def _get_interfaces_for_mac(self, mac): def _get_interfaces_for_mac(self, mac):
@ -155,9 +219,10 @@ class MachineHistorySearch:
:return: An iterable object with the Version objects :return: An iterable object with the Version objects
of Interfaces with the given MAC address of Interfaces with the given MAC address
""" """
return filter( return (
lambda x: str(x.field_dict["mac_address"]) == mac, Version.objects.get_for_model(Interface)
Version.objects.get_for_model(Interface).order_by("revision__date_created") .filter(serialized_data__contains='"mac_address": "{}"'.format(mac))
.order_by("revision__date_created")
) )
def _get_machines_for_interface(self, interface): def _get_machines_for_interface(self, interface):
@ -167,9 +232,10 @@ class MachineHistorySearch:
which the given interface was attributed which the given interface was attributed
""" """
machine_id = interface.field_dict["machine_id"] machine_id = interface.field_dict["machine_id"]
return filter( return (
lambda x: x.field_dict["id"] == machine_id, Version.objects.get_for_model(Machine)
Version.objects.get_for_model(Machine).order_by("revision__date_created") .filter(serialized_data__contains='"pk": {}'.format(machine_id))
.order_by("revision__date_created")
) )
def _get_user_for_machine(self, machine): def _get_user_for_machine(self, machine):
@ -301,9 +367,10 @@ class History:
# Get all the versions for this instance, with the oldest first # Get all the versions for this instance, with the oldest first
self._last_version = None self._last_version = None
interface_versions = filter( interface_versions = (
lambda x: x.field_dict["id"] == instance_id, Version.objects.get_for_model(model)
Version.objects.get_for_model(model).order_by("revision__date_created") .filter(serialized_data__contains='"pk": {}'.format(instance_id))
.order_by("revision__date_created")
) )
for version in interface_versions: for version in interface_versions:
@ -350,6 +417,85 @@ class History:
self._last_version = version self._last_version = version
class VersionAction(HistoryEvent):
def __init__(self, version):
self.version = version
def name(self):
return self.version._object_cache or 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()
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()
try:
query = (
Q(
serialized_data__contains='"pk": {}'.format(self.object_id())
)
& Q(
revision__date_created__lt=self.version.revision.date_created
)
)
return (Version.objects.get_for_model(model)
.filter(query)
.order_by("-revision__date_created")[0])
except Exception as e:
return None
def _compute_diff(self, v1, v2, ignoring=["pwd_ntlm"]):
"""
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 key not in ignoring and 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()]
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): class UserHistoryEvent(HistoryEvent):
def _repr(self, name, value): def _repr(self, name, value):
""" """
@ -450,21 +596,29 @@ class UserHistory(History):
self.events = [] self.events = []
# Try to find an Adherent object # Try to find an Adherent object
adherents = filter( # If it exists, its id will be the same as the user's
lambda x: x.field_dict["user_ptr_id"] == user_id, adherents = (
Version.objects.get_for_model(Adherent) Version.objects.get_for_model(Adherent)
.filter(serialized_data__contains='"pk": {}'.format(user_id))
) )
obj = next(adherents, None) try:
model = Adherent obj = adherents[0]
model = Adherent
except IndexError:
obj = None
# Fallback on a Club # Fallback on a Club
if obj is None: if obj is None:
clubs = filter( clubs = (
lambda x: x.field_dict["user_ptr_id"] == user_id,
Version.objects.get_for_model(Club) Version.objects.get_for_model(Club)
.filter(serialized_data__contains='"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 nothing was found, abort
if obj is None: if obj is None:
@ -472,9 +626,10 @@ class UserHistory(History):
# Add in "related" elements the list of Machine objects # Add in "related" elements the list of Machine objects
# that were once owned by this user # that were once owned by this user
self.related = filter( self.related = (
lambda x: x.field_dict["user_id"] == user_id, Version.objects.get_for_model(Machine)
Version.objects.get_for_model(Machine).order_by("-revision__date_created") .filter(serialized_data__contains='"user": {}'.format(user_id))
.order_by("-revision__date_created")
) )
self.related = [RelatedHistory( self.related = [RelatedHistory(
m.field_dict["name"] or _("None"), m.field_dict["name"] or _("None"),
@ -484,9 +639,10 @@ class UserHistory(History):
# Get all the versions for this user, with the oldest first # Get all the versions for this user, with the oldest first
self._last_version = None self._last_version = None
user_versions = filter( user_versions = (
lambda x: x.field_dict["id"] == user_id, Version.objects.get_for_model(User)
Version.objects.get_for_model(User).order_by("revision__date_created") .filter(serialized_data__contains='"pk": {}'.format(user_id))
.order_by("revision__date_created")
) )
for version in user_versions: for version in user_versions:
@ -497,9 +653,10 @@ class UserHistory(History):
# Do the same thing for the Adherent of Club # Do the same thing for the Adherent of Club
self._last_version = None self._last_version = None
obj_versions = filter( obj_versions = (
lambda x: x.field_dict["id"] == user_id, Version.objects.get_for_model(model)
Version.objects.get_for_model(model).order_by("revision__date_created") .filter(serialized_data__contains='"pk": {}'.format(user_id))
.order_by("revision__date_created")
) )
for version in obj_versions: for version in obj_versions:
@ -562,10 +719,11 @@ class MachineHistory(History):
def get(self, machine_id): def get(self, machine_id):
# Add as "related" histories the list of Interface objects # Add as "related" histories the list of Interface objects
# that were once assigned to this machine # that were once assigned to this machine
self.related = list(filter( self.related = list(
lambda x: x.field_dict["machine_id"] == machine_id, Version.objects.get_for_model(Interface)
Version.objects.get_for_model(Interface).order_by("-revision__date_created") .filter(serialized_data__contains='"machine": {}'.format(machine_id))
)) .order_by("-revision__date_created")
)
# Create RelatedHistory objects and remove duplicates # Create RelatedHistory objects and remove duplicates
self.related = [RelatedHistory( self.related = [RelatedHistory(

View file

@ -34,22 +34,51 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<thead> <thead>
<tr> <tr>
<th>{% trans "Edited object" %}</th> <th>{% trans "Edited object" %}</th>
<th>{% trans "Object type" %}</th>
{% trans "Edited by" as tr_edited_by %} {% trans "Edited by" as tr_edited_by %}
<th>{% include 'buttons/sort.html' with prefix='logs' col='author' text=tr_edited_by %}</th> <th>{% include 'buttons/sort.html' with prefix='logs' col='author' text=tr_edited_by %}</th>
{% trans "Date of editing" as tr_date_of_editing %} {% trans "Date of editing" as tr_date_of_editing %}
<th>{% include 'buttons/sort.html' with prefix='logs' col='date' text=tr_date_of_editing %}</th> <th>{% include 'buttons/sort.html' with prefix='logs' col='date' text=tr_date_of_editing %}</th>
<th>{% trans "Edited" %}</th>
<th>{% trans "Comment" %}</th> <th>{% trans "Comment" %}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
{% for revision in revisions_list %} {% for revision in revisions_list %}
{% for reversion in revision.version_set.all %} {% for version in revision.versions %}
<tr> <tr>
<td>{{ reversion.object|truncatechars:20 }}</td> <td>
<td>{{ reversion.object|classname }}</td> {% if version.object_id %}
<td>{{ revision.user }}</td> <a href="{% url 'logs:history' version.application version.model_name version.object_id %}" title="{% trans "History" %}">
{{ version.name }}
</a>
{% else %}
{{ version.name }}
{% endif %}
</td>
<td>
{% if revision.performed_by %}
<a href="{% url 'users:profil' userid=revision.performed_by.id %}" title=tr_view_the_profile>
{{ revision.performed_by }}
</a>
{% else %}
{{ revision.performed_by }}
{% endif %}
</td>
<td>{{ revision.date_created }}</td> <td>{{ revision.date_created }}</td>
<td>
{% for edit in version.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|truncatechars:50 }}</i><br/>
{% else %}
<strong>{{ edit.0 }}:</strong>
<i class="text-danger"> {{ edit.1|truncatechars:50 }} </i>
<i class="text-success">{{ edit.2|truncatechars:50 }}</i><br/>
{% endif %}
{% endfor %}
</td>
<td>{{ revision.comment }}</td> <td>{{ revision.comment }}</td>
{% can_edit_history %} {% can_edit_history %}
<td> <td>

View file

@ -0,0 +1,44 @@
{% 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 massive_bootstrap_form %}
{% load i18n %}
{% block title %}{% trans "Search events" %}{% endblock %}
{% block content %}
<form class="form">
<h3>{% trans "Search events" %}</h3>
{% massive_bootstrap_form actions_form 'u' %}
{% trans "Search" as tr_search %}
{% bootstrap_button tr_search button_type="submit" icon="search" %}
</form>
<br />
<br />
<br />
<br />
<br />
{% endblock %}

View file

@ -102,13 +102,18 @@ from re2o.base import re2o_paginator, SortTable
from re2o.acl import can_view_all, can_view_app, can_edit_history, can_view from re2o.acl import can_view_all, can_view_app, can_edit_history, can_view
from .models import ( from .models import (
ActionsSearch,
RevisionAction,
MachineHistorySearch, MachineHistorySearch,
UserHistory, UserHistory,
MachineHistory, MachineHistory,
InterfaceHistory InterfaceHistory
) )
from .forms import MachineHistorySearchForm from .forms import (
ActionsSearchForm,
MachineHistorySearchForm
)
@login_required @login_required
@ -158,20 +163,27 @@ def index(request):
def stats_logs(request): def stats_logs(request):
"""Affiche l'ensemble des logs et des modifications sur les objets, """Affiche l'ensemble des logs et des modifications sur les objets,
classés par date croissante, en vrac""" classés par date croissante, en vrac"""
pagination_number = GeneralOption.get_cached_value("pagination_number") actions_form = ActionsSearchForm(request.GET or None)
revisions = (
Revision.objects.all() if actions_form.is_valid():
.select_related("user") actions = ActionsSearch()
.prefetch_related("version_set__object") revisions = actions.get(actions_form.cleaned_data)
) revisions = SortTable.sort(
revisions = SortTable.sort( revisions,
revisions, request.GET.get("col"),
request.GET.get("col"), request.GET.get("order"),
request.GET.get("order"), SortTable.LOGS_STATS_LOGS,
SortTable.LOGS_STATS_LOGS, )
)
revisions = re2o_paginator(request, revisions, pagination_number) pagination_number = GeneralOption.get_cached_value("pagination_number")
return render(request, "logs/stats_logs.html", {"revisions_list": revisions}) revisions = re2o_paginator(request, revisions, pagination_number)
# 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})
@login_required @login_required

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-06-23 16:35+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2019-11-16 00:22+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-06-24 15:54+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-06-24 20:10+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2019-11-16 00:35+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-06-25 14:53+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-06-27 23:35+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"