Skip to main content

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 use JWTAuthentication in DRF)
  • django-allauth
  • django-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


Download files

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

Source Distribution

django_pg_audit-1.0.1.tar.gz (21.4 kB view details)

Uploaded Source

Built Distribution

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

django_pg_audit-1.0.1-py3-none-any.whl (19.4 kB view details)

Uploaded Python 3

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

Hashes for django_pg_audit-1.0.1.tar.gz
Algorithm Hash digest
SHA256 4c2d3b29041c36d83481db4c2616f010aa114d1e2e9907192eff085195395c1c
MD5 3e48c8a22830d1aac5bda5804f956778
BLAKE2b-256 86ffc1c8a80a4e763e5a10b27c75243f6851d5f84ba628783f8aba1dc317b743

See more details on using hashes here.

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

Hashes for django_pg_audit-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c2effe792ee93060969635c3e5928d5de36cd234f7cec59126016580ce81a27f
MD5 7300b10da661c2e92b05d97a67bcb2ab
BLAKE2b-256 a0ba2403432be53ac04a60eac041617a11824bfd479090dcac93df05767d3a8b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page