Why Audit Logging Matters
In Django applications, tracking model changes like create, update, and delete is essential for:
- Debugging regressions and user issues
- Security auditing for unauthorized changes
- Compliance with data retention and traceability policies
- Rollback support for destructive actions
Django doesn’t track this out of the box for most operations. Let’s break down the most popular and effective libraries that handle audit trails in PostgreSQL-backed Django apps.
Django Admin History (Built-in)
Django provides a basic history tracking tool for the admin panel.
What It Logs
- Who made the change (admin user)
- When it happened
- What model was affected (add/change/delete)
Limitations
- Only tracks admin actions (not API or script changes)
- No field-level changes
- No rollback or diffing support
When to Use
- Lightweight internal tools
- Manual admin panel changes
- No external dependencies required
django-simple-history
Stores a complete snapshot of your model every time it changes. Simply add HistoricalRecords()
to your model.
Pros
- Easy integration
- Field-level change tracking
- History UI in Django Admin
- Tracks users with middleware
- Supports rollback/compare
Cons
- Can lead to large DB growth
- Doesn’t log file contents, only paths
- Bulk operations not tracked by default
from simple_history.models import HistoricalRecords
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
history = HistoricalRecords()
Status
Maintained by Jazzband. Latest: 3.9.0
(2025)
django-reversion
Think of this as Git for models. It stores full model states as versions and lets you roll back to any previous one.
Pros
- True version control
- Rollback & recovery support
- Supports deletions
- Admin integration
Cons
- Pickled data, not easily readable
- Doesn’t track bulk updates
- No file diffing
import reversion
@reversion.register()
class Page(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
Status
Stable and mature. Latest: 5.1.0
(2024)
django-auditlog
Tracks changes as JSON diffs, offering a middle-ground between full history and minimal logging.
Pros
- Minimal setup
- Field-level changes in JSON
- Tracks user and timestamp
- Lightweight
Cons
- No rollback or revert support
- Doesn’t log deletes in detail
-
Doesn’t handle bulk operations well
from auditlog.registry import auditlog
class Invoice(models.Model):
amount = models.DecimalField(max_digits=10, decimal_places=2)
auditlog.register(Invoice)
Maintained by Jazzband. Latest: 3.0.0
(2024)
django-pghistory
A PostgreSQL-first audit tool using database-level triggers. It logs every INSERT, UPDATE, DELETE—even from raw SQL and bulk updates.
Pros
- Handles raw SQL and bulk changes
- Efficient (runs in the DB)
- Auto-generated event tables
- Doesn’t rely on Django signals
Cons
- PostgreSQL only
- Complex to debug
- Requires DB triggers and schema migrations
import pghistory
@pghistory.track()
class Customer(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
Actively maintained. Latest: 3.6.0
(2025)
model-utils — FieldTracker
This utility tracks field-level changes in memory, not in the database.
Pros
- Lightweight
- Detects changes before saving
- No DB writes
- Great for triggering side-effects
Cons
- No persistence
- Doesn’t track deletes
- Per-instance only
from model_utils import FieldTracker
class Task(models.Model):
status = models.CharField(max_length=20)
tracker = FieldTracker()
Jazzband project. Latest: 5.0.0
(2024)
Middleware-Based Logging
Middleware can log request-level metadata like URL, IP, method, and authenticated user.
Pros
- Easy to implement
- Captures access logs
- Great for security or monitoring
Cons
- Doesn’t track model changes
- Not structured for diffing
- No rollback or undo
Custom Signal Handlers
For full control, build your own audit logging using post_save
, post_delete
, and context-aware user tracking.
Pros
- Customizable structure
- Tailored for exact needs
- No external dependencies
Cons
- Manual setup
- Easy to forget signal bindings
- Doesn’t capture bulk updates
Comparison Table
Feature | Admin History | Simple History | Reversion | Auditlog | PGHistory | FieldTracker |
Field-level Changes | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
Rollback Support | ❌ | Partial | ✅ | ❌ | ❌ | ❌ |
Track Deletes | ✅ | ✅ | ✅ | Partial | ✅ | ❌ |
Track Bulk Updates | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
Track Bulk Updates | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
Lightweight | ✅ | ⚠️ Medium | ⚠️ Medium | ✅ | ⚠️ DB-level | ✅ |
What to take
Choose the right tool based on your project needs:
- For rollback/version control → django-reversion
- For field-level auditing → django-simple-history or django-auditlog
- For PostgreSQL-native, full-trace → django-pghistory
- For lightweight or signal-driven logic → FieldTracker or custom signals
Make sure to always log essential context: who, what, when, and why. This helps during debugging, incident response, and compliance checks.