8
0
Fork 0
mirror of https://gitlab.federez.net/re2o/re2o synced 2024-07-02 04:04:06 +00:00

Merge branch 'improve_user_history' into 'dev'

Improve user history

See merge request re2o/re2o!516
This commit is contained in:
klafyvel 2020-04-23 17:06:12 +02:00
commit 5b42ca8cbe
17 changed files with 516 additions and 168 deletions

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+0200\n"
"PO-Revision-Date: 2019-01-07 01:37+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+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"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+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,6 +58,20 @@ msgstr "Date de début"
msgid "End date"
msgstr "Date de fin"
#: logs/models.py:238 logs/models.py:263
msgid "None"
msgstr "Aucun(e)"
#: logs/models.py:248
msgid "Deleted"
msgstr "Supprimé(e)"
#: logs/models.py:255 logs/models.py:260
#: logs/templates/logs/machine_history.html:55
#: logs/templates/logs/user_history.html:51
msgid "Unknown"
msgstr "Inconnu(e)"
#: logs/templates/logs/aff_stats_logs.html:36
msgid "Edited object"
msgstr "Objet modifié"
@ -77,6 +91,7 @@ msgstr "Date de modification"
#: logs/templates/logs/aff_stats_logs.html:42
#: logs/templates/logs/machine_history.html:39
#: logs/templates/logs/user_history.html:39
msgid "Comment"
msgstr "Commentaire"
@ -113,6 +128,7 @@ msgid "Rank"
msgstr "Rang"
#: logs/templates/logs/aff_summary.html:37
#: logs/templates/logs/user_history.html:36
msgid "Date"
msgstr "Date"
@ -196,15 +212,11 @@ msgstr "Résultats de la recherche"
msgid "User"
msgstr "Utilisateur"
#: logs/templates/logs/machine_history.html:55
msgid "Unknown"
msgstr "Inconnu(e)"
#: logs/templates/logs/machine_history.html:62
msgid "Now"
msgstr "Maintenant"
#: logs/templates/logs/machine_history.html:70
#: logs/templates/logs/machine_history.html:71
msgid "No result"
msgstr "Aucun résultat"
@ -253,6 +265,27 @@ 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
msgid "Nonexistent revision."
msgstr "Révision inexistante."
@ -377,14 +410,14 @@ msgstr "droits"
msgid "actions"
msgstr "actions"
#: logs/views.py:529
#: logs/views.py:554
msgid "No model found."
msgstr "Aucun modèle trouvé."
#: logs/views.py:535
#: logs/views.py:560
msgid "Nonexistent entry."
msgstr "Entrée inexistante."
#: logs/views.py:542
#: logs/views.py:567
msgid "You don't have the right to access this menu."
msgstr "Vous n'avez pas le droit d'accéder à ce menu."

View file

