L'application mayan/apps/events/ est le système d'audit et de notification de Mayan EDMS. Elle enregistre toutes les actions significatives (création, modification, suppression de documents, etc.) sous forme d'événements, notifie les utilisateurs abonnés, et permet l'export et la purge du journal.
La librairie sous-jacente est django-activity-stream (actstream), qui stocke les événements comme des objets Action avec acteur, verbe, cible et objet d'action.
EventTypeNamespaceRegroupe logiquement un ensemble de types d'événements sous un préfixe commun :
# Dans events.py d'une app
event_namespace = EventTypeNamespace(name='documents', label=_('Documents'))
event_document_created = event_namespace.add_event_type(
name='document_create', label=_('Document created')
)
name unique (ex. 'documents').name retournent la même instance.events.py de chaque app via AppsModuleLoaderMixin.EventTypeReprésente un événement précis. Son identifiant est namespace.name + '.' + event.name :
event_document_created.id # → 'documents.document_create'
Au démarrage, chaque EventType crée ou retrouve son enregistrement StoredEventType en DB.
StoredEventTypeMiroir DB d'un EventType. Utilisé pour les FK dans les modèles d'abonnement et de notification :
class StoredEventType(models.Model):
name = CharField(max_length=64, unique=True) # ex. 'documents.document_create'
event_document_created.commit(
actor=request.user, # qui a fait l'action
target=document, # l'objet principal concerné
action_object=None # objet secondaire optionnel
)
commit() sérialise les IDs des objets et délègue à task_event_commit (Celery). La tâche re-récupère les objets et appelle _commit().
Activé via MAYAN_EVENTS_DISABLE_ASYNCHRONOUS_MODE=true. L'événement est commité dans le même process. Utile pour les tests ou les environnements sans Celery.
_commit()actstream.action.send() → crée l'objet Action en DB.EventSubscription).ObjectEventSubscription).Notification pour chacun.@method_eventDécorateur pour émettre automatiquement un événement lors de l'appel d'une méthode de modèle :
from mayan.apps.events.decorators import method_event
from mayan.apps.events.event_managers import EventManagerSave
class Document(models.Model):
@method_event(
event_manager_class=EventManagerSave,
created={
'event': event_document_created,
'target': 'self',
},
edited={
'event': event_document_edited,
'target': 'self',
}
)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
EventManager)| Classe | Ordre | Utilisation |
|---|---|---|
EventManagerMethodAfter |
Après la méthode | Événement simple après l'appel |
EventManagerSave |
Après le save() |
Distingue création (created) vs modification (edited) |
Avant d'appeler une méthode décorée, on peut passer des arguments d'événement via des attributs _event_* sur l'instance :
document._event_actor = request.user
document._event_target = document
document.save()
Le décorateur les lit via pop_event_attributes() avant et après l'appel. L'attribut _event_ignore = True annule complètement l'émission de l'événement.
EventSubscription — abonnement globalUn utilisateur s'abonne à tous les événements d'un type donné dans le système.
User ──FK──► EventSubscription ◄──FK── StoredEventType
ObjectEventSubscription — abonnement à un objetUn utilisateur s'abonne aux événements d'un type donné sur un objet précis (GenericFK).
User ──FK──► ObjectEventSubscription ◄──FK── StoredEventType
│
GenericFK → n'importe quel objet Django
Notification — notification utilisateurCréée automatiquement lors d'un _commit() pour chaque utilisateur abonné.
class Notification(models.Model):
user = ForeignKey(User)
action = ForeignKey(Action) # l'objet actstream
read = BooleanField(default=False)
EventModelRegistryEnregistre un modèle pour participer au système d'événements :
EventModelRegistry.register(
model=Document,
bind_events_link=True, # ajoute le lien "Événements" dans le menu
bind_subscription_link=True, # ajoute le lien "Abonnements" dans le menu
register_permissions=True # enregistre les permissions events_view/clear/export
)
Appelle en interne actstream.registry.register(model) pour activer le tracking actstream sur ce modèle.
ModelEventTypeAssocie des types d'événements à un modèle (utilisé pour les abonnements sélectifs) :
ModelEventType.register(
model=Document,
event_types=(event_document_created, event_document_edited, event_document_deleted)
)
register_inheritance(model, related) permet qu'un modèle hérite des abonnements d'un modèle parent.
EventLogPruneBackend)Tâche périodique Celery (task_event_prune) qui appelle le backend de purge configuré.
| Backend | Paramètre | Comportement |
|---|---|---|
EventLogPruneBackendLatest |
number |
Garde les N derniers événements globalement |
EventLogPruneBackendLatestPerObject |
number |
Garde les N derniers événements par objet cible |
EventLogPruneBackendLatestPerObjectEventType |
number |
Garde les N derniers par objet et par type d'événement |
EventLogPruneBackendOlderThanDays |
days |
Supprime les événements plus anciens que N jours |
Configuration dans config.yml :
EVENTS_PRUNE_BACKEND: mayan.apps.events.event_prune_backends.EventLogPruneBackendOlderThanDays
EVENTS_PRUNE_BACKEND_ARGUMENTS:
days: 90
EVENTS_PRUNE_TASK_INTERVAL: 86400 # secondes (1 jour)
ActionExporter)Exporte le journal d'événements en CSV vers un DownloadFile (disponible dans la zone de téléchargement) :
ActionExporter(queryset=Action.objects.all()).export_to_download_file(user=request.user)
Champs exportés par défaut : timestamp, id, actor, target, verb, action_object (avec content_type et object_id pour chaque).
Après l'export, un Message est envoyé à l'utilisateur avec le lien de téléchargement.
| Tâche | Description |
|---|---|
task_event_commit |
Commit asynchrone d'un événement (retry sur OperationalError) |
task_event_prune |
Exécute le backend de purge configuré |
task_event_queryset_clear |
Supprime un queryset d'événements (avec vérification ACL) |
task_event_queryset_export |
Exporte un queryset d'événements en CSV |
| Permission | Usage |
|---|---|
permission_events_view |
Voir les événements d'un objet |
permission_events_clear |
Effacer les événements d'un objet |
permission_events_export |
Exporter les événements en CSV |
| Variable d'environnement | Défaut | Description |
|---|---|---|
MAYAN_EVENTS_DISABLE_ASYNCHRONOUS_MODE |
False |
Mode synchrone (avant v4.5) |
MAYAN_EVENTS_PRUNE_BACKEND |
(vide) | Chemin Python du backend de purge |
MAYAN_EVENTS_PRUNE_BACKEND_ARGUMENTS |
{} |
Arguments du backend de purge |
MAYAN_EVENTS_PRUNE_TASK_INTERVAL |
86400 |
Intervalle de purge en secondes |
# myapp/events.py
from mayan.apps.events.classes import EventTypeNamespace
from django.utils.translation import gettext_lazy as _
event_namespace = EventTypeNamespace(name='myapp', label=_('My App'))
event_thing_created = event_namespace.add_event_type(
name='thing_create', label=_('Thing created')
)
# myapp/models.py
from mayan.apps.events.decorators import method_event
from mayan.apps.events.event_managers import EventManagerSave
from .events import event_thing_created, event_thing_edited
class Thing(models.Model):
name = CharField(max_length=128)
@method_event(
event_manager_class=EventManagerSave,
created={'event': event_thing_created, 'target': 'self'},
edited={'event': event_thing_edited, 'target': 'self'},
)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# myapp/apps.py
from mayan.apps.events.classes import EventModelRegistry
from .models import Thing
class MyApp(MayanAppConfig):
def ready(self):
super().ready()
EventModelRegistry.register(model=Thing)