From 290504be788401311d482e74559ea4abcf1bcded Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 17:14:42 +0200 Subject: [PATCH 01/23] Rename MachineHistory to MachineHistorySearch --- logs/forms.py | 6 +++--- logs/models.py | 16 ++++++++-------- logs/views.py | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/logs/forms.py b/logs/forms.py index 3182ef17..07e421c9 100644 --- a/logs/forms.py +++ b/logs/forms.py @@ -18,7 +18,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""The forms used by the search app""" +"""The forms used by the machine search view""" from django import forms from django.forms import Form @@ -31,7 +31,7 @@ CHOICES_TYPE = ( ) -class MachineHistoryForm(Form): +class MachineHistorySearchForm(Form): """The form for a simple search""" q = forms.CharField( @@ -46,7 +46,7 @@ class MachineHistoryForm(Form): e = forms.DateField(required=False, label=_("End date")) def __init__(self, *args, **kwargs): - super(MachineHistoryForm, self).__init__(*args, **kwargs) + super(MachineHistorySearchForm, 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 2066c77e..2161ba9e 100644 --- a/logs/models.py +++ b/logs/models.py @@ -34,7 +34,7 @@ from users.models import Club from topologie.models import Room -class MachineHistoryEvent: +class MachineHistorySearchEvent: def __init__(self, user, machine, interface, start=None, end=None): """ :param user: User, The user owning the maching at the time of the event @@ -76,7 +76,7 @@ class MachineHistoryEvent: ) -class MachineHistory: +class MachineHistorySearch: def __init__(self): self.events = [] self.__last_evt = None @@ -85,7 +85,7 @@ class MachineHistory: """ :param search: ip or mac to lookup :param params: dict built by the search view - :return: list or None, a list of MachineHistoryEvent + :return: list or None, a list of MachineHistorySearchEvent in reverse chronological order """ self.start = params.get("s", None) self.end = params.get("e", None) @@ -93,9 +93,9 @@ class MachineHistory: self.events = [] if search_type == "ip": - return self.__get_by_ip(search) + return self.__get_by_ip(search)[::-1] elif search_type == "mac": - return self.__get_by_mac(search) + return self.__get_by_mac(search)[::-1] return None @@ -106,7 +106,7 @@ class MachineHistory: :param machine: Version, the machine version related to the interface :param interface: Version, the interface targeted by this event """ - evt = MachineHistoryEvent(user, machine, interface) + evt = MachineHistorySearchEvent(user, machine, interface) evt.start_date = interface.revision.date_created # Try not to recreate events if it's unnecessary @@ -182,7 +182,7 @@ class MachineHistory: def __get_by_ip(self, ip): """ :param ip: str, The IP to lookup - :returns: list, a list of MachineHistoryEvent + :returns: list, a list of MachineHistorySearchEvent """ interfaces = self.__get_interfaces_for_ip(ip) @@ -198,7 +198,7 @@ class MachineHistory: def __get_by_mac(self, mac): """ :param mac: str, The MAC address to lookup - :returns: list, a list of MachineHistoryEvent + :returns: list, a list of MachineHistorySearchEvent """ interfaces = self.__get_interfaces_for_mac(mac) diff --git a/logs/views.py b/logs/views.py index 25ee7403..9aaa1e03 100644 --- a/logs/views.py +++ b/logs/views.py @@ -101,8 +101,8 @@ from re2o.utils import ( 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 MachineHistory, UserHistory -from .forms import MachineHistoryForm +from .models import MachineHistorySearch, UserHistory +from .forms import MachineHistorySearchForm @login_required @@ -486,9 +486,9 @@ def stats_actions(request): def stats_search_machine_history(request): """View which displays the history of machines with the given une IP or MAC adresse""" - history_form = MachineHistoryForm(request.GET or None) + history_form = MachineHistorySearchForm(request.GET or None) if history_form.is_valid(): - history = MachineHistory() + history = MachineHistorySearch() events = history.get( history_form.cleaned_data.get("q", ""), history_form.cleaned_data From 8b83f9f4bcfb8e3434e99bb1cb99918b8663c776 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 17:39:45 +0200 Subject: [PATCH 02/23] Create InterfaceHistory --- logs/models.py | 164 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 123 insertions(+), 41 deletions(-) diff --git a/logs/models.py b/logs/models.py index 2161ba9e..ccf00741 100644 --- a/logs/models.py +++ b/logs/models.py @@ -212,7 +212,77 @@ class MachineHistorySearch: return self.events -class UserHistoryEvent: +class HistoryEvent: + def __init__(self, version, previous_version=None, edited_fields=None): + """ + :param version: Version, the version of the object for this event + :param previous_version: Version, the version of the object before this event + :param edited_fields: list, The list of modified fields by this event + """ + self.version = version + self.previous_version = previous_version + self.edited_fields = edited_fields + self.date = version.revision.date_created + self.performed_by = version.revision.user + self.comment = version.revision.get_comment() or None + + def __repr(self, name, value): + """ + Returns the best representation of the given field + :param name: the name of the field + :param value: the value of the field + :return: object + """ + if value is None: + return _("None") + + return value + + def edits(self, hide=[]): + """ + Build a list of the changes performed during this event + :param hide: list, the list of fields for which not to show details + :return: str + """ + edits = [] + + for field in self.edited_fields: + if field in hide: + # Don't show sensitive information + edits.append((field, None, None)) + else: + edits.append(( + field, + self.__repr(field, self.previous_version.field_dict[field]), + self.__repr(field, self.version.field_dict[field]) + )) + + return edits + + +class History: + def __init__(self): + self.events = [] + self.__last_version = None + + def __compute_diff(self, v1, v2, ignoring=[]): + """ + 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 UserHistoryEvent(HistoryEvent): def __init__(self, user, version, previous_version=None, edited_fields=None): """ :param user: User, The user who's history is being built @@ -220,13 +290,8 @@ class UserHistoryEvent: :param previous_version: Version, the version of the user before this event :param edited_fields: list, The list of modified fields by this event """ + super(UserHistoryEvent, self).init(version, previous_version, edited_fields) self.user = user - self.version = version - self.previous_version = previous_version - self.edited_fields = edited_fields - self.date = version.revision.date_created - self.performed_by = version.revision.user - self.comment = version.revision.get_comment() or None def __repr(self, name, value): """ @@ -296,20 +361,7 @@ class UserHistoryEvent: :param hide: list, the list of fields for which not to show details :return: str """ - edits = [] - - for field in self.edited_fields: - if field in hide: - # Don't show sensitive information - edits.append((field, None, None)) - else: - edits.append(( - field, - self.__repr(field, self.previous_version.field_dict[field]), - self.__repr(field, self.version.field_dict[field]) - )) - - return edits + return super(UserHistoryEvent, self).edits(hide) def __eq__(self, other): return ( @@ -332,10 +384,9 @@ class UserHistoryEvent: ) -class UserHistory: +class UserHistory(History): def __init__(self): - self.events = [] - self.__last_version = None + super(UserHistory, self).init() def get(self, user): """ @@ -378,22 +429,6 @@ class UserHistory: reverse=True ) - def __compute_diff(self, v1, v2, ignoring=["last_login", "pwd_ntlm", "email_change_date"]): - """ - 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 - def __add_revision(self, user, version): """ Add a new revision to the chronological order @@ -402,7 +437,11 @@ class UserHistory: """ diff = None if self.__last_version is not None: - diff = self.__compute_diff(version, self.__last_version) + diff = self.__compute_diff( + version, + self.__last_version, + ignoring=["last_login", "pwd_ntlm", "email_change_date"] + ) # Ignore "empty" events like login if not diff: @@ -412,3 +451,46 @@ class UserHistory: evt = UserHistoryEvent(user, version, self.__last_version, diff) self.events.append(evt) self.__last_version = version + + +class InterfaceHistoryEvent(HistoryEvent): + pass + + +class InterfaceHistory: + def get(self, interface_id): + """ + :param interface_id: Interface, the interface to lookup + :return: list or None, a list of InterfaceHistoryEvent, in reverse chronological order + """ + self.events = [] + + # Get all the versions for this interface, with the oldest first + self.__last_version = None + user_versions = filter( + lambda x: x.field_dict["id"] == interface_id, + Version.objects.get_for_model(Interface).order_by("revision__date_created") + ) + + for version in user_versions: + self.__add_revision(version) + + return self.events[::-1] + + def __add_revision(self, version): + """ + Add a new revision to the chronological order + :param version: Version, The version of the interface for this event + """ + diff = None + if self.__last_version is not None: + diff = self.__compute_diff(version, self.__last_version) + + # Ignore "empty" events + if not diff: + self.__last_version = version + return + + evt = InterfaceHistoryEvent(version, self.__last_version, diff) + self.events.append(evt) + self.__last_version = version From 9a88fe1d08577a698ceb7b099c8af02102642ba3 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 15:46:30 +0000 Subject: [PATCH 03/23] Fix history inheritence --- logs/models.py | 100 ++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/logs/models.py b/logs/models.py index ccf00741..0c52f885 100644 --- a/logs/models.py +++ b/logs/models.py @@ -79,7 +79,7 @@ class MachineHistorySearchEvent: class MachineHistorySearch: def __init__(self): self.events = [] - self.__last_evt = None + self._last_evt = None def get(self, search, params): """ @@ -93,13 +93,13 @@ class MachineHistorySearch: self.events = [] if search_type == "ip": - return self.__get_by_ip(search)[::-1] + return self._get_by_ip(search)[::-1] elif search_type == "mac": - return self.__get_by_mac(search)[::-1] + return self._get_by_mac(search)[::-1] return None - def __add_revision(self, user, machine, interface): + def _add_revision(self, user, machine, interface): """ Add a new revision to the chronological order :param user: User, The user owning the maching at the time of the event @@ -110,16 +110,16 @@ class MachineHistorySearch: evt.start_date = interface.revision.date_created # Try not to recreate events if it's unnecessary - if evt.is_similar(self.__last_evt): + if evt.is_similar(self._last_evt): return # Mark the end of validity of the last element - if self.__last_evt and not self.__last_evt.end_date: - self.__last_evt.end_date = evt.start_date + if self._last_evt and not self._last_evt.end_date: + self._last_evt.end_date = evt.start_date # If the event ends before the given date, remove it if self.start and evt.start_date.date() < self.start: - self.__last_evt = None + self._last_evt = None self.events.pop() # Make sure the new event starts before the given end date @@ -128,9 +128,9 @@ class MachineHistorySearch: # Save the new element self.events.append(evt) - self.__last_evt = evt + self._last_evt = evt - def __get_interfaces_for_ip(self, ip): + def _get_interfaces_for_ip(self, ip): """ :param ip: str :return: An iterable object with the Version objects @@ -147,7 +147,7 @@ class MachineHistorySearch: Version.objects.get_for_model(Interface).order_by("revision__date_created") ) - def __get_interfaces_for_mac(self, mac): + def _get_interfaces_for_mac(self, mac): """ :param mac: str :return: An iterable object with the Version objects @@ -158,7 +158,7 @@ class MachineHistorySearch: Version.objects.get_for_model(Interface).order_by("revision__date_created") ) - def __get_machines_for_interface(self, interface): + def _get_machines_for_interface(self, interface): """ :param interface: Version, the interface for which to find the machines :return: An iterable object with the Version objects of Machine to @@ -170,7 +170,7 @@ class MachineHistorySearch: Version.objects.get_for_model(Machine).order_by("revision__date_created") ) - def __get_user_for_machine(self, machine): + def _get_user_for_machine(self, machine): """ :param machine: Version, the machine of which the owner must be found :return: The user to which the given machine belongs @@ -179,35 +179,35 @@ class MachineHistorySearch: user_id = machine.field_dict["user_id"] return User.objects.get(id=user_id) - def __get_by_ip(self, ip): + def _get_by_ip(self, ip): """ :param ip: str, The IP to lookup :returns: list, a list of MachineHistorySearchEvent """ - interfaces = self.__get_interfaces_for_ip(ip) + interfaces = self._get_interfaces_for_ip(ip) for interface in interfaces: - machines = self.__get_machines_for_interface(interface) + machines = self._get_machines_for_interface(interface) for machine in machines: - user = self.__get_user_for_machine(machine) - self.__add_revision(user, machine, interface) + user = self._get_user_for_machine(machine) + self._add_revision(user, machine, interface) return self.events - def __get_by_mac(self, mac): + def _get_by_mac(self, mac): """ :param mac: str, The MAC address to lookup :returns: list, a list of MachineHistorySearchEvent """ - interfaces = self.__get_interfaces_for_mac(mac) + interfaces = self._get_interfaces_for_mac(mac) for interface in interfaces: - machines = self.__get_machines_for_interface(interface) + machines = self._get_machines_for_interface(interface) for machine in machines: - user = self.__get_user_for_machine(machine) - self.__add_revision(user, machine, interface) + user = self._get_user_for_machine(machine) + self._add_revision(user, machine, interface) return self.events @@ -226,7 +226,7 @@ class HistoryEvent: self.performed_by = version.revision.user self.comment = version.revision.get_comment() or None - def __repr(self, name, value): + def _repr(self, name, value): """ Returns the best representation of the given field :param name: the name of the field @@ -253,8 +253,8 @@ class HistoryEvent: else: edits.append(( field, - self.__repr(field, self.previous_version.field_dict[field]), - self.__repr(field, self.version.field_dict[field]) + self._repr(field, self.previous_version.field_dict[field]), + self._repr(field, self.version.field_dict[field]) )) return edits @@ -263,9 +263,9 @@ class HistoryEvent: class History: def __init__(self): self.events = [] - self.__last_version = None + self._last_version = None - def __compute_diff(self, v1, v2, ignoring=[]): + def _compute_diff(self, v1, v2, ignoring=[]): """ Find the edited field between two versions :param v1: Version @@ -290,10 +290,10 @@ class UserHistoryEvent(HistoryEvent): :param previous_version: Version, the version of the user before this event :param edited_fields: list, The list of modified fields by this event """ - super(UserHistoryEvent, self).init(version, previous_version, edited_fields) + super(UserHistoryEvent, self).__init__(version, previous_version, edited_fields) self.user = user - def __repr(self, name, value): + def _repr(self, name, value): """ Returns the best representation of the given field :param name: the name of the field @@ -386,7 +386,7 @@ class UserHistoryEvent(HistoryEvent): class UserHistory(History): def __init__(self): - super(UserHistory, self).init() + super(UserHistory, self).__init__() def get(self, user): """ @@ -402,24 +402,24 @@ class UserHistory(History): obj = Club.objects.get(user_ptr_id=user.id) # Get all the versions for this user, with the oldest first - self.__last_version = None + 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") ) for version in user_versions: - self.__add_revision(user, version) + self._add_revision(user, version) # Do the same thing for the Adherent of Club - self.__last_version = None + self._last_version = None obj_versions = filter( lambda x: x.field_dict["id"] == obj.id, Version.objects.get_for_model(type(obj)).order_by("revision__date_created") ) for version in obj_versions: - self.__add_revision(user, version) + self._add_revision(user, version) # Remove duplicates and sort self.events = list(dict.fromkeys(self.events)) @@ -429,28 +429,28 @@ class UserHistory(History): reverse=True ) - def __add_revision(self, user, version): + def _add_revision(self, user, version): """ Add a new revision to the chronological order :param user: User, The user displayed in this history :param version: Version, The version of the user for this event """ diff = None - if self.__last_version is not None: - diff = self.__compute_diff( + if self._last_version is not None: + diff = self._compute_diff( version, - self.__last_version, + self._last_version, ignoring=["last_login", "pwd_ntlm", "email_change_date"] ) # Ignore "empty" events like login if not diff: - self.__last_version = version + self._last_version = version return - evt = UserHistoryEvent(user, version, self.__last_version, diff) + evt = UserHistoryEvent(user, version, self._last_version, diff) self.events.append(evt) - self.__last_version = version + self._last_version = version class InterfaceHistoryEvent(HistoryEvent): @@ -466,31 +466,31 @@ class InterfaceHistory: self.events = [] # Get all the versions for this interface, with the oldest first - self.__last_version = None + self._last_version = None user_versions = filter( lambda x: x.field_dict["id"] == interface_id, Version.objects.get_for_model(Interface).order_by("revision__date_created") ) for version in user_versions: - self.__add_revision(version) + self._add_revision(version) return self.events[::-1] - def __add_revision(self, version): + def _add_revision(self, version): """ Add a new revision to the chronological order :param version: Version, The version of the interface for this event """ diff = None - if self.__last_version is not None: - diff = self.__compute_diff(version, self.__last_version) + if self._last_version is not None: + diff = self._compute_diff(version, self._last_version) # Ignore "empty" events if not diff: - self.__last_version = version + self._last_version = version return - evt = InterfaceHistoryEvent(version, self.__last_version, diff) + evt = InterfaceHistoryEvent(version, self._last_version, diff) self.events.append(evt) - self.__last_version = version + self._last_version = version From bb901a4c3f16841d672f351f60f323da4eb78183 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 18:06:21 +0200 Subject: [PATCH 04/23] Add view for InterfaceHistory --- ...ser_history.html => detailed_history.html} | 2 +- logs/urls.py | 6 +- logs/views.py | 90 +++++++++++++------ 3 files changed, 70 insertions(+), 28 deletions(-) rename logs/templates/logs/{user_history.html => detailed_history.html} (97%) diff --git a/logs/templates/logs/user_history.html b/logs/templates/logs/detailed_history.html similarity index 97% rename from logs/templates/logs/user_history.html rename to logs/templates/logs/detailed_history.html index ce492281..df0f0162 100644 --- a/logs/templates/logs/user_history.html +++ b/logs/templates/logs/detailed_history.html @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}{% trans "History" %}{% endblock %} {% block content %} -