@ -22,14 +22,19 @@
The models definitions for the logs app
"""
from reversion.models import Version
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import Group
from machines.models import IpList
from machines.models import Interface
from machines.models import Machine
from users.models import User
from users.models import Adherent
from users.models import Club
from topologie.models import Room
class HistoryEvent:
class MachineHistoryEvent:
def __init__(self, user, machine, interface, start=None, end=None):
"""
:param user: User, The user owning the maching at the time of the event
@ -80,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 HistoryEvent
:return: list or None, a list of MachineHistoryEvent
"""
self.start = params.get("s", None)
self.end = params.get("e", None)
@ -101,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 = HistoryEvent(user, machine, interface)
evt = MachineHistoryEvent(user, machine, interface)
evt.start_date = interface.revision.date_created
# Try not to recreate events if it's unnecessary
@ -177,7 +182,7 @@ class MachineHistory:
def __get_by_ip(self, ip):
"""
:param ip: str, The IP to lookup
:returns: list, a list of HistoryEvent
:returns: list, a list of MachineHistoryEvent
"""
interfaces = self.__get_interfaces_for_ip(ip)
@ -193,7 +198,7 @@ class MachineHistory:
def __get_by_mac(self, mac):
"""
:param mac: str, The MAC address to lookup
:returns: list, a list of HistoryEvent
:returns: list, a list of MachineHistoryEvent
"""
interfaces = self.__get_interfaces_for_mac(mac)
@ -205,3 +210,205 @@ class MachineHistory:
self.__add_revision(user, machine, interface)
return self.events
class UserHistoryEvent:
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
"""
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):
"""
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 == "groups":
if len(value) == 0:
# Removed all the user's groups
return _("None")
# value is a list of ints
groups = []
for gid in value:
# Try to get the group name, if it's not deleted
try:
groups.append(Group.objects.get(id=gid).name)
except Group.DoesNotExist:
# TODO: Find the group name in the versions?
groups.append("{} ({})".format(_("Deleted"), gid))
return ", ".join(groups)
elif name == "state":
if value is not None:
return User.STATES[value][1]
else:
return _("Unknown")
elif name == "email_state":
if value is not None:
return User.EMAIL_STATES[value][1]
else:
return _("Unknown")
elif name == "room_id" and value is not None:
# Try to get the room name, if it's not deleted
try:
return Room.objects.get(id=value)
except Room.DoesNotExist:
# TODO: Find the room name in the versions?
return "{} ({})".format(_("Deleted"), value)
elif name == "members" or name == "administrators":
if len(value) == 0:
# Removed all the club's members
return _("None")
# value is a list of ints
users = []
for uid in value:
# Try to get the user's name, if theyr're not deleted
try:
users.append(User.objects.get(id=uid).pseudo)
except User.DoesNotExist:
# TODO: Find the user's name in the versions?
users.append("{} ({})".format(_("Deleted"), uid))
return ", ".join(users)
if value is None:
return _("None")
return value
def edits(self, hide=["password", "pwd_ntlm", "gpg_fingerprint"]):
"""
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
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
and self.comment == other.comment
)
def __hash__(self):
return hash((self.user.id, frozenset(self.edited_fields), self.date, self.performed_by, self.comment))
def __repr__(self):
return "{} edited fields {} of {} ({})".format(
self.performed_by,
self.edited_fields or "nothing",
self.user,
self.comment or "No comment"
)
class UserHistory:
def __init__(self):
self.events = []
self.__last_version = None
def get(self, user):
"""
:param user: User, 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)
# Get all the versions for this user, with the oldest first
self.__last_version = None
user_versions = filter(
lambda x: x.field_dict["id"] == user.id,
Version.objects.get_for_model(User).order_by("revision__date_created")
)
for version in user_versions:
self.__add_revision(user, version)
# 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")
)
for version in obj_versions:
self.__add_revision(user, version)
# Remove duplicates and sort
self.events = list(dict.fromkeys(self.events))
return sorted(
self.events,
key=lambda e: e.date,
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
: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)
# Ignore "empty" events like login
if not diff:
self.__last_version = version
return
evt = UserHistoryEvent(user, version, self.__last_version, diff)
self.events.append(evt)
self.__last_version = version

View file

@ -0,0 +1,80 @@
{% extends 'logs/sidebar.html' %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2020 Jean-Romain Garnier
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load i18n %}
{% block title %}{% trans "History" %}{% endblock %}
{% block content %}
<h2>{% blocktrans %}History of {{ user }}{% endblocktrans %}</h2>
{% if events %}
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "Performed by" %}</th>
<th>{% trans "Edited" %}</th>
<th>{% trans "Comment" %}</th>
</tr>
</thead>
{% for event in events %}
<tr>
<td>{{ event.date }}</td>
<td>
{% if event.performed_by %}
<a href="{% url 'users:profil' userid=event.performed_by.id %}" title=tr_view_the_profile>
{{ event.performed_by }}
</a>
{% else %}
{% trans "Unknown" %}
{% endif %}
</td>
<td>
{% for edit in event.edits %}
{% if edit.1 is None and edit.2 is None %}
<strong>{{ edit.0 }}</strong><br/>
{% elif edit.1 is None %}
<strong>{{ edit.0 }}:</strong>
<i class="text-success"> {{ edit.2 }}</i><br/>
{% else %}
<strong>{{ edit.0 }}:</strong>
<i class="text-danger"> {{ edit.1 }} </i>
<i class="text-success">{{ edit.2 }}</i><br/>
{% endif %}
{% endfor %}
</td>
<td>{{ event.comment }}</td>
</tr>
{% endfor %}
</table>
{% include 'pagination.html' with list=events %}
{% else %}
<h3>{% trans "No event" %}</h3>
{% endif %}
<br />
<br />
<br />
{% endblock %}

View file

@ -47,4 +47,5 @@ urlpatterns = [
name="history",
),
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"),
]

View file

@ -99,9 +99,9 @@ from re2o.utils import (
all_active_interfaces_count,
)
from re2o.base import re2o_paginator, SortTable
from re2o.acl import can_view_all, can_view_app, can_edit_history
from re2o.acl import can_view_all, can_view_app, can_edit_history, can_view
from .models import MachineHistory
from .models import MachineHistory, UserHistory
from .forms import MachineHistoryForm
@ -508,6 +508,26 @@ 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)
max_result = GeneralOption.get_cached_value("pagination_number")
events = re2o_paginator(
request,
events,
max_result
)
return render(
request,
"logs/user_history.html",
{ "user": users, "events": events },
)
def history(request, application, object_name, object_id):
"""Render history for a model.

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+0200\n"
"PO-Revision-Date: 2018-06-23 16:35+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+0200\n"
"PO-Revision-Date: 2019-11-16 00:22+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-23 11:55+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+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:125
#: preferences/models.py:122
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:130
#: preferences/models.py:127
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:134
#: preferences/models.py:131
msgid "A new user can create their account on Re2o."
msgstr "Un nouvel utilisateur peut créer son compte sur Re2o."
#: preferences/models.py:139
#: preferences/models.py:136
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:146
#: preferences/models.py:143
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:153
#: preferences/models.py:150
msgid "If True, archived users are allowed to connect."
msgstr "Si True, les utilisateurs archivés sont autorisés à se connecter."
#: preferences/models.py:157
#: preferences/models.py:154
msgid "Can view the user preferences"
msgstr "Peut voir les préférences d'utilisateur"
#: preferences/models.py:158
#: preferences/models.py:155
msgid "user preferences"
msgstr "Préférences d'utilisateur"
#: preferences/models.py:165
#: preferences/models.py:162
msgid "Email domain must begin with @."
msgstr "Un domaine mail doit commencer par @."
#: preferences/models.py:183
#: preferences/models.py:180
msgid "Automatic configuration by RA"
msgstr "Configuration automatique par RA"
#: preferences/models.py:184
#: preferences/models.py:181
msgid "IP addresses assignment by DHCPv6"
msgstr "Attribution d'adresses IP par DHCPv6"
#: preferences/models.py:185
#: preferences/models.py:182
msgid "Disabled"
msgstr "Désactivé"
#: preferences/models.py:194
#: preferences/models.py:191
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:204
#: preferences/models.py:201
msgid "Can view the machine preferences"
msgstr "Peut voir les préférences de machine"
#: preferences/models.py:205
#: preferences/models.py:202
msgid "machine preferences"
msgstr "Préférences de machine"
#: preferences/models.py:225 preferences/models.py:687
#: preferences/models.py:222 preferences/models.py:684
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:226 preferences/models.py:688
#: preferences/models.py:223 preferences/models.py:685
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:232
#: preferences/models.py:229
msgid "Web management, activated in case of automatic provision."
msgstr "Gestion web, activée en cas de provision automatique."
#: preferences/models.py:237
#: preferences/models.py:234
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:243
#: preferences/models.py:240
msgid "REST management, activated in case of automatic provision."
msgstr "Gestion REST, activée en cas de provision automatique."
#: preferences/models.py:250
#: preferences/models.py:247
msgid "IP range for the management of switches."
msgstr "Plage d'IP pour la gestion des commutateurs réseau."
#: preferences/models.py:256
#: preferences/models.py:253
msgid "Provision of configuration mode for switches."
msgstr "Mode de provision de configuration pour les commutateurs réseau."
#: preferences/models.py:259
#: preferences/models.py:256
msgid "SFTP login for switches."
msgstr "Identifiant SFTP pour les commutateurs réseau."
#: preferences/models.py:262
#: preferences/models.py:259
msgid "SFTP password."
msgstr "Mot de passe SFTP."
#: preferences/models.py:367
#: preferences/models.py:364
msgid "Can view the topology preferences"
msgstr "Peut voir les préférences de topologie"
#: preferences/models.py:369
#: preferences/models.py:366
msgid "topology preferences"
msgstr "préférences de topologie"
#: preferences/models.py:382
#: preferences/models.py:379
msgid "RADIUS key."
msgstr "Clé RADIUS."
#: preferences/models.py:384
#: preferences/models.py:381
msgid "Comment for this key."
msgstr "Commentaire pour cette clé."
#: preferences/models.py:387
#: preferences/models.py:384
msgid "Default key for switches."
msgstr "Clé par défaut pour les commutateurs réseau."
#: preferences/models.py:391
#: preferences/models.py:388
msgid "Can view a RADIUS key object"
msgstr "Peut voir un objet clé RADIUS"
#: preferences/models.py:392 preferences/views.py:335
#: preferences/models.py:389 preferences/views.py:335
msgid "RADIUS key"
msgstr "Clé RADIUS"
#: preferences/models.py:393
#: preferences/templates/preferences/display_preferences.html:223
#: preferences/models.py:390
#: preferences/templates/preferences/display_preferences.html:221
msgid "RADIUS keys"
msgstr "clés RADIUS"
#: preferences/models.py:400
#: preferences/models.py:397
msgid "Default RADIUS key for switches already exists."
msgstr "Clé par défaut pour les commutateurs réseau."
#: preferences/models.py:403
#: preferences/models.py:400
msgid "RADIUS key "
msgstr "clé RADIUS "
#: preferences/models.py:409
#: preferences/models.py:406
msgid "Switch login."
msgstr "Identifiant du commutateur réseau."
#: preferences/models.py:410
#: preferences/models.py:407
msgid "Password."
msgstr "Mot de passe."
#: preferences/models.py:412
#: preferences/models.py:409
msgid "Default credentials for switches."
msgstr "Identifiants par défaut pour les commutateurs réseau."
#: preferences/models.py:419
#: preferences/models.py:416
msgid "Can view a switch management credentials object"
msgstr "Peut voir un objet identifiants de gestion de commutateur réseau"
#: preferences/models.py:422 preferences/views.py:397
#: preferences/models.py:419 preferences/views.py:397
msgid "switch management credentials"
msgstr "identifiants de gestion de commutateur réseau"
#: preferences/models.py:425
#: preferences/models.py:422
msgid "Switch login "
msgstr "Identifiant du commutateur réseau "
#: preferences/models.py:437
#: preferences/models.py:434
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:443
#: preferences/models.py:440
msgid "Message displayed specifically for this reminder."
msgstr "Message affiché spécifiquement pour ce rappel."
#: preferences/models.py:447
#: preferences/models.py:444
msgid "Can view a reminder object"
msgstr "Peut voir un objet rappel"
#: preferences/models.py:448 preferences/views.py:280
#: preferences/models.py:445 preferences/views.py:280
msgid "reminder"
msgstr "rappel"
#: preferences/models.py:449
#: preferences/models.py:446
msgid "reminders"
msgstr "rappels"
#: preferences/models.py:470
#: preferences/models.py:467
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:478
#: preferences/models.py:475
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:493
#: preferences/models.py:490
msgid "Can view the general preferences"
msgstr "Peut voir les préférences générales"
#: preferences/models.py:494
#: preferences/models.py:491
msgid "general preferences"
msgstr "préférences générales"
#: preferences/models.py:514
#: preferences/models.py:511
msgid "Can view the service preferences"
msgstr "Peut voir les préférences de service"
#: preferences/models.py:515 preferences/views.py:231
#: preferences/models.py:512 preferences/views.py:231
msgid "service"
msgstr "service"
#: preferences/models.py:516
#: preferences/models.py:513
msgid "services"
msgstr "services"
#: preferences/models.py:526
#: preferences/models.py:523
msgid "Contact email address."
msgstr "Adresse mail de contact."
#: preferences/models.py:532
#: preferences/models.py:529
msgid "Description of the associated email address."
msgstr "Description de l'adresse mail associée."
#: preferences/models.py:542
#: preferences/models.py:539
msgid "Can view a contact email address object"
msgstr "Peut voir un objet adresse mail de contact"
#: preferences/models.py:544
#: preferences/models.py:541
msgid "contact email address"
msgstr "adresse mail de contact"
#: preferences/models.py:545
#: preferences/models.py:542
msgid "contact email addresses"
msgstr "adresses mail de contact"
#: preferences/models.py:553 preferences/views.py:635
#: preferences/models.py:550 preferences/views.py:635
msgid "mandate"
msgstr "mandat"
#: preferences/models.py:554
#: preferences/models.py:551
msgid "mandates"
msgstr "mandats"
#: preferences/models.py:555
#: preferences/models.py:552
msgid "Can view a mandate object"
msgstr "Peut voir un objet mandat"
#: preferences/models.py:562
#: preferences/models.py:559
msgid "president of the association"
msgstr "président de l'association"
#: preferences/models.py:563
#: preferences/models.py:560
msgid "Displayed on subscription vouchers."
msgstr "Affiché sur les reçus de cotisation."
#: preferences/models.py:565
#: preferences/models.py:562
msgid "start date"
msgstr "date de début"
#: preferences/models.py:566
#: preferences/models.py:563
msgid "end date"
msgstr "date de fin"
#: preferences/models.py:580
#: preferences/models.py:577
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:596
#: preferences/models.py:593
msgid "Networking organisation school Something"
msgstr "Association de réseau de l'école Machin"
#: preferences/models.py:599
#: preferences/models.py:596
msgid "Threadneedle Street"
msgstr "1 rue de la Vrillière"
#: preferences/models.py:600
#: preferences/models.py:597
msgid "London EC2R 8AH"
msgstr "75001 Paris"
#: preferences/models.py:603
#: preferences/models.py:600
msgid "Organisation"
msgstr "Association"
#: preferences/models.py:610
#: preferences/models.py:607
msgid "Can view the organisation preferences"
msgstr "Peut voir les préférences d'association"
#: preferences/models.py:611
#: preferences/models.py:608
msgid "organisation preferences"
msgstr "préférences d'association"
#: preferences/models.py:629
#: preferences/models.py:626
msgid "Can view the homepage preferences"
msgstr "Peut voir les préférences de page d'accueil"
#: preferences/models.py:630
#: preferences/models.py:627
msgid "homepage preferences"
msgstr "Préférences de page d'accueil"
#: preferences/models.py:644
#: preferences/models.py:641
msgid "Welcome email in French."
msgstr "Mail de bienvenue en français."
#: preferences/models.py:647
#: preferences/models.py:644
msgid "Welcome email in English."
msgstr "Mail de bienvenue en anglais."
#: preferences/models.py:652
#: preferences/models.py:649
msgid "Can view the email message preferences"
msgstr "Peut voir les préférences de message pour les mails"
#: preferences/models.py:654
#: preferences/models.py:651
msgid "email message preferences"
msgstr "préférences de messages pour les mails"
#: preferences/models.py:659
#: preferences/models.py:656
msgid "RADIUS attribute"
msgstr "attribut RADIUS"
#: preferences/models.py:660
#: preferences/models.py:657
msgid "RADIUS attributes"
msgstr "attributs RADIUS"
#: preferences/models.py:664 preferences/views.py:588
#: preferences/models.py:661 preferences/views.py:588
msgid "attribute"
msgstr "attribut"
#: preferences/models.py:665
#: preferences/models.py:662
msgid "See https://freeradius.org/rfc/attributes.html."
msgstr "Voir https://freeradius.org/rfc/attributes.html."
#: preferences/models.py:667
#: preferences/models.py:664
msgid "value"
msgstr "valeur"
#: preferences/models.py:669
#: preferences/models.py:666
msgid "comment"
msgstr "commentaire"
#: preferences/models.py:670
#: preferences/models.py:667
msgid "Use this field to document this attribute."
msgstr "Utilisez ce champ pour documenter cet attribut."
#: preferences/models.py:681
#: preferences/models.py:678
msgid "RADIUS policy"
msgstr "politique de RADIUS"
#: preferences/models.py:682
#: preferences/templates/preferences/display_preferences.html:301
#: preferences/models.py:679
#: preferences/templates/preferences/display_preferences.html:299
msgid "RADIUS policies"
msgstr "politiques de RADIUS"
#: preferences/models.py:693
#: preferences/models.py:690
msgid "Reject the machine"
msgstr "Rejeter la machine"
#: preferences/models.py:694
#: preferences/models.py:691
msgid "Place the machine on the VLAN"
msgstr "Placer la machine sur le VLAN"
#: preferences/models.py:703
#: preferences/models.py:700
msgid "policy for unknown machines"
msgstr "politique pour les machines inconnues"
#: preferences/models.py:711
#: preferences/models.py:708
msgid "unknown machines VLAN"
msgstr "VLAN pour les machines inconnues"
#: preferences/models.py:712
#: preferences/models.py:709
msgid "VLAN for unknown machines if not rejected."
msgstr "VLAN pour les machines inconnues si non rejeté."
#: preferences/models.py:718
#: preferences/models.py:715
msgid "unknown machines attributes"
msgstr "attributs pour les machines inconnues"
#: preferences/models.py:719
#: preferences/models.py:716
msgid "Answer attributes for unknown machines."
msgstr "Attributs de réponse pour les machines inconnues."
#: preferences/models.py:725
#: preferences/models.py:722
msgid "policy for unknown ports"
msgstr "politique pour les ports inconnus"
#: preferences/models.py:733
#: preferences/models.py:730
msgid "unknown ports VLAN"
msgstr "VLAN pour les ports inconnus"
#: preferences/models.py:734
#: preferences/models.py:731
msgid "VLAN for unknown ports if not rejected."
msgstr "VLAN pour les ports inconnus si non rejeté."
#: preferences/models.py:740
#: preferences/models.py:737
msgid "unknown ports attributes"
msgstr "attributs pour les ports inconnus"
#: preferences/models.py:741
#: preferences/models.py:738
msgid "Answer attributes for unknown ports."
msgstr "Attributs de réponse pour les ports inconnus."
#: preferences/models.py:748
#: preferences/models.py:745
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:758
#: preferences/models.py:755
msgid "unknown rooms VLAN"
msgstr "VLAN pour les chambres inconnues"
#: preferences/models.py:759
#: preferences/models.py:756
msgid "VLAN for unknown rooms if not rejected."
msgstr "VLAN pour les chambres inconnues si non rejeté."
#: preferences/models.py:765
#: preferences/models.py:762
msgid "unknown rooms attributes"
msgstr "attributs pour les chambres inconnues"
#: preferences/models.py:766
#: preferences/models.py:763
msgid "Answer attributes for unknown rooms."
msgstr "Attributs de réponse pour les chambres inconnues."
#: preferences/models.py:772
#: preferences/models.py:769
msgid "policy for non members"
msgstr "politique pour les non adhérents"
#: preferences/models.py:780
#: preferences/models.py:777
msgid "non members VLAN"
msgstr "VLAN pour les non adhérents"
#: preferences/models.py:781
#: preferences/models.py:778
msgid "VLAN for non members if not rejected."
msgstr "VLAN pour les non adhérents si non rejeté."
#: preferences/models.py:787
#: preferences/models.py:784
msgid "non members attributes"
msgstr "attributs pour les non adhérents"
#: preferences/models.py:788
#: preferences/models.py:785
msgid "Answer attributes for non members."
msgstr "Attributs de réponse pour les non adhérents."
#: preferences/models.py:794
#: preferences/models.py:791
msgid "policy for banned users"
msgstr "politique pour les utilisateurs bannis"
#: preferences/models.py:802
#: preferences/models.py:799
msgid "banned users VLAN"
msgstr "VLAN pour les utilisateurs bannis"
#: preferences/models.py:803
#: preferences/models.py:800
msgid "VLAN for banned users if not rejected."
msgstr "VLAN pour les utilisateurs bannis si non rejeté."
#: preferences/models.py:809
#: preferences/models.py:806
msgid "banned users attributes"
msgstr "attributs pour les utilisateurs bannis"
#: preferences/models.py:810
#: preferences/models.py:807
msgid "Answer attributes for banned users."
msgstr "Attributs de réponse pour les utilisateurs bannis."
#: preferences/models.py:823
#: preferences/models.py:820
msgid "accepted users attributes"
msgstr "attributs pour les utilisateurs acceptés"
#: preferences/models.py:824
#: preferences/models.py:821
msgid "Answer attributes for accepted users."
msgstr "Attributs de réponse pour les utilisateurs acceptés."
#: preferences/models.py:851
#: preferences/models.py:848
msgid "subscription preferences"
msgstr "préférences de cotisation"
#: preferences/models.py:855
#: preferences/models.py:852
msgid "template for invoices"
msgstr "modèle pour les factures"
#: preferences/models.py:862
#: preferences/models.py:859
msgid "template for subscription vouchers"
msgstr "modèle pour les reçus de cotisation"
#: preferences/models.py:868
#: preferences/models.py:865
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:870
#: preferences/models.py:867
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:882
#: preferences/models.py:879
msgid "template"
msgstr "modèle"
#: preferences/models.py:883
#: preferences/models.py:880
msgid "name"
msgstr "nom"
#: preferences/models.py:886
#: preferences/models.py:883
msgid "document template"
msgstr "modèle de document"
#: preferences/models.py:887
#: preferences/models.py:884
msgid "document templates"
msgstr "modèles de document"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+0200\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+0200\n"
"PO-Revision-Date: 2018-06-24 20:10+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"

