django-delete-preview
Preview cascade deletes before execution and enforce safe deletion with explicit confirmation.
Features
- Inspect the full cascade delete tree for model instances and QuerySets
- Get a structured preview (JSON or human-readable text) before deleting
- Block accidental deletes on instances:
instance.delete()raises unless you passconfirm=True - For QuerySets, use
delete_with_preview(confirm=True)— the standard.delete()on QuerySets is not overridden - Django management command for inspecting from the command line
- Works with any Django model — no schema changes required
- Python 3.10+, Django 4.2+, fully typed
Installation
pip install django-delete-preview
Add to INSTALLED_APPS:
INSTALLED_APPS = [
...
"django_delete_preview",
]
Quickstart
from django_delete_preview.mixins import DeletePreviewMixin
from django_delete_preview.managers import DeletePreviewManager
from django.db import models
class Author(DeletePreviewMixin, models.Model):
name = models.CharField(max_length=200)
objects = DeletePreviewManager()
class Book(models.Model):
title = models.CharField(max_length=300)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
Usage
Instance preview
author = Author.objects.get(pk=1)
summary = author.preview_delete()
# {
# "database": "default",
# "total_objects": 3,
# "models": {"myapp.Author": 1, "myapp.Book": 2},
# "items": {"myapp.Author": ["Jane Austen"], "myapp.Book": ["Oliver Twist", "Great Expectations"]}
# }
Safe instance delete
# Without confirmation → raises ValueError
author.delete()
# ValueError: Delete blocked. Run preview_delete() first and confirm the operation.
# With confirmation → executes
author.delete(confirm=True)
QuerySet preview
summary = Author.objects.filter(active=False).preview_delete()
QuerySet safe delete
Use delete_with_preview() — the standard Django .delete() is not overridden by this library,
so Django admin bulk actions and other internal framework usage continue to work normally.
# Without confirmation → raises ValueError
Author.objects.filter(active=False).delete_with_preview()
# With confirmation → executes
Author.objects.filter(active=False).delete_with_preview(confirm=True)
# Standard .delete() still works normally (no protection)
Author.objects.filter(active=False).delete()
Management command
# Preview (text output)
python manage.py preview_delete myapp.Author 1
# Preview (JSON output)
python manage.py preview_delete myapp.Author 1 --format json
# Preview + execute
python manage.py preview_delete myapp.Author 1 --execute
JSON output format
{
"database": "default",
"total_objects": 10,
"models": {
"myapp.Author": 1,
"myapp.Order": 9
}
}
Configuration
# settings.py — maximum number of item string representations per model (default: 100)
DELETE_PREVIEW_MAX_ITEMS = 50
Limitations
- Does not support soft delete — objects are permanently deleted
- Does not preview
pre_delete/post_deletesignal side effects fast_deletes(objects deleted without loading into memory) are counted but may include approximatestr()representations
Example app
The repository includes a fully runnable Django project under example/ that
demonstrates every feature of the library using a bookstore domain
(Author, Book, Order, Publisher).
Setup
git clone https://github.com/adrianomargarin/django-delete-preview
cd django-delete-preview
pip install -e ".[dev]"
cd example
python manage.py migrate
python manage.py loaddata initial_data # loads 3 authors, 9 books, 5 orders, 3 publishers
Instance preview
python manage.py shell
from bookstore.models import Author
author = Author.objects.get(pk=1) # Jane Austen
author.preview_delete()
# {
# "database": "default",
# "total_objects": 6,
# "models": {"bookstore.Author": 1, "bookstore.Book": 3, "bookstore.Order": 2},
# "items": {
# "bookstore.Author": ["Jane Austen"],
# "bookstore.Book": ["Emma", "Persuasion", "Pride and Prejudice"],
# "bookstore.Order": ["ORD-001", "ORD-002"]
# }
# }
Safe instance delete
author.delete()
# ValueError: Delete blocked. Run preview_delete() first and confirm the operation.
author.delete(confirm=True)
# (6, {"bookstore.Book": 3, "bookstore.Order": 2, "bookstore.Author": 1})
QuerySet preview
from bookstore.models import Author
Author.objects.all().preview_delete()
# {
# "database": "default",
# "total_objects": 20,
# "models": {"bookstore.Author": 3, "bookstore.Book": 9, "bookstore.Order": 5},
# ...
# }
QuerySet safe delete
Author.objects.all().delete_with_preview()
# ValueError: Delete blocked. Run preview_delete() first and confirm the operation.
Author.objects.all().delete_with_preview(confirm=True)
# (20, {"bookstore.Book": 9, "bookstore.Order": 5, "bookstore.Author": 3})
Management command
# Human-readable preview
python manage.py preview_delete bookstore.Author 1
# Database : default
# Total items : 6
#
# Models affected:
# bookstore.Author: 1
# bookstore.Book: 3
# bookstore.Order: 2
#
# Items:
# bookstore.Author:
# - Jane Austen
# bookstore.Book:
# - Emma
# - Persuasion
# - Pride and Prejudice
# bookstore.Order:
# - ORD-001
# - ORD-002
# JSON preview
python manage.py preview_delete bookstore.Author 1 --format json
# Preview and execute
python manage.py preview_delete bookstore.Author 1 --execute
Publisher (no cascade)
Publisher has no related models, so deleting one affects only itself:
from bookstore.models import Publisher
pub = Publisher.objects.get(pk=1)
pub.preview_delete()
# {"database": "default", "total_objects": 1, "models": {"bookstore.Publisher": 1}, ...}
pub.delete(confirm=True)
Development setup
git clone https://github.com/adrianomargarin/django-delete-preview
cd django-delete-preview
pip install -e ".[dev]"
pre-commit install
# Run tests
pytest
# Lint
ruff check src tests
# Type check
mypy src/django_delete_preview
Metadata
Release files for django-delete-preview 0.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_delete_preview-0.0.3.tar.gz | 18.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_delete_preview-0.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.8 kB
Release files / django_delete_preview-0.0.3.tar.gz
| Download URL | django_delete_preview-0.0.3.tar.gz |
|---|---|
| Size | 18.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9006c6ddab1796b73cf91c0eca2b5ded86dd760328307c728426b861515e7266
|
|
BLAKE2b-256 checksum How to use checksums |
6309335016f4f2168c0d1d98d57c6181d1fa42730f6dd0487bd6ce786eaf1e80
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Apr 1, 2026.
Transparency logRelease files / django_delete_preview-0.0.3-py3-none-any.whl
| Download URL | django_delete_preview-0.0.3-py3-none-any.whl |
|---|---|
| Size | 11.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eaca687cb82dcdccc16ff5ad43d2237f14491cae8063828a2064286992a96abe
|
|
BLAKE2b-256 checksum How to use checksums |
ef50434ba4974193daec15730b8df4461acbb9976c8bd360e46aa4f20f7aecb3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Apr 1, 2026.
Transparency log