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

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.2.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.2-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: duck_django_soft_delete-0.1.2.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.2.tar.gz
Algorithm Hash digest
SHA256 936402f8facc863c3e8e8919ed38ae4aeb183a5b110922558d64e7063f6326e3
MD5 dc42321d159a996349719dfaf70c5ab9
BLAKE2b-256 d38071a8ef09868923d1898f30c0e59b05f3434a0e435e2b722ca20cea75104f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for duck_django_soft_delete-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 97733ee23a30f39c3a5a49e9b711217aef6cb6d60f30bafd1d5e1c6551035ceb
MD5 084110247cb7fb42d127f2fb13fd0f22
BLAKE2b-256 f7ca61e44d36e17d2e1da30b817764058540e9ce5b8270680e8de0613002b8a1

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