Metadata-Version: 2.4
Name: django-pg-audit
Version: 1.0.1
Summary: Automatic PostgreSQL trigger-based audit logging for Django — tracks every INSERT, UPDATE and DELETE plus API requests, with zero model changes required.
Author-email: "Ganguly Yadav, Harshal Patil, Prithviraj Jadhav" <yadavganguly77@gmail.com>
License: MIT License
        
        Copyright (c) 2025 Ganguly Yadav, Harshal Patil, Prithviraj Jadhav
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/GangulyYadav/django-pg-audit
Project-URL: Documentation, https://github.com/GangulyYadav/django-pg-audit#readme
Project-URL: Repository, https://github.com/GangulyYadav/django-pg-audit
Project-URL: Bug Tracker, https://github.com/GangulyYadav/django-pg-audit/issues
Keywords: django,postgresql,audit,logging,triggers,middleware
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.0
Classifier: Framework :: Django :: 4.1
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Logging
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django>=4.0
Provides-Extra: psycopg2
Requires-Dist: psycopg2-binary>=2.9; extra == "psycopg2"
Provides-Extra: psycopg3
Requires-Dist: psycopg[binary]>=3.1; extra == "psycopg3"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-django>=4.5; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: isort; extra == "dev"
Requires-Dist: flake8; extra == "dev"
Dynamic: license-file

# 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

```bash
pip install django-pg-audit
```

---

## Quick Start

### 1. Add to INSTALLED_APPS

```python
# 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.

```python
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

```bash
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

```bash
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.

```python
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.

```bash
# 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

```python
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

```bash
# 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:

```bash
docker compose exec web python manage.py migrate
docker compose exec web python manage.py attach_audit_triggers
```

---

## Contributing

```bash
git clone https://github.com/GangulyYadav/django-pg-audit
cd django-pg-audit
pip install -e ".[dev]"
pytest
```

---

## License

MIT — see [LICENSE](LICENSE).
