summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
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
commit01d3f76ea4f2bed48d71e78453ca2d0bd23b2342 (patch)
tree5327b984b465590b2bc81439bccb0fc7063a7ea6
parentffd47cbfa1baa5eb2556e38253f548495b4189f1 (diff)
downloadIshtar-01d3f76ea4f2bed48d71e78453ca2d0bd23b2342.tar.bz2
Ishtar-01d3f76ea4f2bed48d71e78453ca2d0bd23b2342.zip
✨ GDPR: admin - log (read only, export)
-rw-r--r--docs/fr/source/administrateur-applicatif.rst2
-rw-r--r--ishtar_common/admin.py70
-rw-r--r--ishtar_common/models.py15
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}"