{% blocktrans %}History of {{ user }}{% endblocktrans %}

+

{% blocktrans %}History of {{ object }}{% endblocktrans %}

{% if events %} diff --git a/logs/urls.py b/logs/urls.py index 562bd93c..eefe6a70 100644 --- a/logs/urls.py +++ b/logs/urls.py @@ -37,6 +37,11 @@ urlpatterns = [ views.revert_action, name="revert-action", ), + url( + r"(?P\w+)/(?P[0-9]+)$", + views.detailed_history, + name="detailed-history", + ), url(r"^stats_general/$", views.stats_general, name="stats-general"), url(r"^stats_models/$", views.stats_models, name="stats-models"), url(r"^stats_users/$", views.stats_users, name="stats-users"), @@ -47,5 +52,4 @@ urlpatterns = [ name="history", ), url(r"^stats_search_machine/$", views.stats_search_machine_history, name="stats-search-machine"), - url(r"^user/(?P[0-9]+)$", views.user_history, name="user-history"), ] diff --git a/logs/views.py b/logs/views.py index 9aaa1e03..0bf593c2 100644 --- a/logs/views.py +++ b/logs/views.py @@ -101,7 +101,12 @@ from re2o.utils import ( 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 MachineHistorySearch, UserHistory +from .models import ( + MachineHistorySearch, + UserHistory, + InterfaceHistory +) + from .forms import MachineHistorySearchForm @@ -508,32 +513,77 @@ def stats_search_machine_history(request): return render(request, "logs/search_machine_history.html", {"history_form": history_form}) -@login_required -@can_view(User) -def user_history(request, users, **_kwargs): - history = UserHistory() - events = history.get(users) +def get_history_object(request, model, object_name, object_id): + """Get the objet of type model with the given object_id + Handles permissions and DoesNotExist errors + """ + try: + object_name_id = object_name + "id" + kwargs = {object_name_id: object_id} + instance = model.get_instance(**kwargs) + except model.DoesNotExist: + messages.error(request, _("Nonexistent entry.")) + return False, redirect( + reverse("users:profil", kwargs={"userid": str(request.user.id)}) + ) + can, msg, _permissions = instance.can_view(request.user) + if not can: + messages.error( + request, msg or _("You don't have the right to access this menu.") + ) + return False, redirect( + reverse("users:profil", kwargs={"userid": str(request.user.id)}) + ) + + return True, instance + + +@login_required +def detailed_history(request, object_name, object_id): + """Render a detailed history for a model. + Permissions are handled by get_history_object. + """ + # Only handle objects for which a detailed view exists + if object_name == "user": + model = User + history = UserHistory() + elif object_name == "machine": + model = Machine + elif object_name == "interface": + model = Interface + history = InterfaceHistory() + else: + raise Http404(_("No model found.")) + + # Get instance and check permissions + can_view, instance = get_history_object(model, object_name, object_id) + if not can_view: + return instance + + # Generate the pagination with the objects max_result = GeneralOption.get_cached_value("pagination_number") events = re2o_paginator( request, - events, + history.get(instance), max_result ) return render( request, - "logs/user_history.html", - { "user": users, "events": events }, + "logs/detailed_history.html", + {"object": instance, "events": events}, ) +@login_required def history(request, application, object_name, object_id): """Render history for a model. The model is determined using the `HISTORY_BIND` dictionnary if none is found, raises a Http404. The view checks if the user is allowed to see the history using the `can_view` method of the model. + Permissions are handled by get_history_object. Args: request: The request sent by the user. @@ -552,23 +602,11 @@ def history(request, application, object_name, object_id): model = apps.get_model(application, object_name) except LookupError: raise Http404(_("No model found.")) - object_name_id = object_name + "id" - kwargs = {object_name_id: object_id} - try: - instance = model.get_instance(**kwargs) - except model.DoesNotExist: - messages.error(request, _("Nonexistent entry.")) - return redirect( - reverse("users:profil", kwargs={"userid": str(request.user.id)}) - ) - can, msg, _permissions = instance.can_view(request.user) - if not can: - messages.error( - request, msg or _("You don't have the right to access this menu.") - ) - return redirect( - reverse("users:profil", kwargs={"userid": str(request.user.id)}) - ) + + can_view, instance = get_history_object(model, object_name, object_id) + if not can_view: + return instance + pagination_number = GeneralOption.get_cached_value("pagination_number") reversions = Version.objects.get_for_object(instance) if hasattr(instance, "linked_objects"): From 20ea0ead9b288084a50c71164ffdf542d77027d0 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 16:08:29 +0000 Subject: [PATCH 05/23] Add missing parameter to get_history_object call --- logs/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logs/views.py b/logs/views.py index 0bf593c2..4c76158c 100644 --- a/logs/views.py +++ b/logs/views.py @@ -557,7 +557,7 @@ def detailed_history(request, object_name, object_id): raise Http404(_("No model found.")) # Get instance and check permissions - can_view, instance = get_history_object(model, object_name, object_id) + can_view, instance = get_history_object(request, model, object_name, object_id) if not can_view: return instance @@ -603,7 +603,7 @@ def history(request, application, object_name, object_id): except LookupError: raise Http404(_("No model found.")) - can_view, instance = get_history_object(model, object_name, object_id) + can_view, instance = get_history_object(get_history_object, model, object_name, object_id) if not can_view: return instance From c1bb37d23fd7376bbd5bb792385ba17f7f93ac57 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 16:16:26 +0000 Subject: [PATCH 06/23] Fix InterfaceHistory.get --- logs/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/logs/models.py b/logs/models.py index 0c52f885..6e520b93 100644 --- a/logs/models.py +++ b/logs/models.py @@ -457,22 +457,22 @@ class InterfaceHistoryEvent(HistoryEvent): pass -class InterfaceHistory: - def get(self, interface_id): +class InterfaceHistory(History): + def get(self, interface): """ - :param interface_id: Interface, the interface to lookup + :param interface: Interface, the interface to lookup :return: list or None, a list of InterfaceHistoryEvent, in reverse chronological order """ self.events = [] # Get all the versions for this interface, with the oldest first self._last_version = None - user_versions = filter( - lambda x: x.field_dict["id"] == interface_id, + interface_versions = filter( + lambda x: x.field_dict["id"] == interface.id, Version.objects.get_for_model(Interface).order_by("revision__date_created") ) - for version in user_versions: + for version in interface_versions: self._add_revision(version) return self.events[::-1] From 9959b7ad0a11150ea41f2faabd9a2a7f830df245 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 18:27:42 +0200 Subject: [PATCH 07/23] Add pretty representation of objects in InterfaceHistory --- logs/models.py | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/logs/models.py b/logs/models.py index 6e520b93..f6c9a10c 100644 --- a/logs/models.py +++ b/logs/models.py @@ -28,10 +28,12 @@ from django.contrib.auth.models import Group from machines.models import IpList from machines.models import Interface from machines.models import Machine +from machines.models import MachineType from users.models import User from users.models import Adherent from users.models import Club from topologie.models import Room +from topologie.models import Port class MachineHistorySearchEvent: @@ -350,10 +352,7 @@ class UserHistoryEvent(HistoryEvent): return ", ".join(users) - if value is None: - return _("None") - - return value + return super(UserHistoryEvent, self)._repr(name, value) def edits(self, hide=["password", "pwd_ntlm", "gpg_fingerprint"]): """ @@ -477,6 +476,41 @@ class InterfaceHistory(History): return self.events[::-1] + def _repr(self, name, value): + """ + Returns the best representation of the given field + :param name: the name of the field + :param value: the value of the field + :return: object + """ + if name == "ipv4_id" and value is not None: + try: + return IpList.objects.get(id=value) + except IpList.DoesNotExist: + return "{} ({})".format(_("Deleted"), value) + elif name == "machine_type_id": + try: + return MachineType.objects.get(id=value).name + except MachineType.DoesNotExist: + return "{} ({})".format(_("Deleted"), value) + elif name == "machine_id": + try: + return Machine.objects.get(id=value).get_name() or _("No name") + except Machine.DoesNotExist: + return "{} ({})".format(_("Deleted"), value) + elif name == "port_lists": + if len(value) == 0: + return _("None") + + ports = [] + for pid in value: + try: + ports.append(Port.objects.get(id=pid).pretty_name()) + except Group.DoesNotExist: + ports.append("{} ({})".format(_("Deleted"), pid)) + + return super(UserHistoryEvent, self)._repr(name, value) + def _add_revision(self, version): """ Add a new revision to the chronological order From 3efedf6bcb165b5fb1d126f2cfbec1da757e20fd Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 18:29:22 +0200 Subject: [PATCH 08/23] Fix _repr definition for InterfaceHistoryEvent --- logs/models.py | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/logs/models.py b/logs/models.py index f6c9a10c..53071637 100644 --- a/logs/models.py +++ b/logs/models.py @@ -453,29 +453,6 @@ class UserHistory(History): class InterfaceHistoryEvent(HistoryEvent): - pass - - -class InterfaceHistory(History): - def get(self, interface): - """ - :param interface: Interface, the interface to lookup - :return: list or None, a list of InterfaceHistoryEvent, in reverse chronological order - """ - self.events = [] - - # Get all the versions for this interface, with the oldest first - self._last_version = None - interface_versions = filter( - lambda x: x.field_dict["id"] == interface.id, - Version.objects.get_for_model(Interface).order_by("revision__date_created") - ) - - for version in interface_versions: - self._add_revision(version) - - return self.events[::-1] - def _repr(self, name, value): """ Returns the best representation of the given field @@ -509,7 +486,28 @@ class InterfaceHistory(History): except Group.DoesNotExist: ports.append("{} ({})".format(_("Deleted"), pid)) - return super(UserHistoryEvent, self)._repr(name, value) + return super(InterfaceHistoryEvent, self)._repr(name, value) + + +class InterfaceHistory(History): + def get(self, interface): + """ + :param interface: Interface, the interface to lookup + :return: list or None, a list of InterfaceHistoryEvent, in reverse chronological order + """ + self.events = [] + + # Get all the versions for this interface, with the oldest first + self._last_version = None + interface_versions = filter( + lambda x: x.field_dict["id"] == interface.id, + Version.objects.get_for_model(Interface).order_by("revision__date_created") + ) + + for version in interface_versions: + self._add_revision(version) + + return self.events[::-1] def _add_revision(self, version): """ From 1f5fa4b43e9b5da38d6331bb2b1a83a601b9d781 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 16:35:22 +0000 Subject: [PATCH 09/23] Fix backward compatibility for logs view --- logs/urls.py | 10 +++++----- logs/views.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/logs/urls.py b/logs/urls.py index eefe6a70..914761bf 100644 --- a/logs/urls.py +++ b/logs/urls.py @@ -37,6 +37,11 @@ urlpatterns = [ views.revert_action, name="revert-action", ), + url( + r"(?P\w+)/(?P\w+)/(?P[0-9]+)$", + views.history, + name="history", + ), url( r"(?P\w+)/(?P[0-9]+)$", views.detailed_history, @@ -46,10 +51,5 @@ urlpatterns = [ url(r"^stats_models/$", views.stats_models, name="stats-models"), url(r"^stats_users/$", views.stats_users, name="stats-users"), url(r"^stats_actions/$", views.stats_actions, name="stats-actions"), - url( - r"(?P\w+)/(?P\w+)/(?P[0-9]+)$", - views.history, - name="history", - ), url(r"^stats_search_machine/$", views.stats_search_machine_history, name="stats-search-machine"), ] diff --git a/logs/views.py b/logs/views.py index 4c76158c..153bfb34 100644 --- a/logs/views.py +++ b/logs/views.py @@ -603,7 +603,7 @@ def history(request, application, object_name, object_id): except LookupError: raise Http404(_("No model found.")) - can_view, instance = get_history_object(get_history_object, model, object_name, object_id) + can_view, instance = get_history_object(request, model, object_name, object_id) if not can_view: return instance From c4e034a5a6c5ef3c1817df8c1d7324cb3dba2907 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 18:43:34 +0200 Subject: [PATCH 10/23] Improve handling of detailed history button --- logs/templatetags/logs_extra.py | 3 ++- machines/templates/machines/aff_machines.html | 4 ++-- templates/buttons/history.html | 4 ++-- users/templates/users/profil.html | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/logs/templatetags/logs_extra.py b/logs/templatetags/logs_extra.py index c436c1fa..2e58cb67 100644 --- a/logs/templatetags/logs_extra.py +++ b/logs/templatetags/logs_extra.py @@ -42,7 +42,7 @@ def is_facture(baseinvoice): @register.inclusion_tag("buttons/history.html") -def history_button(instance, text=False, html_class=True): +def history_button(instance, text=False, detailed=False, html_class=True): """Creates the correct history button for an instance. Args: @@ -57,5 +57,6 @@ def history_button(instance, text=False, html_class=True): "name": instance._meta.model_name, "id": instance.id, "text": text, + "detailed": detailed, "class": html_class, } diff --git a/machines/templates/machines/aff_machines.html b/machines/templates/machines/aff_machines.html index 77b65546..857d2a3a 100644 --- a/machines/templates/machines/aff_machines.html +++ b/machines/templates/machines/aff_machines.html @@ -67,7 +67,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Create an interface" as tr_create_an_interface %} {% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc=tr_create_an_interface %} {% acl_end %} - {% history_button machine %} + {% history_button machine detailed=True %} {% can_delete machine %} {% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %} {% acl_end %} @@ -161,7 +161,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% acl_end %} - {% history_button interface %} + {% history_button interface detailed=True %} {% can_delete interface %} {% include 'buttons/suppr.html' with href='machines:del-interface' id=interface.id %} {% acl_end %} diff --git a/templates/buttons/history.html b/templates/buttons/history.html index f8eed813..04b0178f 100644 --- a/templates/buttons/history.html +++ b/templates/buttons/history.html @@ -24,8 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load i18n %} -{% if name == "user" %} - +{% if detailed %} + {% if text %}{% trans "History" %}{% endif %} {% else %} diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 4fec3c18..4fa780a7 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -176,7 +176,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% trans "Edit the groups" %} {% acl_end %} - {% history_button users text=True %} + {% history_button users text=True detailed=True %}
From fc352948ce17cc7b4d7c1f19d2024359b5d72952 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 18:44:44 +0200 Subject: [PATCH 11/23] Add missing parameter to detailed-history call --- templates/buttons/history.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/buttons/history.html b/templates/buttons/history.html index 04b0178f..2495dec8 100644 --- a/templates/buttons/history.html +++ b/templates/buttons/history.html @@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load i18n %} {% if detailed %} - + {% if text %}{% trans "History" %}{% endif %} {% else %} From 0d9e0ab867590112e03d6e72fe00afff90e5abcb Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 19:06:59 +0200 Subject: [PATCH 12/23] Handle machine history --- logs/models.py | 141 ++++++++++++++++++++++++++++++++++++------------- logs/views.py | 2 + 2 files changed, 107 insertions(+), 36 deletions(-) diff --git a/logs/models.py b/logs/models.py index 53071637..b6ddc692 100644 --- a/logs/models.py +++ b/logs/models.py @@ -214,6 +214,18 @@ class MachineHistorySearch: return self.events +class RelatedHistory: + def __init__(self, model_name, object_id, detailed=True): + """ + :param model_name: Name of the related model (e.g. "user") + :param object_id: ID of the related object + :param detailed: Whether the related history should be shown in an detailed view + """ + self.model_name = model_name + self.object_id = object_id + self.detailed = detailed + + class HistoryEvent: def __init__(self, version, previous_version=None, edited_fields=None): """ @@ -265,7 +277,28 @@ class HistoryEvent: class History: def __init__(self): self.events = [] + self.related = [] # For example, a machine has a list of its interfaces self._last_version = None + self.event_type = HistoryEvent + + def get(self, instance): + """ + :param interface: The instance to lookup + :return: list or None, a list of HistoryEvent, in reverse chronological order + """ + self.events = [] + + # Get all the versions for this interface, with the oldest first + self._last_version = None + interface_versions = filter( + lambda x: x.field_dict["id"] == instance.id, + Version.objects.get_for_model(type(instance)).order_by("revision__date_created") + ) + + for version in interface_versions: + self._add_revision(version) + + return self.events[::-1] def _compute_diff(self, v1, v2, ignoring=[]): """ @@ -283,6 +316,24 @@ class History: return fields + def _add_revision(self, version): + """ + Add a new revision to the chronological order + :param version: Version, The version of the interface for this event + """ + diff = None + if self._last_version is not None: + diff = self._compute_diff(version, self._last_version) + + # Ignore "empty" events + if not diff: + self._last_version = version + return + + evt = self.event_type(version, self._last_version, diff) + self.events.append(evt) + self._last_version = version + class UserHistoryEvent(HistoryEvent): def __init__(self, user, version, previous_version=None, edited_fields=None): @@ -386,6 +437,7 @@ class UserHistoryEvent(HistoryEvent): class UserHistory(History): def __init__(self): super(UserHistory, self).__init__() + self.event_type = UserHistoryEvent def get(self, user): """ @@ -400,6 +452,17 @@ class UserHistory(History): except Adherent.DoesNotExist: obj = Club.objects.get(user_ptr_id=user.id) + # Add as "related" histories the list of Machine objects + # that were once owned by this user + self.related = list(filter( + lambda x: x.field_dict["user_id"] == user.id, + Version.objects.get_for_model(Machine).order_by("revision__date_created") + )) + self.related = sorted( + list(dict.fromkeys(self.related)), + key=lambda r: r.model_name + ) + # Get all the versions for this user, with the oldest first self._last_version = None user_versions = filter( @@ -452,6 +515,45 @@ class UserHistory(History): self._last_version = version +class MachineHistoryEvent(HistoryEvent): + def _repr(self, name, value): + """ + Returns the best representation of the given field + :param name: the name of the field + :param value: the value of the field + :return: object + """ + if name == "user_id": + try: + return User.objects.get(id=value).pseudo + except User.DoesNotExist: + return "{} ({})".format(_("Deleted"), value) + + return super(MachineHistoryEvent, self)._repr(name, value) + + +class MachineHistory(History): + def __init__(self): + super(MachineHistory, self).__init__() + self.event_type = MachineHistoryEvent + + def get(self, machine): + super(MachineHistory, self).get(machine) + + # 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") + )) + + # Remove duplicates and sort + self.related = sorted( + list(dict.fromkeys(self.related)), + key=lambda r: r.model_name + ) + + class InterfaceHistoryEvent(HistoryEvent): def _repr(self, name, value): """ @@ -490,39 +592,6 @@ class InterfaceHistoryEvent(HistoryEvent): class InterfaceHistory(History): - def get(self, interface): - """ - :param interface: Interface, the interface to lookup - :return: list or None, a list of InterfaceHistoryEvent, in reverse chronological order - """ - self.events = [] - - # Get all the versions for this interface, with the oldest first - self._last_version = None - interface_versions = filter( - lambda x: x.field_dict["id"] == interface.id, - Version.objects.get_for_model(Interface).order_by("revision__date_created") - ) - - for version in interface_versions: - self._add_revision(version) - - return self.events[::-1] - - def _add_revision(self, version): - """ - Add a new revision to the chronological order - :param version: Version, The version of the interface for this event - """ - diff = None - if self._last_version is not None: - diff = self._compute_diff(version, self._last_version) - - # Ignore "empty" events - if not diff: - self._last_version = version - return - - evt = InterfaceHistoryEvent(version, self._last_version, diff) - self.events.append(evt) - self._last_version = version + def __init__(self): + super(InterfaceHistory, self).__init__() + self.event_type = InterfaceHistoryEvent diff --git a/logs/views.py b/logs/views.py index 153bfb34..a4396158 100644 --- a/logs/views.py +++ b/logs/views.py @@ -104,6 +104,7 @@ from re2o.acl import can_view_all, can_view_app, can_edit_history, can_view from .models import ( MachineHistorySearch, UserHistory, + MachineHistory, InterfaceHistory ) @@ -550,6 +551,7 @@ def detailed_history(request, object_name, object_id): history = UserHistory() elif object_name == "machine": model = Machine + history = MachineHistory() elif object_name == "interface": model = Interface history = InterfaceHistory() From 063e6b48bdd417e735299fdfb2d957fd62da50da Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 19:24:28 +0200 Subject: [PATCH 13/23] Displayed related history suggestions in detailed history view --- logs/models.py | 36 +++++++++++++---------- logs/templates/logs/detailed_history.html | 23 ++++++++++++++- logs/views.py | 2 +- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/logs/models.py b/logs/models.py index b6ddc692..e48b0dd1 100644 --- a/logs/models.py +++ b/logs/models.py @@ -215,16 +215,26 @@ class MachineHistorySearch: class RelatedHistory: - def __init__(self, model_name, object_id, detailed=True): + def __init__(self, name, instance, detailed=True): """ :param model_name: Name of the related model (e.g. "user") :param object_id: ID of the related object :param detailed: Whether the related history should be shown in an detailed view """ - self.model_name = model_name - self.object_id = object_id + self.name = name + self.instance = instance self.detailed = detailed + def __eq__(self, other): + return ( + self.name == other.name + and self.instance.id == other.instance.id + and self.detailed == other.detailed + ) + + def __hash__(self): + return hash((self.name, self.instance.id, self.detailed)) + class HistoryEvent: def __init__(self, version, previous_version=None, edited_fields=None): @@ -454,14 +464,12 @@ class UserHistory(History): # Add as "related" histories the list of Machine objects # that were once owned by this user - self.related = list(filter( + 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 = sorted( - list(dict.fromkeys(self.related)), - key=lambda r: r.model_name ) + self.related = [RelatedHistory(m.get_name(), m) for m in self.related] + self.related = list(dict.fromkeys(self.related)) # Get all the versions for this user, with the oldest first self._last_version = None @@ -538,8 +546,6 @@ class MachineHistory(History): self.event_type = MachineHistoryEvent def get(self, machine): - super(MachineHistory, self).get(machine) - # Add as "related" histories the list of Interface objects # that were once assigned to this machine self.related = list(filter( @@ -547,11 +553,11 @@ class MachineHistory(History): Version.objects.get_for_model(Interface).order_by("revision__date_created") )) - # Remove duplicates and sort - self.related = sorted( - list(dict.fromkeys(self.related)), - key=lambda r: r.model_name - ) + # Create RelatedHistory objects and remove duplicates + self.related = [RelatedHistory(i.mac_address, i) for i in self.related] + self.related = list(dict.fromkeys(self.related)) + + return super(MachineHistory, self).get(machine) class InterfaceHistoryEvent(HistoryEvent): diff --git a/logs/templates/logs/detailed_history.html b/logs/templates/logs/detailed_history.html index df0f0162..53302451 100644 --- a/logs/templates/logs/detailed_history.html +++ b/logs/templates/logs/detailed_history.html @@ -1,4 +1,3 @@ -{% 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 @@ -73,6 +72,28 @@ with this program; if not, write to the Free Software Foundation, Inc., {% else %}