View file

@ -23,7 +23,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% endcomment %}
{% 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 %}">
<i class="fa fa-history"></i> {% if text %}{% trans "History" %}{% endif %}
</a>
{% else %}
<a {% if class%}class="btn btn-info btn-sm"{% endif %} role="button" title="{% trans "History" %}" href="{% url 'logs:history' application name id %}">
<i class="fa fa-history"></i> {% if text %}{% trans "History" %}{% endif %}
</a>
{% endif %}

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+0200\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-23 03:10+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+0200\n"
"PO-Revision-Date: 2019-11-16 00:35+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"
@ -30,65 +30,65 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: tickets/forms.py:76
#: tickets/forms.py:78
msgid "comment"
msgstr "commentaire"
#: tickets/models.py:57
#: tickets/models.py:58
msgid "Title of the ticket."
msgstr "Titre du ticket."
#: tickets/models.py:66
#: tickets/models.py:67
msgid "An email address to get back to you."
msgstr "Une adresse mail pour vous recontacter."
#: tickets/models.py:70
#: tickets/models.py:71
msgid "Language of the ticket."
msgstr "Langue des tickets"
#: tickets/models.py:74 tickets/models.py:170
#: tickets/models.py:76 tickets/models.py:173
msgid "Can view a ticket object"
msgstr "Peut voir un objet ticket"
#: tickets/models.py:75 tickets/models.py:171
#: tickets/models.py:77 tickets/models.py:174
msgid "ticket"
msgstr "ticket"
#: tickets/models.py:76 tickets/models.py:172
#: tickets/models.py:78 tickets/models.py:175
msgid "tickets"
msgstr "tickets"
#: tickets/models.py:80
#: tickets/models.py:82
#, python-format
msgid "Ticket from %(name)s. Date: %(date)s."
msgstr "Ticket de %(name)s. Date : %(date)s."
#: tickets/models.py:82
#: tickets/models.py:84
#, python-format
msgid "Anonymous ticket. Date: %s."
msgstr "Ticket anonyme. Date : %s."
#: tickets/models.py:90 tickets/templates/tickets/aff_ticket.html:52
#: tickets/models.py:92 tickets/templates/tickets/aff_ticket.html:52
msgid "Anonymous user"
msgstr "Utilisateur anonyme"
#: tickets/models.py:128
#: tickets/models.py:130
msgid "You don't have the right to view other tickets than yours."
msgstr "Vous n'avez pas le droit de voir d'autres tickets que les vôtres."
#: tickets/models.py:140 tickets/models.py:214
#: tickets/models.py:142 tickets/models.py:217
msgid "You don't have the right to view the list of tickets."
msgstr "Vous n'avez pas le droit de voir la liste des tickets."
#: tickets/models.py:187
#: tickets/models.py:190
msgid "You don't have the right to view other tickets comments than yours."
msgstr "Vous n'avez pas le droit de voir d'autres tickets que les vôtres."
#: tickets/models.py:202
#: tickets/models.py:205
msgid "You don't have the right to edit other tickets comments than yours."
msgstr "Vous n'avez pas le droit d'éditer d'autres tickets que les vôtres."
#: tickets/models.py:232
#: tickets/models.py:236
msgid "Update of your ticket"
msgstr "Mise à jour de votre ticket"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+0200\n"
"PO-Revision-Date: 2018-06-25 14:53+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"

View file

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 19:00+0200\n"
"POT-Creation-Date: 2020-04-23 14:44+0200\n"
"PO-Revision-Date: 2018-06-27 23:35+0200\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"