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/forms.py b/logs/forms.py index 07e421c9..30780d4a 100644 --- a/logs/forms.py +++ b/logs/forms.py @@ -25,15 +25,105 @@ 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 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 = ( ("ip", _("IPv4")), ("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): """The form for a simple search""" - q = forms.CharField( label=_("Search"), max_length=100, 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/logs/models.py b/logs/models.py index b8789db8..acf8771b 100644 --- a/logs/models.py +++ b/logs/models.py @@ -21,9 +21,13 @@ """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 django.apps import apps +from netaddr import EUI +from macaddress.fields import default_dialect from machines.models import IpList from machines.models import Interface @@ -35,6 +39,58 @@ 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): + """ + :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: def __init__(self, user, machine, interface, start=None, end=None): @@ -95,11 +151,18 @@ 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, dialect=default_dialect()) + return self._get_by_mac(search)[::-1] + except: + pass - return None + return [] def _add_revision(self, user, machine, interface): """ @@ -144,9 +207,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__contains='"ipv4": {}'.format(ip_id)) + .order_by("revision__date_created") ) def _get_interfaces_for_mac(self, mac): @@ -155,9 +219,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__contains='"mac_address": "{}"'.format(mac)) + .order_by("revision__date_created") ) def _get_machines_for_interface(self, interface): @@ -167,9 +232,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__contains='"pk": {}'.format(machine_id)) + .order_by("revision__date_created") ) def _get_user_for_machine(self, machine): @@ -301,9 +367,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__contains='"pk": {}'.format(instance_id)) + .order_by("revision__date_created") ) for version in interface_versions: @@ -350,6 +417,85 @@ class History: 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): def _repr(self, name, value): """ @@ -450,21 +596,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__contains='"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__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 obj is None: @@ -472,9 +626,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__contains='"user": {}'.format(user_id)) + .order_by("-revision__date_created") ) self.related = [RelatedHistory( m.field_dict["name"] or _("None"), @@ -484,9 +639,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__contains='"pk": {}'.format(user_id)) + .order_by("revision__date_created") ) for version in user_versions: @@ -497,9 +653,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__contains='"pk": {}'.format(user_id)) + .order_by("revision__date_created") ) for version in obj_versions: @@ -562,10 +719,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__contains='"machine": {}'.format(machine_id)) + .order_by("-revision__date_created") + ) # Create RelatedHistory objects and remove duplicates self.related = [RelatedHistory( diff --git a/logs/templates/logs/aff_stats_logs.html b/logs/templates/logs/aff_stats_logs.html index adccc95f..3ba37958 100644 --- a/logs/templates/logs/aff_stats_logs.html +++ b/logs/templates/logs/aff_stats_logs.html @@ -34,22 +34,51 @@ 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 %} {% include 'buttons/sort.html' with prefix='logs' col='date' text=tr_date_of_editing %} + {% trans "Edited" %} {% trans "Comment" %} {% for revision in revisions_list %} - {% for reversion in revision.version_set.all %} + {% for version in revision.versions %} - {{ reversion.object|truncatechars:20 }} - {{ reversion.object|classname }} - {{ revision.user }} + + {% 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 }} + + {% 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|truncatechars:50 }}
+ {% else %} + {{ edit.0 }}: + {{ edit.1|truncatechars:50 }} + ➔ {{ edit.2|truncatechars:50 }}
+ {% endif %} + {% endfor %} + {{ revision.comment }} {% can_edit_history %} diff --git a/logs/templates/logs/search_stats_logs.html b/logs/templates/logs/search_stats_logs.html new file mode 100644 index 00000000..5211d336 --- /dev/null +++ b/logs/templates/logs/search_stats_logs.html @@ -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 %} + +
+

{% trans "Search events" %}

+ + {% massive_bootstrap_form actions_form 'u' %} + {% 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..b71187aa 100644 --- a/logs/views.py +++ b/logs/views.py @@ -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 .models import ( + ActionsSearch, + RevisionAction, MachineHistorySearch, UserHistory, MachineHistory, InterfaceHistory ) -from .forms import MachineHistorySearchForm +from .forms import ( + ActionsSearchForm, + MachineHistorySearchForm +) @login_required @@ -158,20 +163,27 @@ 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) + + # 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 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"