Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

django-forge-log

CI PyPI License: MIT Python 3.12+

Release candidate. django-forge-log est en 1.0.0rc2 : l'API est considérée figée mais n'a pas encore été éprouvée par un usage réel en dehors de ce dépôt. Les retours (issues, cas d'usage, bugs) sont les bienvenus avant de tagger la version 1.0.0 finale — voir RELEASING.md.

Un audit trail léger et automatique — Qui, Quoi, Quand, Où, et le Diff avant/après — pour les vues Django (FBV, CBV, DRF ViewSets) et l'Admin, stocké dans une seule table JSON centrale.

Le problème

Le LogEntry natif de django.contrib.admin ne trace que ce qui passe par l'Admin. Dès qu'une mutation passe par une API DRF, une vue classique ou un script, plus rien n'est journalisé. django-simple-history/django-reversion résolvent ça en dupliquant une table par modèle suivi — lourd, et ça n'explique pas qui a fait le changement ni depuis où. django-forge-log vise une piste d'audit unique et compacte, avec un typage strict du diff.

Installation

Le cœur (middleware, décorateur, moteur de diff, Admin) ne dépend que de Django et Pydantic. Les intégrations DRF, django-signals-all et Celery sont des extras à activer séparément, à combiner selon les besoins :

# Cœur seul
uv add django-forge-log

# Avec l'intégration DRF (forge_log.drf.AuditViewSetMixin)
uv add "django-forge-log[drf]"

# Avec l'intégration bulk django-signals-all (bulk_create/bulk_update/.update())
uv add "django-forge-log[signals]"

# Avec le backend d'écriture Celery (WRITE_BACKEND="celery")
uv add "django-forge-log[celery]"

# Plusieurs extras à la fois
uv add "django-forge-log[drf,signals,celery]"

Avec pip, mêmes combinaisons :

pip install django-forge-log
pip install "django-forge-log[drf,signals,celery]"

Une release candidate n'étant pas une version finale, PyPI ne l'installe pas par défaut avec pip install django-forge-log — utilisez --pre ou fixez la version exacte tant que 1.0.0 n'est pas taggé :

uv add "django-forge-log==1.0.0rc2"
pip install "django-forge-log==1.0.0rc2"
# settings.py
INSTALLED_APPS = [
    ...,
    "django.contrib.contenttypes",
    "forge_log",
]

MIDDLEWARE = [
    ...,
    "forge_log.middleware.RequestContextMiddleware",
]
python manage.py migrate forge_log

Démarrage rapide

Vue générique (FBV/CBV)

from forge_log.decorators import track_action
from .models import Article


@track_action(Article)
def update_article(request, pk):
    article = Article.objects.get(pk=pk)
    article.status = "published"
    article.save()
    return HttpResponse(...)

Sans configuration, l'instance suivie est retrouvée via le PK présent dans les kwargs de la vue (pk, ou le nom du champ clé primaire du modèle) — le cas courant /resource/<pk>/. Pour une création (pas de PK dans l'URL avant l'exécution de la vue), fournissez get_instance explicitement, ou préférez le mixin DRF ci-dessous.

ViewSet DRF

from rest_framework.viewsets import ModelViewSet
from forge_log.drf import AuditViewSetMixin


class ArticleViewSet(AuditViewSetMixin, ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

Capture nativement create/update/partial_update/destroy, y compris les créations (contrairement à track_action, qui a besoin d'un PK dans l'URL). Nécessite l'extra django-forge-log[drf].

Admin

from django.contrib import admin
from forge_log.admin import AuditModelAdminMixin


@admin.register(Article)
class ArticleAdmin(AuditModelAdminMixin, admin.ModelAdmin):
    pass

La table ActionLog elle-même est consultable (lecture seule) dans l'Admin via forge_log.admin.ActionLogAdmin.

Opérations en masse (bulk_create, bulk_update, .update())

Ces opérations contournent Model.save() et aucune des trois intégrations ci-dessus ne peut les voir depuis la vue. En branchant django-signals-all (extra django-forge-log[signals]), les mutations bulk émises via son BulkSignalManager sont journalisées automatiquement, sans code supplémentaire :

# models.py
from django.db import models
from django_signals_all.orm.manager import BulkSignalManager


class Article(models.Model):
    ...
    objects = BulkSignalManager()
# settings.py
INSTALLED_APPS = [..., "django_signals_all", "forge_log"]

.filter(...).update(...) ne charge pas les instances modifiées : une seule entrée agrégée est journalisée (action="BULK_UPDATE", object_id=None), avec la liste des PK impactés dans metadata. bulk_create() et Manager.bulk_update() journalisent en revanche une entrée par instance.

Écriture asynchrone (WRITE_BACKEND)

Écrire dans ActionLog a un coût. Cinq backends, sélectionnables par projet ou pour un test :

Backend Comportement Durabilité Coût sur la requête
sync Écrit immédiatement Maximale, mais journalise même une transaction annulée Élevé
on_commit transaction.on_commit() Jamais loggé si rollback Élevé (toujours avant la réponse)
thread (défaut) File en mémoire + thread démon, bulk_create par lots Fenêtre de perte (~200 ms) si le process est tué Quasi nul (queue.put_nowait)
asyncio asyncio.create_task() (vues async def) Idem thread ; nécessite un event loop actif Quasi nul
celery Dispatch total via une tâche Celery Robuste (persiste dans le broker) Quasi nul
FORGE_LOG = {
    "WRITE_BACKEND": "thread",  # "sync" | "on_commit" | "thread" | "asyncio" | "celery"
}

celery nécessite l'extra django-forge-log[celery] et un broker déjà configuré côté projet.

Sécurité et PII

Le diff peut exposer des données sensibles s'il n'est pas configuré :

FORGE_LOG = {
    # Champs jamais journalisés (avant *et* après), par motif regex.
    "EXCLUDED_FIELDS": [r".*secret.*", r".*token.*", r".*_key$", r"credit_card", r"ssn"],
    # Champs journalisés comme "modifiés" sans exposer les valeurs :
    # {"password": {"masked": true}} plutôt que {"before": ..., "after": ...}.
    "MASKED_FIELDS": ["password"],
}

Ces réglages peuvent aussi être fixés par modèle, ce qui prime sur la config globale :

class Article(models.Model):
    ...
    class ForgeLogMeta:
        excluded_fields = ["internal_note"]
        masked_fields = []

Les FileField/ImageField ne journalisent jamais de contenu binaire, seulement le chemin (.name).

Rétention

python manage.py forgelog_purge --days 90         # supprime les entrées de plus de 90 jours
python manage.py forgelog_purge --days 90 --dry-run

Ou via FORGE_LOG["RETENTION_DAYS"] pour ne pas avoir à passer --days à chaque appel (à brancher sur un cron applicatif — forgelog_purge ne s'exécute jamais tout seul).

Configuration complète

FORGE_LOG = {
    "ENABLED": True,
    "WRITE_BACKEND": "thread",
    "EXCLUDED_FIELDS": [r".*secret.*", r".*token.*", r".*_key$", r"credit_card", r"ssn"],
    "MASKED_FIELDS": ["password"],
    "RETENTION_DAYS": None,
    "THREAD_FLUSH_INTERVAL": 0.2,     # secondes, backend "thread"
    "THREAD_MAX_BATCH_SIZE": 50,      # backend "thread"
    "THREAD_MAX_QUEUE_SIZE": 10_000,  # backend "thread"
}

Limitations connues

  • object_id est un CharField (pas une FK typée) pour supporter les PK non entières (UUID) sans une table par modèle — même compromis que django-reversion/django-guardian. Un index composite (content_type, object_id) compense l'absence de contrainte FK native.
  • track_action sans get_instance ne peut pas capturer une création (pas de PK dans l'URL avant l'exécution de la vue) : utilisez forge_log.drf.AuditViewSetMixin pour un ViewSet DRF, ou fournissez get_instance explicitement.
  • Backend thread : les entrées en file d'attente sont perdues si le process est tué avant le prochain flush (~THREAD_FLUSH_INTERVAL). Pour une garantie stricte de durabilité, utilisez on_commit ou celery.
  • Le diff ne compare que les champs concrets du modèle (_meta.concrete_fields) ou la liste explicite passée à fields= — pas les relations M2M implicites.
  • track_action ne détecte pas les vues async def : les décorer silencieusement ne les exécute pas correctement (voir roadmap ci-dessous).

Roadmap (1.0.0rc3)

  • Support des vues async def pour track_action (détection asyncio.iscoroutinefunction, ORM via sync_to_async).
  • Helper de requête ActionLog.objects.for_object(instance) pour l'historique d'un objet, au-dessus de l'index composite déjà en place.
  • GenericRelation optionnelle sur les modèles suivis (instance.forge_log_entries.all()).

Développement

uv sync --group dev

uv run ruff check src tests
uv run ruff format --check src tests
uv run mypy

# SQLite (par défaut, pas de dépendance externe)
uv run pytest --cov=forge_log --cov-report=term-missing

# PostgreSQL et MySQL (nécessite Docker)
docker compose up -d
FORGE_LOG_TEST_DB=postgres uv run pytest
FORGE_LOG_TEST_DB=mysql uv run pytest

Voir CONTRIBUTING.md pour contribuer, CHANGELOG.md pour l'historique des versions, et RELEASING.md pour le processus de publication.

Licence

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_forge_log-1.0.0rc2.tar.gz (93.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_forge_log-1.0.0rc2-py3-none-any.whl (27.7 kB view details)

Uploaded Python 3

File details

Details for the file django_forge_log-1.0.0rc2.tar.gz.

File metadata

  • Download URL: django_forge_log-1.0.0rc2.tar.gz
  • Upload date:
  • Size: 93.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for django_forge_log-1.0.0rc2.tar.gz
Algorithm Hash digest
SHA256 10d52189a401aaf4c0b744d8757c039a27bd9b68213c12e590ae76e32d1eb211
MD5 8cf423df545384907d83411189f7b5c8
BLAKE2b-256 79cce208028e53369a6c6ccc4f29658982d11723d972d1c08b87fbf636928517

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_forge_log-1.0.0rc2.tar.gz:

Publisher: publish.yml on alzeph/django-forge-log

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_forge_log-1.0.0rc2-py3-none-any.whl.

File metadata

File hashes

Hashes for django_forge_log-1.0.0rc2-py3-none-any.whl
Algorithm Hash digest
SHA256 d754a30f4c2998c033155e027a1060abb5a0cff47a906ea732996a1d314dacdb
MD5 f89b7baab3bdf93d569c41a4c48a42a2
BLAKE2b-256 79670fe8b6f10ace91b94abda61bf72c0512fbc89e1f491527925e77f400ac96

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_forge_log-1.0.0rc2-py3-none-any.whl:

Publisher: publish.yml on alzeph/django-forge-log

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.0.0rc2 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page