Django Database Purge
django-db-purge deletes expired database records based on configurable retention policies, run as a scheduled Django management command. It also ships an MCP server so an AI agent can purge records too, safely, behind a preview, token, execute handshake.
Features:
- Flexible Retention Policy: Define your own retention policy to determine which records should be purged from the database.
- Efficient Data Management: Easily manage the size of your database by removing outdated or unnecessary records.
- Customizable: Adapt the command to suit your project's specific requirements and database structure.
- Safe: Built-in safeguards to prevent accidental data loss, ensuring that only the intended records are purged.
- Agent-safe MCP tools: Optionally let an AI agent purge records through a preview, token, execute handshake that blocks deletion without a matching prior preview.
- Row-set bound deletion: A preview binds the exact rows it matched. Execution deletes only those rows, by primary key, and rejects the call outright if the row set drifted in between, so a concurrent write can never widen a purge.
Scheduled purging (cron)
How to Use:
- Install django-db-purge by running:
pip install django-db-purge
-
Include 'dbpurge' in your INSTALLED_APPS settings.
-
Add a
DB_PURGE_RETENTION_POLICIESlist to your project'ssettings.py, based on your requirements. Below is a guide on how to set up the retention policies:1.
app_name- Description: Name of the Django app containing the model.
- Example:
my_django_app
2.
model_name- Description: Name of the Django model from which records will be deleted.
- Example:
MyModel
3.
time_based_column_name- Description: Name of the column in the model that contains the timestamp or datetime field used for determining the age of records.
- Example:
created_at
4.
data_retention_num_seconds- Description: Time duration in seconds for which records will be retained before deletion.
- Example:
2592000(for 30 days)
Example:
# settings.py DB_PURGE_RETENTION_POLICIES = [ { 'app_name': 'my_django_app', 'model_name': 'MyModel', 'time_based_column_name': 'created_at', 'data_retention_num_seconds': 2592000, # 30 days in seconds }, # Add more retention policies as needed ]
If
DB_PURGE_RETENTION_POLICIESis not set, the command falls back to a placeholder policy that will fail validation until you configure it, so there's no risk of silently deleting the wrong table. -
Then, either periodically call the db_purge management command (e.g., via a system cronjob), or install and configure django-cron.
MCP server
django-db-purge also ships an MCP server that exposes the same purge logic to an AI agent, with guardrails so an agent can never delete rows without a human-verifiable preview step. It runs inside Django as a management command and speaks the MCP protocol over stdio, so it works with Claude Desktop, the MCP Inspector, or any other MCP-capable host. The server makes no LLM API calls of its own: the host runs the model, and this process only exposes deterministic, schema-validated tools.
Installation
The MCP server needs fastmcp, which is an optional extra, not part of the base install:
pip install "django-db-purge[mcp]"
If fastmcp is not installed, running purge_mcp_server fails immediately with a CommandError pointing back at this install command, rather than a raw import traceback.
Tools
-
list_purge_candidates()Read-only. Introspects installed models and returns every app, model, andDateField/DateTimeFieldcolumn, so an agent can discover valid inputs for the other two tools. -
preview_purge(app_name, model_name, time_column, retention_seconds)Read-only. Validates the policy against live schema, counts matching rows, and returns up to 5 sample rows (primary key and the time column only, never the full row), aconfirmation_token, andtoken_expires_at. Performs no deletion. -
execute_purge(app_name, model_name, time_column, retention_seconds, confirmation_token)Deletes the rows apreview_purgecall matched, but only with that call'sconfirmation_token. Any unknown token, expired token, or parameter mismatch fails with the same "invalid or expired confirmation token" error, so a caller can't distinguish which of those it was. If the matched row set has changed since the preview, the call is rejected outright.
Safety handshake
There is no path to deletion without a prior, matching preview:
- Call
preview_purgeto see what would be deleted and get back aconfirmation_token. - Call
execute_purgewith that token and the identical parameters, before it expires (5 minutes).
Tokens are single-use: a successful execute_purge consumes the token, so it cannot be replayed. Tokens are also bound to the exact parameter tuple that produced them, so changing any argument invalidates the token even if it hasn't expired. If execute_purge fails for a reason that isn't the caller's fault, such as a row-cap breach or a row-set change below, the token is reinstated so a retry with the same token can still succeed.
A token binds the exact set of rows the preview matched, not just the parameters. execute_purge deletes only those rows, by primary key, so a row that starts matching the retention filter after the preview is never deleted. Before deleting, it re-runs the filter and compares a fingerprint of the matched rows against the preview's; if the row set has changed, the call is rejected with a distinct error naming both row counts, and you need a fresh preview_purge. The fingerprint covers primary keys only by default, so a row that is modified but still matches is still deleted. Set DB_PURGE_MCP_FINGERPRINT_FIELDS to include specific column values in it and reject in-place modifications too. See DESIGN.md for the full rationale, including the cascade limitation.
Settings
-
DB_PURGE_MCP_ALLOWED_MODELSList of models that may be purged via MCP, in"app_label.ModelName"format (e.g.["tests.SampleRecord"]). Matched case-insensitively, so"tests.samplerecord"also works. Defaults to an empty list, meaning nothing is purgeable until you configure it. Enforced on bothpreview_purgeandexecute_purge. -
DB_PURGE_MCP_MAX_ROWSMaximum number of matching rows anexecute_purgecall may delete, re-checked at execute time even if the preview was under the cap. Defaults to10000. This bounds the rows matched bytime_column, not cascade fan-out:ON DELETE CASCADErelations can remove additional related rows beyond this cap. It also bounds the size of the row-set fingerprint a preview stores. -
DB_PURGE_MCP_FINGERPRINT_FIELDSOptional, per model, opt-in. Maps a model label to the extra columns to include in that model's row-set fingerprint, e.g.{"tests.SampleRecord": ["label"]}. Model labels are matched case-insensitively, likeDB_PURGE_MCP_ALLOWED_MODELS. Defaults to an empty dict, meaning fingerprints cover primary keys only, so rows modified in place between preview and execute are still deleted as long as they still match the filter. Name columns here to haveexecute_purgereject those modifications as well. There is no assumption that a model has anupdated_ator version column: configure whichever columns matter to you.
Running the server
Inside your project's virtualenv, with dbpurge in INSTALLED_APPS:
python manage.py purge_mcp_server
This boots Django (settings, ORM, app registry) and then serves the three tools above over stdio.
Claude Desktop configuration
Add an entry to Claude Desktop's claude_desktop_config.json, pointing at your project's virtualenv Python and manage.py:
{
"mcpServers": {
"django-db-purge": {
"command": "/path/to/your/project/.venv/bin/python",
"args": ["/path/to/your/project/manage.py", "purge_mcp_server"],
"cwd": "/path/to/your/project"
}
}
}
Use the venv's Python directly (not a bare python), since fastmcp and your project's dependencies need to be importable.
Inspecting the server
To poke at the tools interactively, run the server through the MCP Inspector:
npx @modelcontextprotocol/inspector python manage.py purge_mcp_server
Run this from inside your project's virtualenv (activated, or with that venv's python on PATH), so the python the Inspector spawns is the one with Django and fastmcp installed.
A healthy tools/list response shows all three tools: list_purge_candidates, preview_purge, and execute_purge, each with a JSON schema derived from its type hints.
Running the tests
From a bare checkout, with this package installed with the mcp extra (pip install -e ".[mcp]"), no separate install step for Django or fastmcp is required:
python runtests.py
This runs the full Django test suite, including token lifecycle, allowlist, and row-cap coverage, against an in-memory sqlite database.
To exercise the server the way a real MCP client would, over an actual stdio subprocess:
python tests/e2e_stdio.py
This seeds a temporary sqlite database, spawns python -m django purge_mcp_server, and drives a full preview, execute, and reuse-rejected round trip over JSON-RPC.
Contributions:
Contributions are welcome! If you encounter any issues or have suggestions for improvements, please submit an issue or pull request on GitHub.
License:
This project is licensed under the MIT License.
Metadata
Release files for django-db-purge 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_db_purge-1.3.0.tar.gz | 24.2 kB | With provenance | Details for django_db_purge-1.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_db_purge-1.3.0-py3-none-any.whl | Python 3 | none | any | Details for django_db_purge-1.3.0-py3-none-any.whl |
Total release size: 39.6 kB
Release files / django_db_purge-1.3.0.tar.gz
| Download URL | django_db_purge-1.3.0.tar.gz |
|---|---|
| Size | 24.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8322037fb9ef5c3f6362544e8697db1a9891bdaec80757dbd7a9602906b2d20a
|
|
BLAKE2b-256 checksum How to use checksums |
84d4fb2a008a43fc00d3b11d92ead876f61dbb10875c76428c21098dbff6436a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency logRelease files / django_db_purge-1.3.0-py3-none-any.whl
| Download URL | django_db_purge-1.3.0-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d25ee4b535b631dbe9f0d51843384d03213bf99bba9f144023944a7f6497b676
|
|
BLAKE2b-256 checksum How to use checksums |
b07df7228d173cf9a5b87e4681b985a5fdf7c994142bbf6577391bb76cf9f956
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency logSecurity for django-db-purge 1.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for django_db_purge-1.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.