mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-12-23 15:33:45 +00:00
Merge branch 'machine_interface_history' into 'dev'
Add detailed history for machine and interface See merge request re2o/re2o!517
This commit is contained in:
commit
e7795a775c
21 changed files with 756 additions and 434 deletions
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2019-01-07 01:37+0100\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language: fr_FR\n"
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2018-06-23 16:01+0200\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
|
@ -58,20 +58,27 @@ 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: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:248
|
||||
#: 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:255 logs/models.py:260
|
||||
#: logs/models.py:381 logs/models.py:386
|
||||
#: 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:605
|
||||
msgid "No name"
|
||||
msgstr "Sans nom"
|
||||
|
||||
#: logs/templates/logs/aff_stats_logs.html:36
|
||||
msgid "Edited object"
|
||||
msgstr "Objet modifié"
|
||||
|
@ -90,8 +97,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 +135,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 +199,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 %(title)s"
|
||||
msgstr "Historique de %(title)s"
|
||||
|
||||
#: logs/templates/logs/detailed_history.html:38
|
||||
msgid "Performed by"
|
||||
msgstr "Effectué(e) par"
|
||||
|
||||
#: logs/templates/logs/detailed_history.html:39
|
||||
msgid "Edited"
|
||||
msgstr "Modifié"
|
||||
|
||||
#: logs/templates/logs/detailed_history.html:75
|
||||
msgid "No event"
|
||||
msgstr "Aucun évènement"
|
||||
|
||||
#: 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 +232,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 +297,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:532 logs/views.py:583
|
||||
msgid "Nonexistent entry."
|
||||
msgstr "Entrée inexistante."
|
||||
|
||||
#: logs/views.py:567
|
||||
#: 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:570 logs/views.py:626
|
||||
msgid "No model found."
|
||||
msgstr "Aucun modèle trouvé."
|
||||
|
|
415
logs/models.py
415
logs/models.py
|
@ -28,13 +28,15 @@ 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 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,16 +78,16 @@ class MachineHistoryEvent:
|
|||
)
|
||||
|
||||
|
||||
class MachineHistory:
|
||||
class MachineHistorySearch:
|
||||
def __init__(self):
|
||||
self.events = []
|
||||
self.__last_evt = None
|
||||
self._last_evt = None
|
||||
|
||||
def get(self, search, params):
|
||||
"""
|
||||
: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,33 +95,33 @@ 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
|
||||
|
||||
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
|
||||
: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
|
||||
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 +130,9 @@ class MachineHistory:
|
|||
|
||||
# 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 +149,7 @@ class MachineHistory:
|
|||
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 +160,7 @@ class MachineHistory:
|
|||
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 +172,7 @@ class MachineHistory:
|
|||
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,48 +181,67 @@ class MachineHistory:
|
|||
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 MachineHistoryEvent
|
||||
: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 MachineHistoryEvent
|
||||
: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
|
||||
|
||||
|
||||
class UserHistoryEvent:
|
||||
def __init__(self, user, version, previous_version=None, edited_fields=None):
|
||||
class RelatedHistory:
|
||||
def __init__(self, name, model_name, object_id):
|
||||
"""
|
||||
: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 name: Name of this instance
|
||||
:param model_name: Name of the related model (e.g. "user")
|
||||
:param object_id: ID of the related object
|
||||
"""
|
||||
self.name = "{} (id = {})".format(name, object_id)
|
||||
self.model_name = model_name
|
||||
self.object_id = object_id
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
self.model_name == other.model_name
|
||||
and self.object_id == other.object_id
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.model_name, self.object_id))
|
||||
|
||||
|
||||
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.user = user
|
||||
self.version = version
|
||||
self.previous_version = previous_version
|
||||
self.edited_fields = edited_fields
|
||||
|
@ -228,7 +249,109 @@ class UserHistoryEvent:
|
|||
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
|
||||
: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.name = None
|
||||
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_id, model):
|
||||
"""
|
||||
: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 instance, with the oldest first
|
||||
self._last_version = None
|
||||
interface_versions = filter(
|
||||
lambda x: x.field_dict["id"] == instance_id,
|
||||
Version.objects.get_for_model(model).order_by("revision__date_created")
|
||||
)
|
||||
|
||||
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=[]):
|
||||
"""
|
||||
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, 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 _repr(self, name, value):
|
||||
"""
|
||||
Returns the best representation of the given field
|
||||
:param name: the name of the field
|
||||
|
@ -285,10 +408,7 @@ class UserHistoryEvent:
|
|||
|
||||
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"]):
|
||||
"""
|
||||
|
@ -296,79 +416,94 @@ 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 (
|
||||
self.user.id == other.user.id
|
||||
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
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
|
||||
class UserHistory:
|
||||
class UserHistory(History):
|
||||
def __init__(self):
|
||||
self.events = []
|
||||
self.__last_version = None
|
||||
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)
|
||||
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)
|
||||
model = Adherent
|
||||
|
||||
# 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)
|
||||
model = Club
|
||||
|
||||
# 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,
|
||||
Version.objects.get_for_model(Machine).order_by("-revision__date_created")
|
||||
)
|
||||
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
|
||||
self.__last_version = None
|
||||
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")
|
||||
)
|
||||
|
||||
for version in user_versions:
|
||||
self.__add_revision(user, version)
|
||||
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
|
||||
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:
|
||||
self.__add_revision(user, version)
|
||||
self._add_revision(version)
|
||||
|
||||
# Remove duplicates and sort
|
||||
self.events = list(dict.fromkeys(self.events))
|
||||
|
@ -378,37 +513,121 @@ 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):
|
||||
def _add_revision(self, 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(version, self.__last_version)
|
||||
if self._last_version is not None:
|
||||
diff = self._compute_diff(
|
||||
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(version, self._last_version, diff)
|
||||
self.events.append(evt)
|
||||
self.__last_version = version
|
||||
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_id):
|
||||
# Add as "related" histories the list of Interface objects
|
||||
# that were once assigned to this machine
|
||||
self.related = list(filter(
|
||||
lambda x: x.field_dict["machine_id"] == machine_id,
|
||||
Version.objects.get_for_model(Interface).order_by("-revision__date_created")
|
||||
))
|
||||
|
||||
# Create RelatedHistory objects and remove duplicates
|
||||
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))
|
||||
|
||||
events = super(MachineHistory, self).get(machine_id, Machine)
|
||||
|
||||
# Update name
|
||||
self.name = self._last_version.field_dict["name"]
|
||||
|
||||
return events
|
||||
|
||||
|
||||
class InterfaceHistoryEvent(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 == "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(InterfaceHistoryEvent, self)._repr(name, value)
|
||||
|
||||
|
||||
class InterfaceHistory(History):
|
||||
def __init__(self):
|
||||
super(InterfaceHistory, self).__init__()
|
||||
self.event_type = InterfaceHistoryEvent
|
||||
|
||||
def get(self, interface_id):
|
||||
events = super(InterfaceHistory, self).get(interface_id, Interface)
|
||||
|
||||
# Update name
|
||||
self.name = self._last_version.field_dict["mac_address"]
|
||||
|
||||
return events
|
||||
|
|
|
@ -23,11 +23,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load logs_extra %}
|
||||
|
||||
{% block title %}{% trans "History" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% blocktrans %}History of {{ user }}{% endblocktrans %}</h2>
|
||||
<h2>{% blocktrans %}History of {{ title }}{% endblocktrans %}</h2>
|
||||
|
||||
{% if events %}
|
||||
<table class="table table-striped">
|
||||
|
@ -73,6 +74,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% else %}
|
||||
<h3>{% trans "No event" %}</h3>
|
||||
{% endif %}
|
||||
|
||||
{% if related_history %}
|
||||
|
||||
<h2>{% blocktrans %}Related elements{% endblocktrans %}</h2>
|
||||
|
||||
<ul>
|
||||
{% for related in related_history %}
|
||||
<li>
|
||||
<a title="{% trans "History" %}" href="{% url 'logs:detailed-history' related.model_name related.object_id %}">{{ related.model_name }} - {{ related.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
|
@ -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,
|
||||
}
|
||||
|
|
14
logs/urls.py
14
logs/urls.py
|
@ -37,15 +37,19 @@ urlpatterns = [
|
|||
views.revert_action,
|
||||
name="revert-action",
|
||||
),
|
||||
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"),
|
||||
url(r"^stats_actions/$", views.stats_actions, name="stats-actions"),
|
||||
url(
|
||||
r"(?P<application>\w+)/(?P<object_name>\w+)/(?P<object_id>[0-9]+)$",
|
||||
views.history,
|
||||
name="history",
|
||||
),
|
||||
url(
|
||||
r"(?P<object_name>\w+)/(?P<object_id>[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"),
|
||||
url(r"^stats_actions/$", views.stats_actions, name="stats-actions"),
|
||||
url(r"^stats_search_machine/$", views.stats_search_machine_history, name="stats-search-machine"),
|
||||
url(r"^user/(?P<userid>[0-9]+)$", views.user_history, name="user-history"),
|
||||
]
|
||||
|
|
126
logs/views.py
126
logs/views.py
|
@ -101,8 +101,14 @@ 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,
|
||||
MachineHistory,
|
||||
InterfaceHistory
|
||||
)
|
||||
|
||||
from .forms import MachineHistorySearchForm
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -486,9 +492,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
|
||||
|
@ -508,32 +514,98 @@ 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, allow_deleted=False):
|
||||
"""Get the objet of type model with the given object_id
|
||||
Handles permissions and DoesNotExist errors
|
||||
"""
|
||||
is_deleted = False
|
||||
|
||||
max_result = GeneralOption.get_cached_value("pagination_number")
|
||||
events = re2o_paginator(
|
||||
request,
|
||||
events,
|
||||
max_result
|
||||
try:
|
||||
object_name_id = object_name + "id"
|
||||
kwargs = {object_name_id: object_id}
|
||||
instance = model.get_instance(**kwargs)
|
||||
except model.DoesNotExist:
|
||||
is_deleted = True
|
||||
instance = None
|
||||
|
||||
if is_deleted and not allow_deleted:
|
||||
messages.error(request, _("Nonexistent entry."))
|
||||
return False, redirect(
|
||||
reverse("users:profil", kwargs={"userid": str(request.user.id)})
|
||||
)
|
||||
|
||||
if is_deleted:
|
||||
can_view = can_view_app("logs")
|
||||
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.")
|
||||
)
|
||||
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
|
||||
history = MachineHistory()
|
||||
elif object_name == "interface":
|
||||
model = Interface
|
||||
history = InterfaceHistory()
|
||||
else:
|
||||
raise Http404(_("No model found."))
|
||||
|
||||
# Get instance and check permissions
|
||||
can_view, instance = get_history_object(request, model, object_name, object_id, allow_deleted=True)
|
||||
if not can_view:
|
||||
return instance
|
||||
|
||||
# Generate the pagination with the objects
|
||||
max_result = GeneralOption.get_cached_value("pagination_number")
|
||||
events = history.get(int(object_id))
|
||||
|
||||
# Events is None if object wasn't found
|
||||
if events is None:
|
||||
messages.error(request, _("Nonexistent entry."))
|
||||
return redirect(
|
||||
reverse("users:profil", kwargs={"userid": str(request.user.id)})
|
||||
)
|
||||
|
||||
# Add the paginator in case there are many results
|
||||
events = re2o_paginator(request, events, max_result)
|
||||
|
||||
# Add a title in case the object was deleted
|
||||
title = instance or "{} ({})".format(history.name, _("Deleted"))
|
||||
|
||||
return render(
|
||||
request,
|
||||
"logs/user_history.html",
|
||||
{ "user": users, "events": events },
|
||||
"logs/detailed_history.html",
|
||||
{"title": title, "events": events, "related_history": history.related},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def history(request, application, object_name, object_id):
|
||||
"""Render history for a model.
|
||||
|
||||
The model is determined using the `HISTORY_BIND` dictionnary if none is
|
||||
found, raises a Http404. The view checks if the user is allowed to see the
|
||||
history using the `can_view` method of the model.
|
||||
Permissions are handled by get_history_object.
|
||||
|
||||
Args:
|
||||
request: The request sent by the user.
|
||||
|
@ -552,23 +624,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(request, model, object_name, object_id)
|
||||
if not can_view:
|
||||
return instance
|
||||
|
||||
pagination_number = GeneralOption.get_cached_value("pagination_number")
|
||||
reversions = Version.objects.get_for_object(instance)
|
||||
if hasattr(instance, "linked_objects"):
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2018-06-23 16:35+0200\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
|
@ -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 %}
|
||||
</ul>
|
||||
</div>
|
||||
{% 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 %}
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2019-11-16 00:22+0100\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2018-06-24 15:54+0200\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\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"
|
||||
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2018-06-24 20:10+0200\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
|
@ -24,8 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load i18n %}
|
||||
|
||||
{% if name == "user" %}
|
||||
<a {% if class%}class="btn btn-info btn-sm"{% endif %} role="button" title="{% trans "History" %}" href="{% url 'logs:user-history' id %}">
|
||||
{% if detailed %}
|
||||
<a {% if class%}class="btn btn-info btn-sm"{% endif %} role="button" title="{% trans "History" %}" href="{% url 'logs:detailed-history' name id %}">
|
||||
<i class="fa fa-history"></i> {% if text %}{% trans "History" %}{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\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 ""
|
||||
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2019-11-16 00:35+0100\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2018-06-25 14:53+0200\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
|
|
|
@ -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 21:25+0200\n"
|
||||
"PO-Revision-Date: 2018-06-27 23:35+0200\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\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 +."
|
||||
|
||||
|
|
|
@ -176,7 +176,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% trans "Edit the groups" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% history_button users text=True %}
|
||||
{% history_button users text=True detailed=True %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
|
Loading…
Reference in a new issue