{% trans "No event" %}

{% endif %} + +

{% trans Related history %}

+ +{% if related_history %} +
+ + + + + + + {% for related in related_history %} + + + + + {% endfor %} +
{% trans "ID" %}{% trans "Actions" %}
{{ related.name }}{% history_button related.instance text=True detailed=related.detailed %}
+ {% include 'pagination.html' with list=events %} +{% else %} +

{% trans "No related history" %}

+{% endif %}


diff --git a/logs/views.py b/logs/views.py index a4396158..54432794 100644 --- a/logs/views.py +++ b/logs/views.py @@ -574,7 +574,7 @@ def detailed_history(request, object_name, object_id): return render( request, "logs/detailed_history.html", - {"object": instance, "events": events}, + {"object": instance, "events": events, "related_history": history.related}, ) From 6c3d6082b12db30f92e300ffcfc2d96b5c95a916 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 17:46:25 +0000 Subject: [PATCH 14/23] Fix dispalying related history in detailed history view --- logs/models.py | 4 ++-- logs/templates/logs/detailed_history.html | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/logs/models.py b/logs/models.py index e48b0dd1..d2f0b5a9 100644 --- a/logs/models.py +++ b/logs/models.py @@ -468,7 +468,7 @@ class UserHistory(History): lambda x: x.field_dict["user_id"] == user.id, Version.objects.get_for_model(Machine).order_by("revision__date_created") ) - self.related = [RelatedHistory(m.get_name(), m) for m in self.related] + self.related = [RelatedHistory(m.field_dict["name"] or _("None"), m) for m in self.related] self.related = list(dict.fromkeys(self.related)) # Get all the versions for this user, with the oldest first @@ -554,7 +554,7 @@ class MachineHistory(History): )) # Create RelatedHistory objects and remove duplicates - self.related = [RelatedHistory(i.mac_address, i) for i in self.related] + self.related = [RelatedHistory(i.field_dict["mac_address"], i) for i in self.related] self.related = list(dict.fromkeys(self.related)) return super(MachineHistory, self).get(machine) diff --git a/logs/templates/logs/detailed_history.html b/logs/templates/logs/detailed_history.html index 53302451..34da370c 100644 --- a/logs/templates/logs/detailed_history.html +++ b/logs/templates/logs/detailed_history.html @@ -1,3 +1,4 @@ +{% 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 @@ -22,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% load bootstrap3 %} {% load i18n %} +{% load logs_extra %} {% block title %}{% trans "History" %}{% endblock %} @@ -73,20 +75,20 @@ with this program; if not, write to the Free Software Foundation, Inc.,

