Django SafeDelete
Django SafeDelete is an active library (current version 1.4.1) that provides soft deletion functionality for Django models, allowing objects to be masked from the database instead of permanently deleted. This enables recovery of 'deleted' data and aids in auditing. It offers various deletion policies and integrates with Django's ORM and Admin interface. Releases occur periodically, with significant updates often tied to Django version compatibility.
Warnings
- breaking The `SafeDeleteMixin` class was renamed to `SafeDeleteModel` in version 0.4.0. Using `SafeDeleteMixin` is deprecated and will raise a warning.
- breaking The `deleted` field on `SafeDeleteModel` changed from a `BooleanField` to a `DateTimeField` in version 0.4.0. This can break existing queries or logic relying on a boolean check.
- breaking Significant Django and Python version compatibility changes can occur between minor and major versions of django-safedelete. For instance, version 1.3.0 dropped support for Django < 3.2 and Python 3.6, and version 1.4.0 dropped support for Django 3.2, 4.0, and 4.1.
- gotcha By default, related objects (via `ForeignKey` or `OneToOneField`) that are soft-deleted might still be accessible through direct relation traversal (e.g., `article.author` if `author` is deleted). This can lead to unexpected behavior where 'deleted' objects are still retrieved.
- gotcha If the `SAFE_DELETE_INTERPRET_UNDELETED_OBJECTS_AS_CREATED` setting is enabled, Django's `update_or_create()` method will return `(object, True)` (indicating 'created') when an object was soft-deleted and subsequently 'revived' or undeleted, rather than a truly new object being created. This can be misleading.
Install
-
pip install django-safedelete
Imports
- SafeDeleteModel
from safedelete.models import SafeDeleteModel
- SOFT_DELETE, HARD_DELETE, SOFT_DELETE_CASCADE, HARD_DELETE_NOCASCADE, NO_DELETE
from safedelete.models import SOFT_DELETE
Quickstart
import os
import django
from django.db import models
from safedelete.models import SafeDeleteModel, SOFT_DELETE
# Configure Django settings minimally for a runnable example
os.environ.setdefault('DJANGO_SETTINGS_MODULE', __name__)
django.setup()
class Article(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE
title = models.CharField(max_length=200)
content = models.TextField()
published_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
# Example Usage:
# Make sure Django is set up and models are migrated (not covered here)
# Create an object
article = Article.objects.create(title='My First Article', content='This is some content.')
print(f"Created: {article}") # cite: 5
# Soft delete the object
article.delete()
print(f"Article soft-deleted. Is it deleted? {article.deleted is not None}") # cite: 5
# It won't appear in default queries
print(f"Visible articles count: {Article.objects.count()}") # Should be 0
# Access soft-deleted objects using the all_objects manager
all_articles = Article.all_objects.all()
print(f"All articles (including deleted): {all_articles.count()}") # Should be 1 # cite: 5
# Filter for only deleted objects
deleted_articles = Article.deleted_objects.all()
print(f"Only deleted articles: {deleted_articles.count()}") # Should be 1
# Undelete the object
deleted_article = Article.deleted_objects.get(pk=article.pk)
deleted_article.undelete()
print(f"Article undeleted. Is it deleted? {deleted_article.deleted is not None}")
print(f"Visible articles count after undelete: {Article.objects.count()}") # Should be 1
# Hard delete (bypassing soft delete)
hard_delete_article = Article.objects.create(title='To be hard deleted', content='Ephemeral content.')
hard_delete_article.delete(force_policy=SafeDeleteModel.HARD_DELETE)
print(f"Hard-deleted article count: {Article.all_objects.filter(pk=hard_delete_article.pk).count()}") # Should be 0