django-simple-history

Automatically tracks every create, update, and delete on your Django models, with a full admin UI to browse, diff, and revert history.

Library
PyPI
v3.13.0
2,463stars
BSD 3-Clause License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
82/100Excellent
Development Activity84
Maintenance64
Community80
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
86/100Excellent
Architecture85
Code Quality90
Innovation78
Learning Curve90

django-simple-history is a Django app that transparently records the full change history of your models. Attach a HistoricalRecords() descriptor to any model and the library generates a parallel Historical<Model> table that captures a snapshot on every create, update, and delete, preserving the original model’s field types instead of collapsing changes into a generic JSON blob.

Beyond raw logging, it ships an admin integration that lets staff browse past versions and revert with one click, a diff_against() API for field-level change comparisons, and a register() function for tracking third-party models you don’t own. A middleware-backed context var can auto-populate the user responsible for each change, and the project maintains compatibility across the current Django and Python support matrix with Postgres, MySQL, and SQLite.

What You Get

  • A HistoricalRecords() model field that auto-generates a historical shadow model, table, and migration for any Django model you track
  • SimpleHistoryAdmin, a drop-in admin class that adds a version history view with browse and one-click revert
  • diff_against() for structured field-level diffing between any two historical records, including many-to-many changes
  • register() for retroactively tracking third-party or vendor models without modifying their source
  • HistoryRequestMiddleware for automatically attaching the requesting user to each historical record
  • Signals (pre_create_historical_record, post_create_historical_record, etc.) for hooking custom logic into the history-recording lifecycle

Common Use Cases

  • Adding an audit trail to admin-managed models so staff can see who changed what and when
  • Building a “revert to previous version” feature for content or configuration records
  • Meeting compliance/audit requirements that demand a durable, queryable change log per row
  • Tracking changes on third-party app models (e.g. Django’s own User model) without forking them
  • Powering timeline or activity-feed features from diff_against() output

Under The Hood

Architecture Attaching HistoricalRecords() to a model hooks into Django’s model metaclass at app-loading time to dynamically create a companion Historical<Model> model with its own table and migration, then wires post_save/post_delete/m2m_changed signals (signals.py) to write a snapshot row on every mutation. HistoryManager/HistoricalQuerySet (manager.py) attach the .history accessor used to query, diff, and revert past state, while admin.py (375 lines) overrides the standard ModelAdmin change-history views to render diffs and drive reverts, and middleware.py exposes a thread/context-local so the acting user can be attached without threading it through every call site. Because the historical model and its signal wiring sit at the center of every tracked model’s save/delete path, changing that core descriptor is the one place that would ripple through every consumer and its migrations.

Tech Stack The library is a pure Django app with a single runtime dependency, django>=5.2, targeting Python 3.10 through 3.14; CI runs the full cross-product of supported Python and Django versions (including Django’s main branch) against Postgres, MySQL, and SQLite services. Packaging uses hatchling with hatch-vcs for git-tag-derived versioning, and documentation is built with Sphinx and published via Read the Docs. There is no frontend build step — admin diff views are rendered with plain Django templates and templatetags.

Code Quality The test suite spans admin, manager, middleware, model, signal, template, and command behavior across dedicated test modules, plus a separate registry-tests package and a custom-user fixture app, all exercised through runtests.py/tox across the full Python×Django×database matrix in CI. Pre-commit enforces bandit for security scanning, black for formatting, flake8 and isort for linting/import order, and codespell, with coverage tracked (branch coverage enabled) via Codecov. Errors are raised through a dedicated exceptions module rather than silently swallowed, and newer code uses TYPE_CHECKING imports and dataclasses for stronger typing.

What Makes It Unique Rather than logging changes into a generic JSON or key-value audit table, django-simple-history generates a real parallel Django model — with the same field types, indexes, and query interface as the tracked model — for every history-enabled model, so history rows stay natively queryable and admin-browsable instead of requiring a separate deserialization layer. Combined with register() for tracking models you don’t control and middleware-based user attribution, it lets teams retrofit a full audit trail onto an existing Django project with minimal code changes.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search