{% trans "No event" %}

{% endif %} -

{% trans Related history %}

+

{% trans "Related history" %}

{% if related_history %} - + {% for related in related_history %} + - {% endfor %}
{% trans "ID" %} {% trans "Actions" %}{% trans "ID" %}
{% history_button related.instance detailed=related.detailed %} {{ related.name }}{% history_button related.instance text=True detailed=related.detailed %}
From 8469d758ce98cc5b5eb09bf67ebe9f99a26e9ab5 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 20:00:38 +0200 Subject: [PATCH 15/23] Make related history list nicer --- logs/models.py | 26 ++++++++++++++--------- logs/templates/logs/detailed_history.html | 18 +++++----------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/logs/models.py b/logs/models.py index d2f0b5a9..3b088b87 100644 --- a/logs/models.py +++ b/logs/models.py @@ -215,25 +215,25 @@ class MachineHistorySearch: class RelatedHistory: - def __init__(self, name, instance, detailed=True): + def __init__(self, name, model_name, object_id): """ + :param name: Name of this instance :param model_name: Name of the related model (e.g. "user") :param object_id: ID of the related object - :param detailed: Whether the related history should be shown in an detailed view """ - self.name = name - self.instance = instance - self.detailed = detailed + self.name = "{} (id = {})".format(name, object_id) + self.model_name = model_name + self.object_id = object_id def __eq__(self, other): return ( self.name == other.name - and self.instance.id == other.instance.id - and self.detailed == other.detailed + and self.model_name == other.model_name + and self.object_id == other.object_id ) def __hash__(self): - return hash((self.name, self.instance.id, self.detailed)) + return hash((self.name, self.model_name, self.object_id)) class HistoryEvent: @@ -468,7 +468,10 @@ class UserHistory(History): lambda x: x.field_dict["user_id"] == user.id, Version.objects.get_for_model(Machine).order_by("revision__date_created") ) - self.related = [RelatedHistory(m.field_dict["name"] or _("None"), m) for m in self.related] + self.related = [RelatedHistory( + m.field_dict["name"] or _("None"), + "machine", + m.field_dict["id"]) for m in self.related] self.related = list(dict.fromkeys(self.related)) # Get all the versions for this user, with the oldest first @@ -554,7 +557,10 @@ class MachineHistory(History): )) # Create RelatedHistory objects and remove duplicates - self.related = [RelatedHistory(i.field_dict["mac_address"], i) for i in self.related] + self.related = [RelatedHistory( + i.field_dict["mac_address"] or _("None"), + "interface", + i.field_dict["id"]) for i in self.related] self.related = list(dict.fromkeys(self.related)) return super(MachineHistory, self).get(machine) diff --git a/logs/templates/logs/detailed_history.html b/logs/templates/logs/detailed_history.html index 34da370c..b862448d 100644 --- a/logs/templates/logs/detailed_history.html +++ b/logs/templates/logs/detailed_history.html @@ -78,21 +78,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,

{% trans "Related history" %}

{% if related_history %} - - - - - - - +
    {% for related in related_history %} -
