Preview cascade deletes before execution and enforce safe deletion with explicit confirmation.
- 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
pip install django-delete-previewAdd to INSTALLED_APPS:
INSTALLED_APPS = [
...
"django_delete_preview",
]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)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"]}
# }# Without confirmation → raises ValueError
author.delete()
# ValueError: Delete blocked. Run preview_delete() first and confirm the operation.
# With confirmation → executes
author.delete(confirm=True)summary = Author.objects.filter(active=False).preview_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()# 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{
"database": "default",
"total_objects": 10,
"models": {
"myapp.Author": 1,
"myapp.Order": 9
}
}# settings.py — maximum number of item string representations per model (default: 100)
DELETE_PREVIEW_MAX_ITEMS = 50- 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
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).
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 publisherspython manage.py shellfrom 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"]
# }
# }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})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},
# ...
# }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})# 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 --executePublisher 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)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