Automatic PostgreSQL trigger-based audit logging for Django — tracks every INSERT, UPDATE and DELETE plus API requests, with zero model changes required.
Project description
django-pg-audit
Automatic PostgreSQL trigger-based audit logging for Django.
Track every INSERT, UPDATE, and DELETE across your entire database — plus every API request — with zero changes to your existing models.
How it works
HTTP Request
│
▼
APIAuditMiddleware ← logs request + response to pg_api_audit_log
│
▼
AuditSessionMiddleware ← writes user/IP/path into PostgreSQL session vars
│
▼
Your Django View
│
▼
PostgreSQL Trigger ← fires on every INSERT/UPDATE/DELETE
│ reads the session vars set above
▼
pg_audit_log ← one row per data change, with full context
Because the trigger runs inside PostgreSQL, it also catches writes made
directly via psql, migration scripts, or any other tool — not just Django ORM.
Requirements
| Requirement | Version |
|---|---|
| Python | ≥ 3.9 |
| Django | ≥ 4.0 |
| PostgreSQL | ≥ 13 |
| psycopg2 or psycopg3 | any recent |
Note: This package requires PostgreSQL. It will not work with SQLite or MySQL.
Installation
pip install django-pg-audit
Quick Start
1. Add to INSTALLED_APPS
# settings.py
INSTALLED_APPS = [
...
"django_pg_audit",
]
2. Add middleware
Place both middleware classes after Django's AuthenticationMiddleware
so that request.user is already populated.
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", # ← must come first
# ── django-pg-audit ──────────────────────────────────────────────────────
"django_pg_audit.middleware.APIAuditMiddleware", # log API requests
"django_pg_audit.middleware.AuditSessionMiddleware", # inject PG session vars
# ─────────────────────────────────────────────────────────────────────────
"django.contrib.messages.middleware.MessageMiddleware",
]
3. Run migrations
python manage.py migrate django_pg_audit
This creates the two log tables and attaches audit triggers to every existing
table in the public schema (excluding Django/auth system tables).
4. Attach triggers to tables added after initial migration
python manage.py attach_audit_triggers
Configuration
Add a DJANGO_PG_AUDIT dict to your settings.py. All keys are optional —
the defaults are shown below.
DJANGO_PG_AUDIT = {
# Paths that APIAuditMiddleware will NOT log
"EXCLUDED_PATHS": ["/admin", "/static", "/media"],
# Additional table names (exact match) to exclude from DB triggers
"EXCLUDED_TABLES": [],
# Field names whose values are replaced with "********"
"SENSITIVE_FIELDS": [
"password", "token", "access", "refresh",
"authorization", "secret", "api_key", "apikey",
"credit_card", "cvv", "ssn",
],
# Requests/responses larger than this (bytes) are stored as {"message": "too large"}
"MAX_PAYLOAD_SIZE": 10000,
# Set True to also log GET requests in APIAuditMiddleware
"LOG_GET_REQUESTS": False,
# Capture direct PostgreSQL user + IP when no app session variables exist
"TRACK_DB_USERS": True,
}
Models
AuditLog — table pg_audit_log
| Field | Type | Description |
|---|---|---|
table_name |
CharField | Name of the table that was modified |
operation |
CharField | INSERT, UPDATE, or DELETE |
old_data |
JSONField | Row state before change (null for INSERT) |
new_data |
JSONField | Row state after change (null for DELETE) |
record_id |
CharField | Primary key of the affected row |
ip_address |
IPField | Client IP address |
updated_at |
DateTimeField | Timestamp of the change |
updated_by |
FK → User | Authenticated Django user (null if direct DB) |
is_db_user |
BooleanField | True if the write came directly from psql |
db_user |
CharField | PostgreSQL role name (when is_db_user=True) |
api_name |
CharField | API path that triggered the change |
http_method |
CharField | HTTP method of the triggering request |
request_id |
UUIDField | Links to APIAuditLog.request_id |
APIAuditLog — table pg_api_audit_log
| Field | Type | Description |
|---|---|---|
request_id |
UUIDField | Unique ID for the request |
api_name |
CharField | Request path |
http_method |
CharField | HTTP verb |
request_payload |
JSONField | Masked request body |
response_body |
JSONField | Masked response body |
response_code |
IntegerField | HTTP status code |
user |
FK → User | Authenticated user (null if anon) |
ip_address |
IPField | Client IP |
created_at |
DateTimeField | Request timestamp |
duration_ms |
FloatField | Request duration in milliseconds |
Management Commands
attach_audit_triggers
Re-attach (or remove) triggers on all tables. Run after adding new tables.
# Attach triggers to all eligible tables
python manage.py attach_audit_triggers
# Preview what would happen without making changes
python manage.py attach_audit_triggers --dry-run
# Exclude specific tables
python manage.py attach_audit_triggers --exclude orders_archive logs_raw
# Remove all audit triggers
python manage.py attach_audit_triggers --remove
Querying the Audit Log
from django_pg_audit.models import AuditLog, APIAuditLog
# All changes to the orders table
AuditLog.objects.filter(table_name="myapp_order")
# Who deleted a specific record?
AuditLog.objects.filter(
table_name="myapp_order",
record_id="123",
operation="DELETE",
)
# All changes made by a specific user
AuditLog.objects.filter(updated_by=request.user)
# All changes from a single API request
AuditLog.objects.filter(request_id=some_uuid)
# The API request that caused those changes
APIAuditLog.objects.get(request_id=some_uuid)
# Slow API calls (> 500 ms)
APIAuditLog.objects.filter(duration_ms__gt=500)
# Failed requests
APIAuditLog.objects.filter(response_code__gte=400)
# Direct database writes (bypassed the API)
AuditLog.objects.filter(is_db_user=True)
Middleware Order Explanation
APIAuditMiddleware ← must run FIRST so it captures the full response
AuditSessionMiddleware ← runs second, sets PG session variables used by the trigger
Both must come after AuthenticationMiddleware so request.user is set.
Authentication backend compatibility
AuditSessionMiddleware reads request.user.pk after all auth middleware has
run. It works with:
- Django session authentication (default)
- Django REST Framework
TokenAuthentication djangorestframework-simplejwt(if you useJWTAuthenticationin DRF)django-allauthdjango-oauth-toolkit- Any custom backend that sets
request.user
No JWT-specific code is present in this package. If your auth middleware sets
request.user, the audit context will be captured correctly.
Resetting a Broken Migration State
# Roll back all django_pg_audit migrations (drops tables + triggers)
python manage.py migrate django_pg_audit zero
# Re-apply from scratch
python manage.py migrate django_pg_audit
Docker / Production Setup
After building your container and running migrations, triggers are already attached. For zero-downtime deployments where new tables are added:
docker compose exec web python manage.py migrate
docker compose exec web python manage.py attach_audit_triggers
Contributing
git clone https://github.com/GangulyYadav/django-pg-audit
cd django-pg-audit
pip install -e ".[dev]"
pytest
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_pg_audit-1.0.1.tar.gz.
File metadata
- Download URL: django_pg_audit-1.0.1.tar.gz
- Upload date:
- Size: 21.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.0rc2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c2d3b29041c36d83481db4c2616f010aa114d1e2e9907192eff085195395c1c
|
|
| MD5 |
3e48c8a22830d1aac5bda5804f956778
|
|
| BLAKE2b-256 |
86ffc1c8a80a4e763e5a10b27c75243f6851d5f84ba628783f8aba1dc317b743
|
File details
Details for the file django_pg_audit-1.0.1-py3-none-any.whl.
File metadata
- Download URL: django_pg_audit-1.0.1-py3-none-any.whl
- Upload date:
- Size: 19.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.0rc2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2effe792ee93060969635c3e5928d5de36cd234f7cec59126016580ce81a27f
|
|
| MD5 |
7300b10da661c2e92b05d97a67bcb2ab
|
|
| BLAKE2b-256 |
a0ba2403432be53ac04a60eac041617a11824bfd479090dcac93df05767d3a8b
|