Skip to main content

A Django framework that provides soft delete functionality with automatic query filtering, admin integration, and safe relation prefetching for models using a deleted_at timestamp.

Project description

DuckDI Logo

duck-django-soft-delete

Pragmatic soft delete framework for Django.
It provides a safe and transparent way to "delete" records by marking them with a timestamp instead of permanently removing them from the database.

This approach is useful when you need:

  • Data recovery after accidental deletions;
  • Historical/audit tracking;
  • To keep database integrity while still hiding records from normal queries;
  • To allow reusing unique keys (with conditional uniqueness).

Features

  • deleted_at field automatically added to your models;
  • Ready-to-use managers:
    • objects → filters out deleted records;
    • everything → returns all records (including deleted);
  • soft_delete() and restore() methods;
  • Admin integration: filter by deletion status + actions for Soft Delete and Restore;
  • Helper build_prefetches() to automatically prefetch only alive related records;
  • Supports partial unique constraints in Postgres (unique only among alive records).

Installation

pip install duck-django-soft-delete

Add to your project and import:

# settings.py
INSTALLED_APPS = [
  # ...
  "duck_django_soft_delete",
]

Concept

Abstract base

from duck_django_soft_delete.table.soft_delete_table import SoftDeleteTable

class MyModel(SoftDeleteTable):
    # your fields...

SoftDeleteTable defines:

  • Model time stamp fields: created_at, updated_at
  • Soft delete model field: deleted_at
  • Django, soft delete and restore model methods: soft_delete() / restore()
  • Django prefetch_related method for soft delete build_prefetches(relations: list[str]) -> list[Prefetch|str]
  • Load django model as BaseModel Dto: as_dto(include_fks: bool = False, show_deleted: bool = False) -> BaseModel (abstract method — implement in your model)

Managers

  • objectsalive only (deleted_at IS NULL)
  • everythingall (alive and deleted)

Real example

Model with conditional uniqueness (Postgres)

from duck_django_soft_delete.table.soft_delete_table import SoftDeleteTable
from django.db import models
from uuid import uuid4

class Item(SoftDeleteTable):
    uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    name = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True, default="")
    is_public = models.BooleanField(default=True)
    
    foreign_key_exemple = models.ForeignKey(
		"foreign_key_exemple.ForeignKeyExemple",
		on_delete=models.CASCADE,
		db_column="foreign_key_exemple_id",
		related_name="foreign_key_exemples",
	)
	
	class Meta(SoftDeleteTable.Meta):
		db_table = "items"

Admin

from duck_django_soft_delete.admin.soft_delete_admin import SoftDeleteAdmin
from django.contrib import admin
from item import Item

@admin.register(Item)
class ItemAdmin(SoftDeleteAdmin):
    list_display = (
        "uuid", 
        "name",
        "is_public",
        "created_at", 
        "updated_at", 
        "deleted_at",
    )
    list_filter = [
	    *SoftDeleteAdmin.list_filter, 
	    "is_public", 
	    "foreign_key_exemple",
	    "updated_at",
	]
    search_fields = ("name", "is_public", "foreign_key_exemple__id", )

    # ... your @admin.display helpers

SoftDeleteAdmin provides:

  • filter SoftDeletedAtFilter (Not Deleted / Deleted);
  • actions “Soft delete selected” and “Restore selected”;
  • get_queryset using everything so admin sees all records.

Usage

obj = Item.objects.create(name="X")
obj.soft_delete()           # sets deleted_at = now()
obj.restore()               # clears deleted_at
Item.objects.all()      # only alive
Item.everything.all()   # all

Prefetch alive-only relations

If you want to prefetch only alive children when the related model also inherits SoftDeleteTable:

qs = Item.objects.all().prefetch_related(
    *Item.build_prefetches(["foreign_key_exemples", ])
)
  • If the relation inherits SoftDeleteTable, prefetch uses its alive manager.
  • If it does not, falls back to standard Django prefetch.

Best practices

  • Postgres: use UniqueConstraint(..., condition=Q(deleted_at__isnull=True)) for uniqueness only on alive records.
	class Meta(SoftDeleteTable.Meta):
		db_table = "items"
		constraints = [
			models.UniqueConstraint(
				fields=["foreign_key_exemple", "name"],
				condition=Q(deleted_at__isnull=True),
				name="unique_item_name_per_foreign_key_exemple_not_deleted",
			)
		]
  • Document whether you override Model.delete() to do soft delete by default (this lib does not enforce — you can choose to keep delete() as hard delete).

Compatibility

  • Django: 4.2+
  • Python: 3.10+

Admin — details

  • Filter: “Soft Delete” → Not Deleted / Deleted
  • Actions:
    • Soft delete selected: calls soft_delete() on alive items;
    • Restore selected: calls restore() on deleted items;
  • get_queryset uses model.everything.all() so admin shows all records.

Tip: you can remove Django’s default delete_selected action to avoid accidental hard deletes.


Tests

See the tests/ folder in this repo (pytest + pytest-django), covering:

  • Managers (objects vs everything);
  • soft_delete() and restore();
  • Partial unique (Postgres);
  • Admin actions;
  • build_prefetches().

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

duck_django_soft_delete-0.1.4.tar.gz (5.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

duck_django_soft_delete-0.1.4-py3-none-any.whl (7.3 kB view details)

Uploaded Python 3

File details

Details for the file duck_django_soft_delete-0.1.4.tar.gz.

File metadata

  • Download URL: duck_django_soft_delete-0.1.4.tar.gz
  • Upload date:
  • Size: 5.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.13.4 Darwin/24.0.0

File hashes

Hashes for duck_django_soft_delete-0.1.4.tar.gz
Algorithm Hash digest
SHA256 8522a6cae11b5b74c8f2d5d466cd3ca2f332633ce14242ec7eabd697d05950d0
MD5 1ae19c0eb2c180db052750d45da1853a
BLAKE2b-256 7e2b313c41d522363ce99d52c936c553582c13ff4e57303323d91210d91850c9

See more details on using hashes here.

File details

Details for the file duck_django_soft_delete-0.1.4-py3-none-any.whl.

File metadata

File hashes

Hashes for duck_django_soft_delete-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 085397a90537ad21d9bef4c3efb97a4c5992ac5a63296ce7ed2e84d4f5f0fe0b
MD5 e495650bf08fa196f246e67dc9b40c0d
BLAKE2b-256 fa15beae1a82397cd8564fe6ee1e8126de1c6995f1f14172d3c9121df579db49

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page