diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2023-11-16 16:24:51 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-04-16 16:38:33 +0200 |
commit | 01d3f76ea4f2bed48d71e78453ca2d0bd23b2342 (patch) | |
tree | 5327b984b465590b2bc81439bccb0fc7063a7ea6 | |
parent | ffd47cbfa1baa5eb2556e38253f548495b4189f1 (diff) | |
download | Ishtar-01d3f76ea4f2bed48d71e78453ca2d0bd23b2342.tar.bz2 Ishtar-01d3f76ea4f2bed48d71e78453ca2d0bd23b2342.zip |
✨ GDPR: admin - log (read only, export)
-rw-r--r-- | docs/fr/source/administrateur-applicatif.rst | 2 | ||||
-rw-r--r-- | ishtar_common/admin.py | 70 | ||||
-rw-r--r-- | ishtar_common/models.py | 15 |
3 files changed, 65 insertions, 22 deletions
diff --git a/docs/fr/source/administrateur-applicatif.rst b/docs/fr/source/administrateur-applicatif.rst index 1f11728ac..f3e94047f 100644 --- a/docs/fr/source/administrateur-applicatif.rst +++ b/docs/fr/source/administrateur-applicatif.rst @@ -323,7 +323,7 @@ Cette journalisation est activée par défaut. Elle peut être désactivée par La durée de conservation des données de journalisation est fixée à 6 mois (durée minimale préconisée par la CNIL). Cette durée peut être changée par l'administrateur serveur (paramètre `GDPR_RETENTION_PERIOD` dans le fichier `local_settings.py`) mais il convient de rester dans le cadre légal (sauf exception la durée de conservation ne peut excéder un an). Au-delà de la durée paramétrée les données sont supprimées automatiquement (un script vérifie quotidiennement la péremption des données). -Ces données sont accessibles en consultation uniquement (la suppression et la modification ne sont pas possible) via l'interface d'administrateur aux utilisateurs : statut « super utilisateur » ou dans le groupe « Administrateur RGPD » (et disposant du statut équipe : cf. :ref:`gestion des comptes <gestion-comptes>` ). +Ces données sont accessibles en consultation uniquement (la suppression et la modification ne sont pas possible) via l'interface d'administrateur aux utilisateurs : statut « super utilisateur » ou dans le groupe « Administrateur RGPD » (et disposant du statut équipe : cf. :ref:`gestion des comptes <gestion-comptes>` ). Un export de ces données en CSV est possible pour analyse. Le cas échéant, l'administrateur RGPD doit prendre des mesures organisationnelles afin de garantir une détruction de cet export une fois que celui-ci a été exploité. .. note:: Si des utilisateurs disposent d'un statut « super utilisateur » mais ne sont pas administrateur RGPD, il est nécessaire de leur retirer ce statut pour leur donner le groupe « administrateur technique ». diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index af11be06c..803b5766a 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -18,6 +18,7 @@ # See the file COPYING for details. import csv +import datetime import json from io import TextIOWrapper, BytesIO import os @@ -160,29 +161,36 @@ def export_as_csv_action( based on http://djangosnippets.org/snippets/1697/ """ opts = modeladmin.model._meta - field_names = set([field.name for field in opts.fields]) - if fields: - fieldset = set(fields) - field_names = field_names & fieldset - elif exclude: - excludeset = set(exclude) - field_names = field_names - excludeset - - if hasattr(modeladmin, "CSV_FIELD_ORDER"): - field_order = modeladmin.CSV_FIELD_ORDER - field_names = sorted( - field_names, - key=lambda x: field_order.index(x) if x in field_order else 1000 - ) + if hasattr(modeladmin, "CSV_FIELDS"): + field_names = field_order = modeladmin.CSV_FIELDS + else: + field_names = set([field.name for field in opts.fields]) + if fields: + fieldset = set(fields) + field_names = field_names & fieldset + elif exclude: + excludeset = set(exclude) + field_names = field_names - excludeset + + if hasattr(modeladmin, "CSV_FIELD_ORDER"): + field_order = modeladmin.CSV_FIELD_ORDER + field_names = sorted( + field_names, + key=lambda x: field_order.index(x) if x in field_order else 1000 + ) response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=%s.csv" % str( - opts - ).replace(".", "_") + csv_name = str(opts).replace(".", "_") + if hasattr(modeladmin, "get_csv_name"): + csv_name = modeladmin.get_csv_name() + response["Content-Disposition"] = "attachment; filename=%s.csv" % csv_name writer = csv.writer(response) if header: - writer.writerow(list(field_names)) + if hasattr(modeladmin, "CSV_HEADER"): + writer.writerow(modeladmin.CSV_HEADER) + else: + writer.writerow(list(field_names)) for obj in queryset.order_by("pk"): row = [] for field in field_names: @@ -698,6 +706,32 @@ class PersonAdmin(HistorizedObjectAdmin): admin_site.register(models.Person, PersonAdmin) +@admin.register(models.GDPRLog, site=admin_site) +class GDPRLogAdmin(admin.ModelAdmin): + list_display = ("user", "date", "ip", "activity") + list_filter = ("activity",) + search_fields = ("user__username",) + actions = [ + export_as_csv_action(exclude=("id",)), + ] + CSV_HEADER = (_("Date"), _("User"), _("IP"), _("Activity"), _("Persons")) + CSV_FIELDS = ("date", "user", "ip", "activity_lbl", "persons_lbl") + + def has_add_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + def get_csv_name(self): + now = datetime.datetime.now() + lbl = str(_("export-gdpr")) + return f"{now.strftime('%Y-%m-%d-%H%M')}-{lbl}" + + class AuthorAdmin(admin.ModelAdmin): list_display = ["person", "author_type"] list_filter = ("author_type",) diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 944cbcf5d..35c59a94e 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -3213,6 +3213,8 @@ GDPR_ACTIVITY = ( ("PD", _("Person deletion")), ) +GDPR_ACTIVITY_DICT = dict(GDPR_ACTIVITY) + class GDPRPerson(models.Model): person = models.ForeignKey(Person, verbose_name=_("Person"), on_delete=models.SET_NULL, @@ -3242,10 +3244,17 @@ class GDPRLog(models.Model): @property def activity_lbl(self): - gdpr_activity_dict = dict(GDPR_ACTIVITY) - if self.activity not in gdpr_activity_dict: + if self.activity not in GDPR_ACTIVITY_DICT: return str(_("Unknown activity :")) + self.activity - return gdpr_activity_dict[self.activity] + return GDPR_ACTIVITY_DICT[self.activity] + + @property + def persons_lbl(self): + return " ; ".join( + self.persons.through.objects.filter( + gdprlog_id=self.pk + ).values_list("gdprperson__raw_name", flat=True) + ) def __str__(self): return f"{self.user.username} - {self.date} - {self.activity_lbl}" |