Metadata-Version: 2.4
Name: django-migration-auditor
Version: 0.1.0
Summary: Audit every Django migration with diffs, timestamps, executor identity, and auto-generated rollback scripts.
Home-page: https://github.com/abburisaikarthik/django-migration-auditor
Author: Saikarthik Abburi
License: MIT
Project-URL: Homepage, https://github.com/abburisaikarthik/django-migration-auditor
Project-URL: Documentation, https://github.com/abburisaikarthik/django-migration-auditor#readme
Project-URL: Repository, https://github.com/abburisaikarthik/django-migration-auditor
Project-URL: Issues, https://github.com/abburisaikarthik/django-migration-auditor/issues
Project-URL: Changelog, https://github.com/abburisaikarthik/django-migration-auditor/blob/main/CHANGELOG.md
Keywords: django,migrations,audit,rollback,schema,devops
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 3.2
Classifier: Framework :: Django :: 4.0
Classifier: Framework :: Django :: 4.1
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
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.8
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Logging
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django>=3.2
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-django>=4; extra == "dev"
Requires-Dist: flake8; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: isort; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# django-migration-auditor

[![CI](https://github.com/abburisaikarthik/django-migration-auditor/actions/workflows/ci.yml/badge.svg)](https://github.com/abburisaikarthik/django-migration-auditor/actions)
[![PyPI](https://img.shields.io/pypi/v/django-migration-auditor)](https://pypi.org/project/django-migration-auditor/)
[![Python](https://img.shields.io/pypi/pyversions/django-migration-auditor)](https://pypi.org/project/django-migration-auditor/)
[![Django](https://img.shields.io/badge/django-3.2%20%7C%204.x%20%7C%205.x-green)](https://www.djangoproject.com/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

**A Django package that logs every migration run with schema diffs, timestamps, executor identity, and generates rollback scripts automatically.**

---

## ✨ Features

| Feature                   | Description                                                                                                                                                   |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Full Audit Trail**      | Records every `apply` / `unapply` with app, migration name, direction, who ran it, environment, hostname, database alias, start/end timestamps, and duration. |
| **Schema Diffs**          | Captures before/after schema snapshots via `connection.introspection` and stores a human-readable diff (tables, columns, indexes, constraints).               |
| **Auto Rollback Scripts** | Generates `.sql` rollback scripts for each applied migration using `SchemaEditor(collect_sql=True)` — no subprocess, no side effects.                         |
| **Management Commands**   | `audit_migrations` (list/filter/export), `generate_rollback` (on-demand SQL), `migration_report` (summary stats).                                             |
| **Django Admin**          | Read-only admin panel with colour-coded badges, collapsible schema diffs, date hierarchy, and full-text search.                                               |
| **Zero Config**           | Works out of the box — just add to `INSTALLED_APPS` and run `migrate`.                                                                                        |
| **Fail-Safe**             | Audit failures never break real migrations. All recording is wrapped in try/except.                                                                           |

---

## 📦 Installation

```bash
pip install django-migration-auditor
```

Or for development:

```bash
git clone https://github.com/abburisaikarthik/django-migration-auditor.git
cd django-migration-auditor
pip install -e ".[dev]"
```

---

## 🚀 Quick Start

### 1. Add to `INSTALLED_APPS`

```python
# settings.py
INSTALLED_APPS = [
    # ...
    "migration_auditor",
]
```

### 2. Run migrations

```bash
python manage.py migrate
```

This creates the `migration_auditor_log` table **and** starts auditing immediately.

### 3. View audit logs

```bash
# Table format (default)
python manage.py audit_migrations

# JSON
python manage.py audit_migrations --format json

# CSV
python manage.py audit_migrations --format csv

# Filter by app and status
python manage.py audit_migrations --app myapp --status failed --since 2024-01-01
```

### 4. View summary report

```bash
python manage.py migration_report
python manage.py migration_report --app myapp
```

### 5. Generate rollback scripts

```bash
# Save to file
python manage.py generate_rollback --app myapp --name 0002_add_field

# Print to stdout
python manage.py generate_rollback --app myapp --name 0002_add_field --stdout
```

### 6. Django Admin

Navigate to `/admin/migration_auditor/migrationauditlog/` for a read-only view of all audit entries with:

- Colour-coded direction badges (▲ Apply / ▼ Unapply)
- Status badges (Success / Failed / In Progress)
- Collapsible schema diff and rollback SQL sections
- Date hierarchy, filters, and search

---

## ⚙️ Configuration

All settings are optional. Configure via the `MIGRATION_AUDITOR` dict in `settings.py`:

```python
MIGRATION_AUDITOR = {
    # Master switch — set to False to disable all auditing.
    "ENABLED": True,

    # Directory for .sql rollback files (relative to BASE_DIR or absolute).
    "ROLLBACK_DIR": "rollbacks",

    # Capture before/after schema snapshots.
    "CAPTURE_SCHEMA_DIFF": True,

    # Optional callable: (request) -> str to resolve the current user.
    # Only useful with the optional middleware.
    "USER_RESOLVER": None,

    # Override automatic environment detection.
    # When None, checks DJANGO_ENV → ENVIRONMENT → DJANGO_SETTINGS_MODULE.
    "ENVIRONMENT": None,

    # App labels to skip (never audit these).
    "EXCLUDE_APPS": [],

    # Max chars for schema diff storage.  0 = unlimited.
    "MAX_DIFF_LENGTH": 0,
}
```

### Environment Detection

The package auto-detects the environment by checking (in order):

1. `MIGRATION_AUDITOR["ENVIRONMENT"]` setting
2. `DJANGO_ENV` environment variable
3. `ENVIRONMENT` environment variable
4. Keywords in `DJANGO_SETTINGS_MODULE` (prod, staging, dev, test, local)
5. Falls back to `"unknown"`

### User Detection

The executor identity is resolved by checking (in order):

1. Custom `USER_RESOLVER` callable (if middleware is installed)
2. `MIGRATION_AUDITOR_USER` environment variable
3. `USER` / `USERNAME` / `LOGNAME` environment variables
4. `getpass.getuser()` (OS-level username)

---

## 🔌 Optional Middleware

If you need to identify the _Django user_ who triggered a migration (e.g. from a custom admin view), add the middleware:

```python
MIDDLEWARE = [
    # ...
    "migration_auditor.middleware.MigrationAuditorMiddleware",
]
```

Then configure a `USER_RESOLVER`:

```python
MIGRATION_AUDITOR = {
    "USER_RESOLVER": lambda request: str(request.user) if request.user.is_authenticated else "anonymous",
}
```

> **Note:** This is only useful in rare setups where migrations run inside a request cycle. For normal `manage.py migrate` usage, the middleware is not needed.

---

## 📊 Audit Record Fields

| Field                  | Type          | Description                           |
| ---------------------- | ------------- | ------------------------------------- |
| `app_label`            | CharField     | Django app label                      |
| `migration_name`       | CharField     | Migration file name                   |
| `direction`            | CharField     | `apply` or `unapply`                  |
| `applied_by`           | CharField     | Who ran the migration                 |
| `environment`          | CharField     | Runtime environment                   |
| `hostname`             | CharField     | Machine hostname                      |
| `database_alias`       | CharField     | Django DB alias                       |
| `started_at`           | DateTimeField | When it began                         |
| `finished_at`          | DateTimeField | When it finished                      |
| `duration_ms`          | IntegerField  | Wall-clock time (ms)                  |
| `status`               | CharField     | `success`, `failed`, or `in_progress` |
| `error_detail`         | TextField     | Full traceback on failure             |
| `schema_diff`          | TextField     | Human-readable schema diff            |
| `rollback_sql`         | TextField     | SQL to reverse the migration          |
| `rollback_script_path` | CharField     | Path to saved `.sql` file             |

---

## 🧪 Running Tests

```bash
pip install -e ".[dev]"
pytest tests/ -v
```

---

## 📋 How It Works

1. **Signal Interception**: On `pre_migrate`, the package monkey-patches `MigrationExecutor` with `AuditingMigrationExecutor`.
2. **Schema Snapshot**: Before each migration, a snapshot of the database schema is taken via `connection.introspection`.
3. **Migration Execution**: The original `apply_migration()` / `unapply_migration()` runs normally.
4. **Post-Execution**:
   - A second schema snapshot is taken and diffed against the first.
   - Rollback SQL is generated via `SchemaEditor(collect_sql=True)` — dry-run, nothing is executed.
   - The rollback SQL is saved to a `.sql` file.
   - A `MigrationAuditLog` row is written with all metadata.
5. **Fail-Safe**: All audit operations are wrapped in try/except. If anything goes wrong with recording, the real migration still completes.

---

## 🤝 Contributing

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Run tests (`pytest tests/ -v`)
4. Commit changes (`git commit -m 'Add amazing feature'`)
5. Push to branch (`git push origin feature/amazing-feature`)
6. Open a Pull Request

---

## 📄 License

This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
