At some point in most Django projects, someone asks a question that the database cannot answer on its own:
“Who changed this record, and when?“
Maybe a price got updated, and nobody knows who did it. Maybe a user account was deactivated, and the reason is unclear. Maybe a critical configuration changed, and you need to trace it back.
It keeps what is there now. It does not keep what was there before, who changed it, or what he changed it to. This is what audit logging takes care of, and in Django, the django-auditlog package is one of the cleanest ways to add it.
What django-auditlog Does
django-auditlog is a third-party package that automatically tracks changes to your Django models. Every time a model instance is created, updated, or deleted, the package records a log entry capturing what changed, who made the change, when it happened, and what the previous and new values were.
It works by hooking into Django’s signals system, so you do not need to modify your views, serializers, or business logic to make it work. You register the models you want to track, and the rest happens automatically.
Installation and Basic Setup
Install the package with pip:
pip install django-auditlog
Add it to your INSTALLED_APPS:
INSTALLED_APPS = [
...
"auditlog",
]
Add the middleware to track the current user requesting resources:
MIDDLEWARE = [
...
"auditlog.middleware.AuditlogMiddleware",
]
Run migrations to create the audit log tables:
python manage.py migrate
Registering Models for Tracking
Once installed, you register the models you want to track using the @auditlog.register() decorator or the auditlog.register() function.
Using the decorator:
from django.db import models
from auditlog.registry import auditlog
from auditlog.models import AuditlogHistoryField
@auditlog.register()
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
is_active = models.BooleanField(default=True)
history = AuditlogHistoryField()
def __str__(self):
return self.name
From this point, every create, update, and delete on the Product model is automatically logged. The optional AuditlogHistoryField gives you a convenient accessor to query the log entries for a specific instance.
You can also register models in your AppConfig.ready() method if you prefer keeping registration out of the model file:
from auditlog.registry import auditlog
from myapp.models import Product
auditlog.register(Product)
What Gets Logged
For each change, django-auditlog creates a LogEntry record with the following information:
- object_id — The primary key of the changed object
- content_type — The model that was changed
- object_repr — A string representation of the object at the time of the log
- action — CREATE, UPDATE, or DELETE
- changes — A JSON field recording what changed, with the old and new values for each field
- actor — The user who made the change, captured from the request via middleware
- timestamp — When the change occurred
- remote_addr — The IP address of the request
The changes field is particularly useful. For an update, it records exactly which fields were modified and what they were changed from and to, rather than just noting that the record changed.
Accessing Log Entries
You can query the log for a specific instance using the history field:
product = Product.objects.get(id=1)
for entry in product.history.all():
print(entry.timestamp, entry.actor, entry.changes)
Or query the global log for all changes to a model:
from auditlog.models import LogEntry
from auditlog.models import LogEntry
from django.contrib.contenttypes.models import ContentType
ct = ContentType.objects.get_for_model(Product)
entries = (
LogEntry.objects
.filter(content_type=ct)
.order_by("-timestamp")
)
You can also filter by actor to see everything a specific user has done across any tracked model:
from django.contrib.auth import get_user_model
from auditlog.models import LogEntry
User = get_user_model()
user = User.objects.get(username="admin")
entries = (
LogEntry.objects
.filter(actor=user)
.order_by("-timestamp")
)
Selective Field Tracking
If you only want to track changes to specific fields rather than the whole model, you can configure this during registration:
auditlog.register(
Product,
include_fields=["price", "is_active"]
)
Or exclude specific fields:
auditlog.register(
Product,
exclude_fields=["updated_at"]
)
This is useful for large models where logging every field change would generate excessive noise. You can focus the audit log on the fields that actually matter for compliance or debugging needs.
Logging Programmatic Changes
The middleware captures the user from web requests automatically. But sometimes changes happen outside a request context — in management commands, background tasks, or scheduled jobs.
For these cases, you can manually set the actor:
from auditlog.context import set_actor
system_user = User.objects.get(username="system")
with set_actor(system_user ):
product.price = 99.99
product.save()
This ensures that even non-request-driven changes are attributed to the right actor rather than being logged with no user attached.
Admin Integration
django-auditlog integrates cleanly with the Django admin. By mixing in LogEntryAdminMixin on your ModelAdmin class, you can display the full change history for any model instance directly within the admin interface:
from django.contrib import admin
from auditlog.admin import LogEntryAdminMixin
from .models import Product
@admin.register(Product)
class ProductAdmin(LogEntryAdminMixin, admin.ModelAdmin):
pass
This gives admin users a readable history panel on each record showing who changed what and when, without needing to build a custom view.
When to Use It
django-auditlog is a good fit when you need to know who changed what for compliance, debugging, or accountability purposes.
Financial systems, HR applications, content management platforms, and any multi-user system where records have meaningful lifecycle changes all benefit from it.
It is not the right tool if you need full database-level change tracking, including raw SQL operations, or if you need to roll back changes. It records history, but it does not manage state transitions.
django-auditlog solves one of the more common gaps in Django applications — the inability to answer “who changed this and when” — with minimal setup and no changes to existing application logic.
Register your models, add the middleware, and every tracked change is automatically logged with the actor, timestamp, and a clear record of what was changed.
For any Django project where accountability and traceability matter, it is one of the more useful packages to have installed from the start.