- - - +
  • + {{ related.name }} +
  • {% endfor %} -
    {% trans "Actions" %}{% trans "ID" %}
    {% history_button related.instance detailed=related.detailed %}{{ related.name }}
    - {% include 'pagination.html' with list=events %} + {% else %}

    {% trans "No related history" %}

    {% endif %} From a3d31f5c4b64b1f56f98455882db1b6c6e0d2e84 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 18:09:39 +0000 Subject: [PATCH 16/23] Make related history in detailed history view nicer --- logs/models.py | 9 ++++----- logs/templates/logs/detailed_history.html | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/logs/models.py b/logs/models.py index 3b088b87..e3f0cba8 100644 --- a/logs/models.py +++ b/logs/models.py @@ -227,13 +227,12 @@ class RelatedHistory: def __eq__(self, other): return ( - self.name == other.name - and self.model_name == other.model_name + self.model_name == other.model_name and self.object_id == other.object_id ) def __hash__(self): - return hash((self.name, self.model_name, self.object_id)) + return hash((self.model_name, self.object_id)) class HistoryEvent: @@ -466,7 +465,7 @@ class UserHistory(History): # 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") + Version.objects.get_for_model(Machine).order_by("-revision__date_created") ) self.related = [RelatedHistory( m.field_dict["name"] or _("None"), @@ -553,7 +552,7 @@ class MachineHistory(History): # 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") + Version.objects.get_for_model(Interface).order_by("-revision__date_created") )) # Create RelatedHistory objects and remove duplicates diff --git a/logs/templates/logs/detailed_history.html b/logs/templates/logs/detailed_history.html index b862448d..26bae90c 100644 --- a/logs/templates/logs/detailed_history.html +++ b/logs/templates/logs/detailed_history.html @@ -75,18 +75,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,

    {% trans "No event" %}

    {% endif %} -

    {% trans "Related history" %}

    - {% if related_history %} + +

    {% blocktrans %}Related elements{% endblocktrans %}

    + -{% else %} -

    {% trans "No related history" %}

    {% endif %}

    From 4731e7da02af5795a3c48c5957bf695dd2796e78 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 18:13:37 +0000 Subject: [PATCH 17/23] Add missing translations for detailed history --- api/locale/fr/LC_MESSAGES/django.po | 2 +- cotisations/locale/fr/LC_MESSAGES/django.po | 2 +- logs/locale/fr/LC_MESSAGES/django.po | 142 ++++++----- machines/locale/fr/LC_MESSAGES/django.po | 2 +- multi_op/locale/fr/LC_MESSAGES/django.po | 2 +- preferences/locale/fr/LC_MESSAGES/django.po | 254 ++++++++++---------- re2o/locale/fr/LC_MESSAGES/django.po | 2 +- search/locale/fr/LC_MESSAGES/django.po | 2 +- templates/locale/fr/LC_MESSAGES/django.po | 5 +- tickets/locale/fr/LC_MESSAGES/django.po | 2 +- topologie/locale/fr/LC_MESSAGES/django.po | 2 +- users/locale/fr/LC_MESSAGES/django.po | 182 +++++++------- 12 files changed, 311 insertions(+), 288 deletions(-) diff --git a/api/locale/fr/LC_MESSAGES/django.po b/api/locale/fr/LC_MESSAGES/django.po index f5f9153f..6b5b3c75 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+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 ac3900ea..674a417c 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+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 2c5265fc..e9310b54 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+0200\n" "PO-Revision-Date: 2018-06-23 16:01+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -58,20 +58,26 @@ msgstr "Date de début" msgid "End date" msgstr "Date de fin" -#: logs/models.py:238 logs/models.py:263 +#: logs/models.py:260 logs/models.py:368 logs/models.py:401 logs/models.py:471 +#: logs/models.py:560 logs/models.py:593 msgid "None" msgstr "Aucun(e)" -#: logs/models.py:248 +#: logs/models.py:378 logs/models.py:397 logs/models.py:411 logs/models.py:540 +#: logs/models.py:580 logs/models.py:585 logs/models.py:590 logs/models.py:600 msgid "Deleted" msgstr "Supprimé(e)" -#: logs/models.py:255 logs/models.py:260 +#: logs/models.py:385 logs/models.py:390 +#: logs/templates/logs/detailed_history.html:52 #: logs/templates/logs/machine_history.html:55 -#: logs/templates/logs/user_history.html:51 msgid "Unknown" msgstr "Inconnu(e)" +#: logs/models.py:588 +msgid "No name" +msgstr "Sans nom" + #: logs/templates/logs/aff_stats_logs.html:36 msgid "Edited object" msgstr "Objet modifié" @@ -90,8 +96,8 @@ msgid "Date of editing" msgstr "Date de modification" #: logs/templates/logs/aff_stats_logs.html:42 +#: logs/templates/logs/detailed_history.html:40 #: logs/templates/logs/machine_history.html:39 -#: logs/templates/logs/user_history.html:39 msgid "Comment" msgstr "Commentaire" @@ -128,7 +134,7 @@ msgid "Rank" msgstr "Rang" #: logs/templates/logs/aff_summary.html:37 -#: logs/templates/logs/user_history.html:36 +#: logs/templates/logs/detailed_history.html:37 msgid "Date" msgstr "Date" @@ -192,6 +198,31 @@ 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 +msgid "History of %(object)s" +msgstr "Historique de %(object)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" + +#: logs/templates/logs/detailed_history.html:80 +msgid "Related elements" +msgstr "Élements liés" + #: logs/templates/logs/index.html:29 logs/templates/logs/stats_general.html:29 #: logs/templates/logs/stats_logs.html:29 #: logs/templates/logs/stats_models.html:29 @@ -200,7 +231,7 @@ msgid "Statistics" msgstr "Statistiques" #: logs/templates/logs/index.html:32 logs/templates/logs/stats_logs.html:32 -#: logs/views.py:421 +#: logs/views.py:427 msgid "Actions performed" msgstr "Actions effectuées" @@ -265,159 +296,138 @@ msgstr "Statistiques sur la base de données" msgid "Statistics about users" msgstr "Statistiques sur les utilisateurs" -#: logs/templates/logs/user_history.html:27 -msgid "History" -msgstr "Historique" - -#: logs/templates/logs/user_history.html:30 -#, python-format -msgid "History of %(user)s" -msgstr "Historique de %(user)s" - -#: logs/templates/logs/user_history.html:37 -msgid "Performed by" -msgstr "Effectué(e) par" - -#: logs/templates/logs/user_history.html:38 -msgid "Edited" -msgstr "Modifié" - -#: logs/templates/logs/user_history.html:74 -msgid "No event" -msgstr "Aucun évènement" - -#: logs/views.py:178 +#: logs/views.py:184 msgid "Nonexistent revision." msgstr "Révision inexistante." -#: logs/views.py:181 +#: logs/views.py:187 msgid "The action was deleted." msgstr "L'action a été supprimée." -#: logs/views.py:222 +#: logs/views.py:228 msgid "Category" msgstr "Catégorie" -#: logs/views.py:223 +#: logs/views.py:229 msgid "Number of users (members and clubs)" msgstr "Nombre d'utilisateurs (adhérents et clubs)" -#: logs/views.py:224 +#: logs/views.py:230 msgid "Number of members" msgstr "Nombre d'adhérents" -#: logs/views.py:225 +#: logs/views.py:231 msgid "Number of clubs" msgstr "Nombre de clubs" -#: logs/views.py:229 +#: logs/views.py:235 msgid "Activated users" msgstr "Utilisateurs activés" -#: logs/views.py:235 +#: logs/views.py:241 msgid "Disabled users" msgstr "Utilisateurs désactivés" -#: logs/views.py:241 +#: logs/views.py:247 msgid "Archived users" msgstr "Utilisateurs archivés" -#: logs/views.py:247 +#: logs/views.py:253 msgid "Fully archived users" msgstr "Utilisateurs complètement archivés" -#: logs/views.py:257 +#: logs/views.py:263 msgid "Not yet active users" msgstr "Utilisateurs pas encore actifs" -#: logs/views.py:267 +#: logs/views.py:273 msgid "Contributing members" msgstr "Adhérents cotisants" -#: logs/views.py:273 +#: logs/views.py:279 msgid "Users benefiting from a connection" msgstr "Utilisateurs bénéficiant d'une connexion" -#: logs/views.py:279 +#: logs/views.py:285 msgid "Banned users" msgstr "Utilisateurs bannis" -#: logs/views.py:285 +#: logs/views.py:291 msgid "Users benefiting from a free connection" msgstr "Utilisateurs bénéficiant d'une connexion gratuite" -#: logs/views.py:291 +#: logs/views.py:297 msgid "Users with a confirmed email" msgstr "Utilisateurs ayant un mail confirmé" -#: logs/views.py:297 +#: logs/views.py:303 msgid "Users with an unconfirmed email" msgstr "Utilisateurs ayant un mail non confirmé" -#: logs/views.py:303 +#: logs/views.py:309 msgid "Users pending email confirmation" msgstr "Utilisateurs en attente de confirmation du mail" -#: logs/views.py:309 +#: logs/views.py:315 msgid "Active interfaces (with access to the network)" msgstr "Interfaces actives (ayant accès au réseau)" -#: logs/views.py:323 +#: logs/views.py:329 msgid "Active interfaces assigned IPv4" msgstr "Interfaces actives assignées IPv4" -#: logs/views.py:340 +#: logs/views.py:346 msgid "IP range" msgstr "Plage d'IP" -#: logs/views.py:341 +#: logs/views.py:347 msgid "VLAN" msgstr "VLAN" -#: logs/views.py:342 +#: logs/views.py:348 msgid "Total number of IP addresses" msgstr "Nombre total d'adresses IP" -#: logs/views.py:343 +#: logs/views.py:349 msgid "Number of assigned IP addresses" msgstr "Nombre d'adresses IP assignées" -#: logs/views.py:344 +#: logs/views.py:350 msgid "Number of IP address assigned to an activated machine" msgstr "Nombre d'adresses IP assignées à une machine activée" -#: logs/views.py:345 +#: logs/views.py:351 msgid "Number of unassigned IP addresses" msgstr "Nombre d'adresses IP non assignées" -#: logs/views.py:360 +#: logs/views.py:366 msgid "Users (members and clubs)" msgstr "Utilisateurs (adhérents et clubs)" -#: logs/views.py:406 +#: logs/views.py:412 msgid "Topology" msgstr "Topologie" -#: logs/views.py:422 +#: logs/views.py:428 msgid "Number of actions" msgstr "Nombre d'actions" -#: logs/views.py:447 +#: logs/views.py:453 msgid "rights" msgstr "droits" -#: logs/views.py:476 +#: logs/views.py:482 msgid "actions" msgstr "actions" -#: logs/views.py:554 -msgid "No model found." -msgstr "Aucun modèle trouvé." - -#: logs/views.py:560 +#: logs/views.py:526 msgid "Nonexistent entry." msgstr "Entrée inexistante." -#: logs/views.py:567 +#: logs/views.py:534 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:559 logs/views.py:606 +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 5013ea49..81109ef4 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+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 f4daa664..026a1da2 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+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 2f109979..00ea2a38 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+0200\n" "PO-Revision-Date: 2018-06-24 15:54+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -342,13 +342,13 @@ msgid "Maximum number of local email addresses for a standard user." msgstr "" "Nombre maximum d'adresses mail locales autorisé pour un utilisateur standard." -#: preferences/models.py:122 +#: preferences/models.py:125 msgid "Not yet active users will be deleted after this number of days." msgstr "" "Les utilisateurs n'ayant jamais adhéré seront supprimés après ce nombre de " "jours." -#: preferences/models.py:127 +#: preferences/models.py:130 msgid "" "Users with an email address not yet confirmed will be disabled after this " "number of days." @@ -356,11 +356,11 @@ msgstr "" "Les utilisateurs n'ayant pas confirmé leur addresse mail seront désactivés " "après ce nombre de jours" -#: preferences/models.py:131 +#: preferences/models.py:134 msgid "A new user can create their account on Re2o." msgstr "Un nouvel utilisateur peut créer son compte sur Re2o." -#: preferences/models.py:136 +#: preferences/models.py:139 msgid "" "If True, all new created and connected users are active. If False, only when " "a valid registration has been paid." @@ -368,7 +368,7 @@ msgstr "" "Si True, tous les nouveaux utilisations créés et connectés sont actifs. Si " "False, seulement quand une inscription validée a été payée." -#: preferences/models.py:143 +#: preferences/models.py:146 msgid "" "If True, users have the choice to receive an email containing a link to " "reset their password during creation, or to directly set their password in " @@ -379,172 +379,172 @@ msgstr "" "de choisir leur mot de passe immédiatement. Si False, un mail est toujours " "envoyé." -#: preferences/models.py:150 +#: preferences/models.py:153 msgid "If True, archived users are allowed to connect." msgstr "Si True, les utilisateurs archivés sont autorisés à se connecter." -#: preferences/models.py:154 +#: preferences/models.py:157 msgid "Can view the user preferences" msgstr "Peut voir les préférences d'utilisateur" -#: preferences/models.py:155 +#: preferences/models.py:158 msgid "user preferences" msgstr "Préférences d'utilisateur" -#: preferences/models.py:162 +#: preferences/models.py:165 msgid "Email domain must begin with @." msgstr "Un domaine mail doit commencer par @." -#: preferences/models.py:180 +#: preferences/models.py:183 msgid "Automatic configuration by RA" msgstr "Configuration automatique par RA" -#: preferences/models.py:181 +#: preferences/models.py:184 msgid "IP addresses assignment by DHCPv6" msgstr "Attribution d'adresses IP par DHCPv6" -#: preferences/models.py:182 +#: preferences/models.py:185 msgid "Disabled" msgstr "Désactivé" -#: preferences/models.py:191 +#: preferences/models.py:194 msgid "default Time To Live (TTL) for CNAME, A and AAAA records" msgstr "" "Temps de vie (TTL) par défault pour des enregistrements CNAME, A et AAAA" -#: preferences/models.py:201 +#: preferences/models.py:204 msgid "Can view the machine preferences" msgstr "Peut voir les préférences de machine" -#: preferences/models.py:202 +#: preferences/models.py:205 msgid "machine preferences" msgstr "Préférences de machine" -#: preferences/models.py:222 preferences/models.py:684 +#: preferences/models.py:225 preferences/models.py:687 msgid "On the IP range's VLAN of the machine" msgstr "Sur le VLAN de la plage d'IP de la machine" -#: preferences/models.py:223 preferences/models.py:685 +#: preferences/models.py:226 preferences/models.py:688 msgid "Preset in \"VLAN for machines accepted by RADIUS\"" msgstr "Prédéfinie dans « VLAN pour les machines acceptées par RADIUS »" -#: preferences/models.py:229 +#: preferences/models.py:232 msgid "Web management, activated in case of automatic provision." msgstr "Gestion web, activée en cas de provision automatique." -#: preferences/models.py:234 +#: preferences/models.py:237 msgid "" "SSL web management, make sure that a certificate is installed on the switch." msgstr "" "Gestion web SSL, vérifiez qu'un certificat est installé sur le commutateur " "réseau." -#: preferences/models.py:240 +#: preferences/models.py:243 msgid "REST management, activated in case of automatic provision." msgstr "Gestion REST, activée en cas de provision automatique." -#: preferences/models.py:247 +#: preferences/models.py:250 msgid "IP range for the management of switches." msgstr "Plage d'IP pour la gestion des commutateurs réseau." -#: preferences/models.py:253 +#: preferences/models.py:256 msgid "Provision of configuration mode for switches." msgstr "Mode de provision de configuration pour les commutateurs réseau." -#: preferences/models.py:256 +#: preferences/models.py:259 msgid "SFTP login for switches." msgstr "Identifiant SFTP pour les commutateurs réseau." -#: preferences/models.py:259 +#: preferences/models.py:262 msgid "SFTP password." msgstr "Mot de passe SFTP." -#: preferences/models.py:364 +#: preferences/models.py:367 msgid "Can view the topology preferences" msgstr "Peut voir les préférences de topologie" -#: preferences/models.py:366 +#: preferences/models.py:369 msgid "topology preferences" msgstr "préférences de topologie" -#: preferences/models.py:379 +#: preferences/models.py:382 msgid "RADIUS key." msgstr "Clé RADIUS." -#: preferences/models.py:381 +#: preferences/models.py:384 msgid "Comment for this key." msgstr "Commentaire pour cette clé." -#: preferences/models.py:384 +#: preferences/models.py:387 msgid "Default key for switches." msgstr "Clé par défaut pour les commutateurs réseau." -#: preferences/models.py:388 +#: preferences/models.py:391 msgid "Can view a RADIUS key object" msgstr "Peut voir un objet clé RADIUS" -#: preferences/models.py:389 preferences/views.py:335 +#: preferences/models.py:392 preferences/views.py:335 msgid "RADIUS key" msgstr "Clé RADIUS" -#: preferences/models.py:390 -#: preferences/templates/preferences/display_preferences.html:221 +#: preferences/models.py:393 +#: preferences/templates/preferences/display_preferences.html:223 msgid "RADIUS keys" msgstr "clés RADIUS" -#: preferences/models.py:397 +#: preferences/models.py:400 msgid "Default RADIUS key for switches already exists." msgstr "Clé par défaut pour les commutateurs réseau." -#: preferences/models.py:400 +#: preferences/models.py:403 msgid "RADIUS key " msgstr "clé RADIUS " -#: preferences/models.py:406 +#: preferences/models.py:409 msgid "Switch login." msgstr "Identifiant du commutateur réseau." -#: preferences/models.py:407 +#: preferences/models.py:410 msgid "Password." msgstr "Mot de passe." -#: preferences/models.py:409 +#: preferences/models.py:412 msgid "Default credentials for switches." msgstr "Identifiants par défaut pour les commutateurs réseau." -#: preferences/models.py:416 +#: preferences/models.py:419 msgid "Can view a switch management credentials object" msgstr "Peut voir un objet identifiants de gestion de commutateur réseau" -#: preferences/models.py:419 preferences/views.py:397 +#: preferences/models.py:422 preferences/views.py:397 msgid "switch management credentials" msgstr "identifiants de gestion de commutateur réseau" -#: preferences/models.py:422 +#: preferences/models.py:425 msgid "Switch login " msgstr "Identifiant du commutateur réseau " -#: preferences/models.py:434 +#: preferences/models.py:437 msgid "Delay between the email and the membership's end." msgstr "Délai entre le mail et la fin d'adhésion." -#: preferences/models.py:440 +#: preferences/models.py:443 msgid "Message displayed specifically for this reminder." msgstr "Message affiché spécifiquement pour ce rappel." -#: preferences/models.py:444 +#: preferences/models.py:447 msgid "Can view a reminder object" msgstr "Peut voir un objet rappel" -#: preferences/models.py:445 preferences/views.py:280 +#: preferences/models.py:448 preferences/views.py:280 msgid "reminder" msgstr "rappel" -#: preferences/models.py:446 +#: preferences/models.py:449 msgid "reminders" msgstr "rappels" -#: preferences/models.py:467 +#: preferences/models.py:470 msgid "" "General message displayed on the French version of the website (e.g. in case " "of maintenance)." @@ -552,7 +552,7 @@ msgstr "" "Message général affiché sur la version française du site (ex : en cas de " "maintenance)." -#: preferences/models.py:475 +#: preferences/models.py:478 msgid "" "General message displayed on the English version of the website (e.g. in " "case of maintenance)." @@ -560,75 +560,75 @@ msgstr "" "Message général affiché sur la version anglaise du site (ex : en cas de " "maintenance)." -#: preferences/models.py:490 +#: preferences/models.py:493 msgid "Can view the general preferences" msgstr "Peut voir les préférences générales" -#: preferences/models.py:491 +#: preferences/models.py:494 msgid "general preferences" msgstr "préférences générales" -#: preferences/models.py:511 +#: preferences/models.py:514 msgid "Can view the service preferences" msgstr "Peut voir les préférences de service" -#: preferences/models.py:512 preferences/views.py:231 +#: preferences/models.py:515 preferences/views.py:231 msgid "service" msgstr "service" -#: preferences/models.py:513 +#: preferences/models.py:516 msgid "services" msgstr "services" -#: preferences/models.py:523 +#: preferences/models.py:526 msgid "Contact email address." msgstr "Adresse mail de contact." -#: preferences/models.py:529 +#: preferences/models.py:532 msgid "Description of the associated email address." msgstr "Description de l'adresse mail associée." -#: preferences/models.py:539 +#: preferences/models.py:542 msgid "Can view a contact email address object" msgstr "Peut voir un objet adresse mail de contact" -#: preferences/models.py:541 +#: preferences/models.py:544 msgid "contact email address" msgstr "adresse mail de contact" -#: preferences/models.py:542 +#: preferences/models.py:545 msgid "contact email addresses" msgstr "adresses mail de contact" -#: preferences/models.py:550 preferences/views.py:635 +#: preferences/models.py:553 preferences/views.py:635 msgid "mandate" msgstr "mandat" -#: preferences/models.py:551 +#: preferences/models.py:554 msgid "mandates" msgstr "mandats" -#: preferences/models.py:552 +#: preferences/models.py:555 msgid "Can view a mandate object" msgstr "Peut voir un objet mandat" -#: preferences/models.py:559 +#: preferences/models.py:562 msgid "president of the association" msgstr "président de l'association" -#: preferences/models.py:560 +#: preferences/models.py:563 msgid "Displayed on subscription vouchers." msgstr "Affiché sur les reçus de cotisation." -#: preferences/models.py:562 +#: preferences/models.py:565 msgid "start date" msgstr "date de début" -#: preferences/models.py:563 +#: preferences/models.py:566 msgid "end date" msgstr "date de fin" -#: preferences/models.py:577 +#: preferences/models.py:580 msgid "" "No mandates have been created. Please go to the preferences page to create " "one." @@ -636,140 +636,140 @@ msgstr "" "Aucun mandat n'a été créé. Veuillez vous rendre sur la page de préférences " "pour en créer un." -#: preferences/models.py:593 +#: preferences/models.py:596 msgid "Networking organisation school Something" msgstr "Association de réseau de l'école Machin" -#: preferences/models.py:596 +#: preferences/models.py:599 msgid "Threadneedle Street" msgstr "1 rue de la Vrillière" -#: preferences/models.py:597 +#: preferences/models.py:600 msgid "London EC2R 8AH" msgstr "75001 Paris" -#: preferences/models.py:600 +#: preferences/models.py:603 msgid "Organisation" msgstr "Association" -#: preferences/models.py:607 +#: preferences/models.py:610 msgid "Can view the organisation preferences" msgstr "Peut voir les préférences d'association" -#: preferences/models.py:608 +#: preferences/models.py:611 msgid "organisation preferences" msgstr "préférences d'association" -#: preferences/models.py:626 +#: preferences/models.py:629 msgid "Can view the homepage preferences" msgstr "Peut voir les préférences de page d'accueil" -#: preferences/models.py:627 +#: preferences/models.py:630 msgid "homepage preferences" msgstr "Préférences de page d'accueil" -#: preferences/models.py:641 +#: preferences/models.py:644 msgid "Welcome email in French." msgstr "Mail de bienvenue en français." -#: preferences/models.py:644 +#: preferences/models.py:647 msgid "Welcome email in English." msgstr "Mail de bienvenue en anglais." -#: preferences/models.py:649 +#: preferences/models.py:652 msgid "Can view the email message preferences" msgstr "Peut voir les préférences de message pour les mails" -#: preferences/models.py:651 +#: preferences/models.py:654 msgid "email message preferences" msgstr "préférences de messages pour les mails" -#: preferences/models.py:656 +#: preferences/models.py:659 msgid "RADIUS attribute" msgstr "attribut RADIUS" -#: preferences/models.py:657 +#: preferences/models.py:660 msgid "RADIUS attributes" msgstr "attributs RADIUS" -#: preferences/models.py:661 preferences/views.py:588 +#: preferences/models.py:664 preferences/views.py:588 msgid "attribute" msgstr "attribut" -#: preferences/models.py:662 +#: preferences/models.py:665 msgid "See https://freeradius.org/rfc/attributes.html." msgstr "Voir https://freeradius.org/rfc/attributes.html." -#: preferences/models.py:664 +#: preferences/models.py:667 msgid "value" msgstr "valeur" -#: preferences/models.py:666 +#: preferences/models.py:669 msgid "comment" msgstr "commentaire" -#: preferences/models.py:667 +#: preferences/models.py:670 msgid "Use this field to document this attribute." msgstr "Utilisez ce champ pour documenter cet attribut." -#: preferences/models.py:678 +#: preferences/models.py:681 msgid "RADIUS policy" msgstr "politique de RADIUS" -#: preferences/models.py:679 -#: preferences/templates/preferences/display_preferences.html:299 +#: preferences/models.py:682 +#: preferences/templates/preferences/display_preferences.html:301 msgid "RADIUS policies" msgstr "politiques de RADIUS" -#: preferences/models.py:690 +#: preferences/models.py:693 msgid "Reject the machine" msgstr "Rejeter la machine" -#: preferences/models.py:691 +#: preferences/models.py:694 msgid "Place the machine on the VLAN" msgstr "Placer la machine sur le VLAN" -#: preferences/models.py:700 +#: preferences/models.py:703 msgid "policy for unknown machines" msgstr "politique pour les machines inconnues" -#: preferences/models.py:708 +#: preferences/models.py:711 msgid "unknown machines VLAN" msgstr "VLAN pour les machines inconnues" -#: preferences/models.py:709 +#: preferences/models.py:712 msgid "VLAN for unknown machines if not rejected." msgstr "VLAN pour les machines inconnues si non rejeté." -#: preferences/models.py:715 +#: preferences/models.py:718 msgid "unknown machines attributes" msgstr "attributs pour les machines inconnues" -#: preferences/models.py:716 +#: preferences/models.py:719 msgid "Answer attributes for unknown machines." msgstr "Attributs de réponse pour les machines inconnues." -#: preferences/models.py:722 +#: preferences/models.py:725 msgid "policy for unknown ports" msgstr "politique pour les ports inconnus" -#: preferences/models.py:730 +#: preferences/models.py:733 msgid "unknown ports VLAN" msgstr "VLAN pour les ports inconnus" -#: preferences/models.py:731 +#: preferences/models.py:734 msgid "VLAN for unknown ports if not rejected." msgstr "VLAN pour les ports inconnus si non rejeté." -#: preferences/models.py:737 +#: preferences/models.py:740 msgid "unknown ports attributes" msgstr "attributs pour les ports inconnus" -#: preferences/models.py:738 +#: preferences/models.py:741 msgid "Answer attributes for unknown ports." msgstr "Attributs de réponse pour les ports inconnus." -#: preferences/models.py:745 +#: preferences/models.py:748 msgid "" "Policy for machines connecting from unregistered rooms (relevant on ports " "with STRICT RADIUS mode)" @@ -777,87 +777,87 @@ msgstr "" "Politique pour les machines se connectant depuis des chambre non " "enregistrées (pertinent pour les ports avec le mode de RADIUS STRICT)" -#: preferences/models.py:755 +#: preferences/models.py:758 msgid "unknown rooms VLAN" msgstr "VLAN pour les chambres inconnues" -#: preferences/models.py:756 +#: preferences/models.py:759 msgid "VLAN for unknown rooms if not rejected." msgstr "VLAN pour les chambres inconnues si non rejeté." -#: preferences/models.py:762 +#: preferences/models.py:765 msgid "unknown rooms attributes" msgstr "attributs pour les chambres inconnues" -#: preferences/models.py:763 +#: preferences/models.py:766 msgid "Answer attributes for unknown rooms." msgstr "Attributs de réponse pour les chambres inconnues." -#: preferences/models.py:769 +#: preferences/models.py:772 msgid "policy for non members" msgstr "politique pour les non adhérents" -#: preferences/models.py:777 +#: preferences/models.py:780 msgid "non members VLAN" msgstr "VLAN pour les non adhérents" -#: preferences/models.py:778 +#: preferences/models.py:781 msgid "VLAN for non members if not rejected." msgstr "VLAN pour les non adhérents si non rejeté." -#: preferences/models.py:784 +#: preferences/models.py:787 msgid "non members attributes" msgstr "attributs pour les non adhérents" -#: preferences/models.py:785 +#: preferences/models.py:788 msgid "Answer attributes for non members." msgstr "Attributs de réponse pour les non adhérents." -#: preferences/models.py:791 +#: preferences/models.py:794 msgid "policy for banned users" msgstr "politique pour les utilisateurs bannis" -#: preferences/models.py:799 +#: preferences/models.py:802 msgid "banned users VLAN" msgstr "VLAN pour les utilisateurs bannis" -#: preferences/models.py:800 +#: preferences/models.py:803 msgid "VLAN for banned users if not rejected." msgstr "VLAN pour les utilisateurs bannis si non rejeté." -#: preferences/models.py:806 +#: preferences/models.py:809 msgid "banned users attributes" msgstr "attributs pour les utilisateurs bannis" -#: preferences/models.py:807 +#: preferences/models.py:810 msgid "Answer attributes for banned users." msgstr "Attributs de réponse pour les utilisateurs bannis." -#: preferences/models.py:820 +#: preferences/models.py:823 msgid "accepted users attributes" msgstr "attributs pour les utilisateurs acceptés" -#: preferences/models.py:821 +#: preferences/models.py:824 msgid "Answer attributes for accepted users." msgstr "Attributs de réponse pour les utilisateurs acceptés." -#: preferences/models.py:848 +#: preferences/models.py:851 msgid "subscription preferences" msgstr "préférences de cotisation" -#: preferences/models.py:852 +#: preferences/models.py:855 msgid "template for invoices" msgstr "modèle pour les factures" -#: preferences/models.py:859 +#: preferences/models.py:862 msgid "template for subscription vouchers" msgstr "modèle pour les reçus de cotisation" -#: preferences/models.py:865 +#: preferences/models.py:868 msgid "send voucher by email when the invoice is controlled" msgstr "envoyer le reçu par mail quand la facture est contrôlée" -#: preferences/models.py:867 +#: preferences/models.py:870 msgid "" "Be careful, if no mandate is defined on the preferences page, errors will be " "triggered when generating vouchers." @@ -865,19 +865,19 @@ msgstr "" "Faites attention, si aucun mandat n'est défini sur la page de préférences, " "des erreurs seront déclenchées en générant des reçus." -#: preferences/models.py:879 +#: preferences/models.py:882 msgid "template" msgstr "modèle" -#: preferences/models.py:880 +#: preferences/models.py:883 msgid "name" msgstr "nom" -#: preferences/models.py:883 +#: preferences/models.py:886 msgid "document template" msgstr "modèle de document" -#: preferences/models.py:884 +#: preferences/models.py:887 msgid "document templates" msgstr "modèles de document" diff --git a/re2o/locale/fr/LC_MESSAGES/django.po b/re2o/locale/fr/LC_MESSAGES/django.po index 942bab4a..625eda15 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+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 d9828af5..e33d68fd 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+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 5dae6c1d..7187c189 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -240,7 +240,8 @@ msgstr "" msgid "Edit" msgstr "Modifier" -#: templates/buttons/history.html:26 templates/buttons/history.html:27 +#: templates/buttons/history.html:28 templates/buttons/history.html:29 +#: templates/buttons/history.html:32 templates/buttons/history.html:33 msgid "History" msgstr "" diff --git a/tickets/locale/fr/LC_MESSAGES/django.po b/tickets/locale/fr/LC_MESSAGES/django.po index 4ac73acc..bb56a162 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+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 53a55c1a..ee5bfb93 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+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 fce3163a..f5029a64 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 14:44+0200\n" +"POT-Creation-Date: 2020-04-23 20:09+0200\n" "PO-Revision-Date: 2018-06-27 23:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -56,7 +56,7 @@ msgid "The current password is incorrect." msgstr "Le mot de passe actuel est incorrect." #: users/forms.py:133 users/forms.py:181 users/forms.py:405 -#: users/models.py:1912 +#: users/models.py:1936 msgid "Password" msgstr "Mot de passe" @@ -109,7 +109,7 @@ msgstr "Prénom" msgid "Surname" msgstr "Nom" -#: users/forms.py:321 users/forms.py:536 users/models.py:1912 +#: users/forms.py:321 users/forms.py:536 users/models.py:1936 #: users/templates/users/aff_emailaddress.html:36 #: users/templates/users/profil.html:209 msgid "Email address" @@ -323,7 +323,7 @@ msgstr "Non confirmé" msgid "Waiting for email confirmation" msgstr "En attente de confirmation" -#: users/models.py:204 users/models.py:1565 +#: users/models.py:204 users/models.py:1589 msgid "Must only contain letters, numerals or dashes." msgstr "Doit seulement contenir des lettres, chiffres ou tirets." @@ -365,66 +365,72 @@ msgstr "Peut forcer le déménagement" msgid "Can edit the shell of a user" msgstr "Peut modifier l'interface en ligne de commande d'un utilisateur" -#: users/models.py:257 +#: users/models.py:255 +#, fuzzy +#| msgid "Can edit the state of a user" +msgid "Can edit the pseudo of a user" +msgstr "Peut changer l'état d'un utilisateur" + +#: users/models.py:258 msgid "Can edit the groups of rights of a user (critical permission)" msgstr "" "Peut modifier les groupes de droits d'un utilisateur (permission critique)" -#: users/models.py:259 +#: users/models.py:260 msgid "Can edit all users, including those with rights" msgstr "" "Peut modifier tous les utilisateurs, y compris ceux possédant des droits" -#: users/models.py:260 +#: users/models.py:261 msgid "Can view a user object" msgstr "Peut voir un objet utilisateur" -#: users/models.py:262 +#: users/models.py:263 msgid "user (member or club)" msgstr "utilisateur (adhérent ou club)" -#: users/models.py:263 +#: users/models.py:264 msgid "users (members or clubs)" msgstr "utilisateurs (adhérents ou clubs)" -#: users/models.py:281 users/models.py:309 users/models.py:319 +#: users/models.py:282 users/models.py:310 users/models.py:320 msgid "Unknown type." msgstr "Type inconnu." -#: users/models.py:315 users/templates/users/aff_listright.html:75 +#: users/models.py:316 users/templates/users/aff_listright.html:75 #: users/templates/users/aff_listright.html:180 msgid "Member" msgstr "Adhérent" -#: users/models.py:317 +#: users/models.py:318 msgid "Club" msgstr "Club" -#: users/models.py:896 +#: users/models.py:897 msgid "Maximum number of registered machines reached." msgstr "Nombre maximum de machines enregistrées atteint." -#: users/models.py:898 +#: users/models.py:899 msgid "Re2o doesn't know wich machine type to assign." msgstr "Re2o ne sait pas quel type de machine attribuer." -#: users/models.py:921 users/templates/users/user_autocapture.html:64 +#: users/models.py:922 users/templates/users/user_autocapture.html:64 msgid "OK" msgstr "OK" -#: users/models.py:1019 +#: users/models.py:1020 msgid "This user is archived." msgstr "Cet utilisateur est archivé." -#: users/models.py:1033 users/models.py:1087 +#: users/models.py:1034 users/models.py:1088 msgid "You don't have the right to edit this club." msgstr "Vous n'avez pas le droit de modifier ce club." -#: users/models.py:1045 +#: users/models.py:1046 msgid "User with critical rights, can't be edited." msgstr "Utilisateur avec des droits critiques, ne peut être modifié." -#: users/models.py:1052 +#: users/models.py:1053 msgid "" "Impossible to edit the organisation's user without the \"change_all_users\" " "right." @@ -432,257 +438,263 @@ msgstr "" "Impossible de modifier l'utilisateur de l'association sans le droit « " "change_all_users »." -#: users/models.py:1064 users/models.py:1102 +#: users/models.py:1065 users/models.py:1103 msgid "You don't have the right to edit another user." msgstr "Vous n'avez pas le droit de modifier un autre utilisateur." -#: users/models.py:1128 +#: users/models.py:1129 msgid "You don't have the right to change the room." msgstr "Vous n'avez pas le droit de changer la chambre." -#: users/models.py:1145 +#: users/models.py:1146 msgid "You don't have the right to change the state." msgstr "Vous n'avez pas le droit de changer l'état." -#: users/models.py:1165 +#: users/models.py:1166 msgid "You don't have the right to change the shell." msgstr "Vous n'avez pas le droit de changer l'interface en ligne de commande." -#: users/models.py:1182 users/models.py:1197 +#: users/models.py:1188 +#, fuzzy +#| msgid "You don't have the right to change the state." +msgid "You don't have the right to change the pseudo." +msgstr "Vous n'avez pas le droit de changer l'état." + +#: users/models.py:1205 users/models.py:1220 msgid "Local email accounts must be enabled." msgstr "Les comptes mail locaux doivent être activés." -#: users/models.py:1212 +#: users/models.py:1235 msgid "You don't have the right to force the move." msgstr "Vous n'avez pas le droit de forcer le déménagement." -#: users/models.py:1227 +#: users/models.py:1250 msgid "You don't have the right to edit the user's groups of rights." msgstr "" "Vous n'avez pas le droit de modifier les groupes de droits d'un autre " "utilisateur." -#: users/models.py:1243 +#: users/models.py:1266 msgid "\"superuser\" right required to edit the superuser flag." msgstr "Droit « superuser » requis pour modifier le signalement superuser." -#: users/models.py:1268 +#: users/models.py:1291 msgid "You don't have the right to view this club." msgstr "Vous n'avez pas le droit de voir ce club." -#: users/models.py:1277 +#: users/models.py:1300 msgid "You don't have the right to view another user." msgstr "Vous n'avez pas le droit de voir un autre utilisateur." -#: users/models.py:1292 users/models.py:1501 +#: users/models.py:1315 users/models.py:1525 msgid "You don't have the right to view the list of users." msgstr "Vous n'avez pas le droit de voir la liste des utilisateurs." -#: users/models.py:1309 +#: users/models.py:1332 msgid "You don't have the right to delete this user." msgstr "Vous n'avez pas le droit de supprimer cet utilisateur." -#: users/models.py:1330 +#: users/models.py:1354 msgid "This username is already used." msgstr "Ce pseudo est déjà utilisé." -#: users/models.py:1337 +#: users/models.py:1361 msgid "Email field cannot be empty." msgstr "Le champ mail ne peut pas ^êêtre vide" -#: users/models.py:1344 +#: users/models.py:1368 msgid "You can't use a {} address as an external contact address." msgstr "Vous ne pouvez pas utiliser une adresse {} pour votre adresse externe." -#: users/models.py:1371 +#: users/models.py:1395 msgid "member" msgstr "adhérent" -#: users/models.py:1372 +#: users/models.py:1396 msgid "members" msgstr "adhérents" -#: users/models.py:1389 +#: users/models.py:1413 msgid "A GPG fingerprint must contain 40 hexadecimal characters." msgstr "Une empreinte GPG doit contenir 40 caractères hexadécimaux." -#: users/models.py:1414 +#: users/models.py:1438 msgid "Self registration is disabled." msgstr "L'auto inscription est désactivée." -#: users/models.py:1424 +#: users/models.py:1448 msgid "You don't have the right to create a user." msgstr "Vous n'avez pas le droit de créer un utilisateur." -#: users/models.py:1454 +#: users/models.py:1478 msgid "club" msgstr "club" -#: users/models.py:1455 +#: users/models.py:1479 msgid "clubs" msgstr "clubs" -#: users/models.py:1466 +#: users/models.py:1490 msgid "You must be authenticated." msgstr "Vous devez être authentifié." -#: users/models.py:1474 +#: users/models.py:1498 msgid "You don't have the right to create a club." msgstr "Vous n'avez pas le droit de créer un club." -#: users/models.py:1569 +#: users/models.py:1593 msgid "Comment." msgstr "Commentaire." -#: users/models.py:1575 +#: users/models.py:1599 msgid "Can view a service user object" msgstr "Peut voir un objet utilisateur service" -#: users/models.py:1576 users/views.py:349 +#: users/models.py:1600 users/views.py:349 msgid "service user" msgstr "utilisateur service" -#: users/models.py:1577 +#: users/models.py:1601 msgid "service users" msgstr "utilisateurs service" -#: users/models.py:1581 +#: users/models.py:1605 #, python-brace-format msgid "Service user <{name}>" msgstr "Utilisateur service <{name}>" -#: users/models.py:1648 +#: users/models.py:1672 msgid "Can view a school object" msgstr "Peut voir un objet établissement" -#: users/models.py:1649 +#: users/models.py:1673 msgid "school" msgstr "établissement" -#: users/models.py:1650 +#: users/models.py:1674 msgid "schools" msgstr "établissements" -#: users/models.py:1669 +#: users/models.py:1693 msgid "UNIX group names can only contain lower case letters." msgstr "" "Les noms de groupe UNIX peuvent seulement contenir des lettres minuscules." -#: users/models.py:1675 +#: users/models.py:1699 msgid "Description." msgstr "Description." -#: users/models.py:1678 +#: users/models.py:1702 msgid "Can view a group of rights object" msgstr "Peut voir un objet groupe de droits" -#: users/models.py:1679 +#: users/models.py:1703 msgid "group of rights" msgstr "groupe de droits" -#: users/models.py:1680 +#: users/models.py:1704 msgid "groups of rights" msgstr "groupes de droits" -#: users/models.py:1725 +#: users/models.py:1749 msgid "Can view a shell object" msgstr "Peut voir un objet interface en ligne de commande" -#: users/models.py:1726 users/views.py:649 +#: users/models.py:1750 users/views.py:649 msgid "shell" msgstr "interface en ligne de commande" -#: users/models.py:1727 +#: users/models.py:1751 msgid "shells" msgstr "interfaces en ligne de commande" -#: users/models.py:1745 +#: users/models.py:1769 msgid "HARD (no access)" msgstr "HARD (pas d'accès)" -#: users/models.py:1746 +#: users/models.py:1770 msgid "SOFT (local access only)" msgstr "SOFT (accès local uniquement)" -#: users/models.py:1747 +#: users/models.py:1771 msgid "RESTRICTED (speed limitation)" msgstr "RESTRICTED (limitation de vitesse)" -#: users/models.py:1758 +#: users/models.py:1782 msgid "Can view a ban object" msgstr "Peut voir un objet bannissement" -#: users/models.py:1759 users/views.py:400 +#: users/models.py:1783 users/views.py:400 msgid "ban" msgstr "bannissement" -#: users/models.py:1760 +#: users/models.py:1784 msgid "bans" msgstr "bannissements" -#: users/models.py:1797 +#: users/models.py:1821 msgid "You don't have the right to view other bans than yours." msgstr "" "Vous n'avez pas le droit de voir d'autres bannissements que les vôtres." -#: users/models.py:1845 +#: users/models.py:1869 msgid "Can view a whitelist object" msgstr "Peut voir un objet accès gracieux" -#: users/models.py:1846 +#: users/models.py:1870 msgid "whitelist (free of charge access)" msgstr "Accès gracieux" -#: users/models.py:1847 +#: users/models.py:1871 msgid "whitelists (free of charge access)" msgstr "Accès gracieux" -#: users/models.py:1867 +#: users/models.py:1891 msgid "You don't have the right to view other whitelists than yours." msgstr "" "Vous n'avez pas le droit de voir d'autres accès gracieux que les vôtres." -#: users/models.py:2065 +#: users/models.py:2089 msgid "User of the local email account." msgstr "Utilisateur du compte mail local." -#: users/models.py:2068 +#: users/models.py:2092 msgid "Local part of the email address." msgstr "Partie locale de l'adresse mail." -#: users/models.py:2073 +#: users/models.py:2097 msgid "Can view a local email account object" msgstr "Peut voir un objet compte mail local" -#: users/models.py:2075 +#: users/models.py:2099 msgid "local email account" msgstr "compte mail local" -#: users/models.py:2076 +#: users/models.py:2100 msgid "local email accounts" msgstr "comptes mail locaux" -#: users/models.py:2104 users/models.py:2139 users/models.py:2173 -#: users/models.py:2207 +#: users/models.py:2128 users/models.py:2163 users/models.py:2197 +#: users/models.py:2231 msgid "The local email accounts are not enabled." msgstr "Les comptes mail locaux ne sont pas activés." -#: users/models.py:2109 +#: users/models.py:2133 msgid "You don't have the right to add a local email account to another user." msgstr "" "Vous n'avez pas le droit d'ajouter un compte mail local à un autre " "utilisateur." -#: users/models.py:2119 +#: users/models.py:2143 msgid "You reached the limit of {} local email accounts." msgstr "Vous avez atteint la limite de {} comptes mail locaux." -#: users/models.py:2145 +#: users/models.py:2169 msgid "You don't have the right to view another user's local email account." msgstr "" "Vous n'avez pas le droit de voir le compte mail local d'un autre utilisateur." -#: users/models.py:2165 +#: users/models.py:2189 msgid "" "You can't delete a local email account whose local part is the same as the " "username." @@ -690,13 +702,13 @@ msgstr "" "Vous ne pouvez pas supprimer un compte mail local dont la partie locale est " "la même que le pseudo." -#: users/models.py:2179 +#: users/models.py:2203 msgid "You don't have the right to delete another user's local email account." msgstr "" "Vous n'avez pas le droit de supprimer le compte mail local d'un autre " "utilisateur." -#: users/models.py:2199 +#: users/models.py:2223 msgid "" "You can't edit a local email account whose local part is the same as the " "username." @@ -704,13 +716,13 @@ msgstr "" "Vous ne pouvez pas modifier un compte mail local dont la partie locale est " "la même que le pseudo." -#: users/models.py:2213 +#: users/models.py:2237 msgid "You don't have the right to edit another user's local email account." msgstr "" "Vous n'avez pas le droit de modifier le compte mail local d'un autre " "utilisateur." -#: users/models.py:2222 +#: users/models.py:2246 msgid "The local part must not contain @ or +." msgstr "La partie locale ne doit pas contenir @ ou +." From 78bbef821b3baca3c496753cc5bb50d72150c043 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 20:26:44 +0200 Subject: [PATCH 18/23] Allow viewing history of deleted elements --- logs/models.py | 48 ++++++++++++++++++++---------------------------- logs/views.py | 32 +++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/logs/models.py b/logs/models.py index 3b088b87..970b8902 100644 --- a/logs/models.py +++ b/logs/models.py @@ -291,18 +291,19 @@ class History: self._last_version = None self.event_type = HistoryEvent - def get(self, instance): + def get(self, instance_id, model): """ - :param interface: The instance to lookup + :param instance_id: int, The id of the instance to lookup + :param model: class, The type of object to lookup :return: list or None, a list of HistoryEvent, in reverse chronological order """ self.events = [] - # Get all the versions for this interface, with the oldest first + # 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(type(instance)).order_by("revision__date_created") + lambda x: x.field_dict["id"] == instance_id, + Version.objects.get_for_model(model).order_by("revision__date_created") ) for version in interface_versions: @@ -346,16 +347,6 @@ class History: class UserHistoryEvent(HistoryEvent): - def __init__(self, user, version, previous_version=None, edited_fields=None): - """ - :param user: User, The user who's history is being built - :param version: Version, the version of the user for this event - :param previous_version: Version, the version of the user before this event - :param edited_fields: list, The list of modified fields by this event - """ - super(UserHistoryEvent, self).__init__(version, previous_version, edited_fields) - self.user = user - def _repr(self, name, value): """ Returns the best representation of the given field @@ -425,7 +416,6 @@ class UserHistoryEvent(HistoryEvent): def __eq__(self, other): return ( - self.user.id == other.user.id and self.edited_fields == other.edited_fields and self.date == other.date and self.performed_by == other.performed_by @@ -433,13 +423,12 @@ class UserHistoryEvent(HistoryEvent): ) def __hash__(self): - return hash((self.user.id, frozenset(self.edited_fields), self.date, self.performed_by, self.comment)) + return hash((frozenset(self.edited_fields), self.date, self.performed_by, self.comment)) def __repr__(self): - return "{} edited fields {} of {} ({})".format( + return "{} edited fields {} ({})".format( self.performed_by, self.edited_fields or "nothing", - self.user, self.comment or "No comment" ) @@ -449,23 +438,23 @@ class UserHistory(History): super(UserHistory, self).__init__() self.event_type = UserHistoryEvent - def get(self, user): + def get(self, user_id): """ - :param user: User, the user to lookup + :param user_id: int, the id of the user to lookup :return: list or None, a list of UserHistoryEvent, in reverse chronological order """ self.events = [] # Find whether this is a Club or an Adherent try: - obj = Adherent.objects.get(user_ptr_id=user.id) + obj = Adherent.objects.get(user_ptr_id=user_id) except Adherent.DoesNotExist: - obj = Club.objects.get(user_ptr_id=user.id) + obj = Club.objects.get(user_ptr_id=user_id) # Add as "related" histories the list of Machine objects # that were once owned by this user self.related = filter( - lambda x: x.field_dict["user_id"] == user.id, + lambda x: x.field_dict["user_id"] == user_id, Version.objects.get_for_model(Machine).order_by("revision__date_created") ) self.related = [RelatedHistory( @@ -477,7 +466,7 @@ 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, + lambda x: x.field_dict["id"] == user_id, Version.objects.get_for_model(User).order_by("revision__date_created") ) @@ -548,11 +537,11 @@ class MachineHistory(History): super(MachineHistory, self).__init__() self.event_type = MachineHistoryEvent - def get(self, machine): + 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, + lambda x: x.field_dict["machine_id"] == machine_id, Version.objects.get_for_model(Interface).order_by("revision__date_created") )) @@ -563,7 +552,7 @@ class MachineHistory(History): i.field_dict["id"]) for i in self.related] self.related = list(dict.fromkeys(self.related)) - return super(MachineHistory, self).get(machine) + return super(MachineHistory, self).get(machine_id, Machine) class InterfaceHistoryEvent(HistoryEvent): @@ -607,3 +596,6 @@ class InterfaceHistory(History): def __init__(self): super(InterfaceHistory, self).__init__() self.event_type = InterfaceHistoryEvent + + def get(self, interface_id): + return super(InterfaceHistory, self).get(machine_id, Interface) diff --git a/logs/views.py b/logs/views.py index 54432794..c0f3d6e9 100644 --- a/logs/views.py +++ b/logs/views.py @@ -514,22 +514,44 @@ def stats_search_machine_history(request): return render(request, "logs/search_machine_history.html", {"history_form": history_form}) -def get_history_object(request, model, object_name, object_id): +def get_history_object(request, model, object_name, object_id, allow_deleted=False): """Get the objet of type model with the given object_id Handles permissions and DoesNotExist errors """ + instance = None + is_deleted = False + try: object_name_id = object_name + "id" kwargs = {object_name_id: object_id} instance = model.get_instance(**kwargs) except model.DoesNotExist: + pass + + if instance is None and allow_deleted: + # Try to find an instance among the Version objects + is_deleted = True + versions = filter( + lambda x: x.field_dict["id"] == object_id, + Version.objects.get_for_model(model) + ) + versions = list(versions) + if len(versions): + instance = versions[0] + + if instance is None: messages.error(request, _("Nonexistent entry.")) return False, redirect( reverse("users:profil", kwargs={"userid": str(request.user.id)}) ) - can, msg, _permissions = instance.can_view(request.user) - if not can: + if is_deleted: + can_view = can_view_app("logs") + msg = None + else: + can_view, msg, _permissions = instance.can_view(request.user) + + if not can_view: messages.error( request, msg or _("You don't have the right to access this menu.") ) @@ -559,7 +581,7 @@ def detailed_history(request, object_name, object_id): raise Http404(_("No model found.")) # Get instance and check permissions - can_view, instance = get_history_object(request, model, object_name, object_id) + can_view, instance = get_history_object(request, model, object_name, object_id, allow_deleted=True) if not can_view: return instance @@ -567,7 +589,7 @@ def detailed_history(request, object_name, object_id): max_result = GeneralOption.get_cached_value("pagination_number") events = re2o_paginator( request, - history.get(instance), + history.get(object_id), max_result ) From 3fa84d82165d54339c103764720601b998924de4 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 20:30:54 +0200 Subject: [PATCH 19/23] Fix typos in logs/models.py --- logs/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/logs/models.py b/logs/models.py index af72cf64..4b5b9569 100644 --- a/logs/models.py +++ b/logs/models.py @@ -415,7 +415,7 @@ class UserHistoryEvent(HistoryEvent): def __eq__(self, other): return ( - and self.edited_fields == other.edited_fields + self.edited_fields == other.edited_fields and self.date == other.date and self.performed_by == other.performed_by and self.comment == other.comment @@ -470,7 +470,7 @@ class UserHistory(History): ) for version in user_versions: - self._add_revision(user, version) + self._add_revision(version) # Do the same thing for the Adherent of Club self._last_version = None @@ -480,7 +480,7 @@ class UserHistory(History): ) for version in obj_versions: - self._add_revision(user, version) + self._add_revision(version) # Remove duplicates and sort self.events = list(dict.fromkeys(self.events)) @@ -490,7 +490,7 @@ class UserHistory(History): reverse=True ) - def _add_revision(self, user, version): + def _add_revision(self, version): """ Add a new revision to the chronological order :param user: User, The user displayed in this history @@ -509,7 +509,7 @@ class UserHistory(History): self._last_version = version return - evt = UserHistoryEvent(user, version, self._last_version, diff) + evt = UserHistoryEvent(version, self._last_version, diff) self.events.append(evt) self._last_version = version @@ -597,4 +597,4 @@ class InterfaceHistory(History): self.event_type = InterfaceHistoryEvent def get(self, interface_id): - return super(InterfaceHistory, self).get(machine_id, Interface) + return super(InterfaceHistory, self).get(interface_id, Interface) From e8a3134492d78f73d7b70cd40fc48c17cad6dc63 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 20:50:31 +0200 Subject: [PATCH 20/23] Better handle deleted objects in detailed history view --- logs/models.py | 29 +++++++++++++++++++++++------ logs/views.py | 21 ++++++++------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/logs/models.py b/logs/models.py index 4b5b9569..d7f9e270 100644 --- a/logs/models.py +++ b/logs/models.py @@ -308,6 +308,10 @@ class History: for version in interface_versions: self._add_revision(version) + # Return None if interface_versions was empty + if self._last_version is None: + return None + return self.events[::-1] def _compute_diff(self, v1, v2, ignoring=[]): @@ -444,13 +448,26 @@ class UserHistory(History): """ self.events = [] - # Find whether this is a Club or an Adherent - try: - obj = Adherent.objects.get(user_ptr_id=user_id) - except Adherent.DoesNotExist: - obj = Club.objects.get(user_ptr_id=user_id) + # Try to find an Adherent object + adherents = filter( + lambda x: x.field_dict["user_ptr_id"] == user_id, + Version.objects.get_for_model(Adherent) + ) + obj = next(adherents, None) - # Add as "related" histories the list of Machine objects + # Fallback on a Club + if obj is None: + clubs = filter( + lambda x: x.field_dict["user_ptr_id"] == user_id, + Version.objects.get_for_model(Club) + ) + obj = next(clubs, None) + + # If nothing was found, abort + if obj is None: + return None + + # 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, diff --git a/logs/views.py b/logs/views.py index c0f3d6e9..981d3d00 100644 --- a/logs/views.py +++ b/logs/views.py @@ -518,7 +518,6 @@ def get_history_object(request, model, object_name, object_id, allow_deleted=Fal """Get the objet of type model with the given object_id Handles permissions and DoesNotExist errors """ - instance = None is_deleted = False try: @@ -526,20 +525,9 @@ def get_history_object(request, model, object_name, object_id, allow_deleted=Fal kwargs = {object_name_id: object_id} instance = model.get_instance(**kwargs) except model.DoesNotExist: - pass - - if instance is None and allow_deleted: - # Try to find an instance among the Version objects is_deleted = True - versions = filter( - lambda x: x.field_dict["id"] == object_id, - Version.objects.get_for_model(model) - ) - versions = list(versions) - if len(versions): - instance = versions[0] - if instance is None: + if is_deleted and not allow_deleted: messages.error(request, _("Nonexistent entry.")) return False, redirect( reverse("users:profil", kwargs={"userid": str(request.user.id)}) @@ -593,6 +581,13 @@ def detailed_history(request, object_name, object_id): max_result ) + # Events is None if object wasn't found + if events is None: + messages.error(request, _("Nonexistent entry.")) + return redirect( + reverse("users:profil", kwargs={"userid": str(request.user.id)}) + ) + return render( request, "logs/detailed_history.html", From bd06ee33271eec00897ee210ad8c7db53f64c10d Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 19:20:55 +0000 Subject: [PATCH 21/23] Handle history for non-existant objects --- logs/models.py | 6 ++++-- logs/views.py | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/logs/models.py b/logs/models.py index d7f9e270..bdc222e4 100644 --- a/logs/models.py +++ b/logs/models.py @@ -454,6 +454,7 @@ class UserHistory(History): Version.objects.get_for_model(Adherent) ) obj = next(adherents, None) + model = Adherent # Fallback on a Club if obj is None: @@ -462,6 +463,7 @@ class UserHistory(History): Version.objects.get_for_model(Club) ) obj = next(clubs, None) + model = Club # If nothing was found, abort if obj is None: @@ -492,8 +494,8 @@ 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"] == obj.id, - Version.objects.get_for_model(type(obj)).order_by("revision__date_created") + lambda x: x.field_dict["id"] == user_id, + Version.objects.get_for_model(model).order_by("revision__date_created") ) for version in obj_versions: diff --git a/logs/views.py b/logs/views.py index 981d3d00..007da9f7 100644 --- a/logs/views.py +++ b/logs/views.py @@ -526,6 +526,7 @@ def get_history_object(request, model, object_name, object_id, allow_deleted=Fal instance = model.get_instance(**kwargs) except model.DoesNotExist: is_deleted = True + instance = None if is_deleted and not allow_deleted: messages.error(request, _("Nonexistent entry.")) @@ -575,11 +576,7 @@ def detailed_history(request, object_name, object_id): # Generate the pagination with the objects max_result = GeneralOption.get_cached_value("pagination_number") - events = re2o_paginator( - request, - history.get(object_id), - max_result - ) + events = history.get(int(object_id)) # Events is None if object wasn't found if events is None: @@ -588,6 +585,9 @@ def detailed_history(request, object_name, object_id): reverse("users:profil", kwargs={"userid": str(request.user.id)}) ) + # Add the paginator in case there are many results + events = re2o_paginator(request, events, max_result) + return render( request, "logs/detailed_history.html", From 47c5f0cc5454d0a400f621835bfe2cd179a7aafb Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 21:25:00 +0200 Subject: [PATCH 22/23] Add title to history of deleted objects --- logs/models.py | 18 ++++++++++++++++-- logs/templates/logs/detailed_history.html | 2 +- logs/views.py | 5 ++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/logs/models.py b/logs/models.py index bdc222e4..b8789db8 100644 --- a/logs/models.py +++ b/logs/models.py @@ -285,6 +285,7 @@ class HistoryEvent: class History: def __init__(self): + self.name = None self.events = [] self.related = [] # For example, a machine has a list of its interfaces self._last_version = None @@ -491,6 +492,9 @@ class UserHistory(History): for version in user_versions: self._add_revision(version) + # Update name + self.name = self._last_version.field_dict["pseudo"] + # Do the same thing for the Adherent of Club self._last_version = None obj_versions = filter( @@ -570,7 +574,12 @@ class MachineHistory(History): i.field_dict["id"]) for i in self.related] self.related = list(dict.fromkeys(self.related)) - return super(MachineHistory, self).get(machine_id, Machine) + events = super(MachineHistory, self).get(machine_id, Machine) + + # Update name + self.name = self._last_version.field_dict["name"] + + return events class InterfaceHistoryEvent(HistoryEvent): @@ -616,4 +625,9 @@ class InterfaceHistory(History): self.event_type = InterfaceHistoryEvent def get(self, interface_id): - return super(InterfaceHistory, self).get(interface_id, Interface) + events = super(InterfaceHistory, self).get(interface_id, Interface) + + # Update name + self.name = self._last_version.field_dict["mac_address"] + + return events diff --git a/logs/templates/logs/detailed_history.html b/logs/templates/logs/detailed_history.html index 26bae90c..7aaf67a0 100644 --- a/logs/templates/logs/detailed_history.html +++ b/logs/templates/logs/detailed_history.html @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}{% trans "History" %}{% endblock %} {% block content %} -

    {% blocktrans %}History of {{ object }}{% endblocktrans %}

    +

    {% blocktrans %}History of {{ title }}{% endblocktrans %}

    {% if events %} diff --git a/logs/views.py b/logs/views.py index 007da9f7..ad78bd23 100644 --- a/logs/views.py +++ b/logs/views.py @@ -588,10 +588,13 @@ def detailed_history(request, object_name, object_id): # Add the paginator in case there are many results events = re2o_paginator(request, events, max_result) + # Add a title in case the object was deleted + title = instance or "{} ({})".format(history.name, _("Deleted")) + return render( request, "logs/detailed_history.html", - {"object": instance, "events": events, "related_history": history.related}, + {"title": title, "events": events, "related_history": history.related}, ) From 6371f115362ee7291190bff7165b6456aa85bff2 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Thu, 23 Apr 2020 19:28:15 +0000 Subject: [PATCH 23/23] 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 | 25 +++++++++++---------- 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, 24 insertions(+), 23 deletions(-) diff --git a/api/locale/fr/LC_MESSAGES/django.po b/api/locale/fr/LC_MESSAGES/django.po index 6b5b3c75..51857377 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 674a417c..782c72b7 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 e9310b54..039ba799 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+0200\n" "PO-Revision-Date: 2018-06-23 16:01+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -58,23 +58,24 @@ msgstr "Date de début" msgid "End date" msgstr "Date de fin" -#: logs/models.py:260 logs/models.py:368 logs/models.py:401 logs/models.py:471 -#: logs/models.py:560 logs/models.py:593 +#: logs/models.py:260 logs/models.py:364 logs/models.py:397 logs/models.py:480 +#: logs/models.py:572 logs/models.py:610 msgid "None" msgstr "Aucun(e)" -#: logs/models.py:378 logs/models.py:397 logs/models.py:411 logs/models.py:540 -#: logs/models.py:580 logs/models.py:585 logs/models.py:590 logs/models.py:600 +#: 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 msgid "Deleted" msgstr "Supprimé(e)" -#: logs/models.py:385 logs/models.py:390 +#: logs/models.py:381 logs/models.py:386 #: logs/templates/logs/detailed_history.html:52 #: logs/templates/logs/machine_history.html:55 msgid "Unknown" msgstr "Inconnu(e)" -#: logs/models.py:588 +#: logs/models.py:605 msgid "No name" msgstr "Sans nom" @@ -204,8 +205,8 @@ msgid "History" msgstr "Historique" #: logs/templates/logs/detailed_history.html:31 -msgid "History of %(object)s" -msgstr "Historique de %(object)s" +msgid "History of %(title)s" +msgstr "Historique de %(title)s" #: logs/templates/logs/detailed_history.html:38 msgid "Performed by" @@ -420,14 +421,14 @@ msgstr "droits" msgid "actions" msgstr "actions" -#: logs/views.py:526 +#: logs/views.py:532 logs/views.py:583 msgid "Nonexistent entry." msgstr "Entrée inexistante." -#: logs/views.py:534 +#: logs/views.py:545 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:559 logs/views.py:606 +#: logs/views.py:570 logs/views.py:626 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 81109ef4..e8abd822 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 026a1da2..3439441d 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 00ea2a38..8423e0cb 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 625eda15..7bba1fe6 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 e33d68fd..c69e0148 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 7187c189..5e7b31d0 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 bb56a162..42c96960 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 ee5bfb93..5bba5b46 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+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 f5029a64..6b57f34b 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 20:09+0200\n" +"POT-Creation-Date: 2020-04-23 21:25+0200\n" "PO-Revision-Date: 2018-06-27 23:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n"