django-safedelete

repository·master·Indexed 20 days ago

https://github.com/makinacorpus/django-safedelete

A Django application providing an abstract model for transparently retrieving or deleting objects without physically removing them from the database. It supports various deletion policies including SOFT_DELETE, SOFT_DELETE_CASCADE, HARD_DELETE, HARD_DELETE_NOCASCADE, and NO_DELETE. The library includes SafeDeleteAdmin for Django admin integration, SafeDeleteManager for controlling object visibility, and custom signals (pre_softdelete, post_softdelete, post_undelete) to hook into the deletion and restoration lifecycles.

Tokens
3.2K
Snippets
11
Records
15
Agent score
71%

What's inside django-safedelete

  1. What are the available soft-delete policies?

    master

    When inheriting from SafeDeleteModel, you can define the _safedelete_policy attribute to control how objects are handled during deletion. The available policies are:

    • SOFT_DELETE: The default behavior. The object is masked from the database but not physically removed.
    • SOFT_DELETE_CASCADE: The object is masked, and any dependent models are also masked.
    • HARD_DELETE: The object is normally deleted from the database.
    • HARD_DELETE_NOCASCADE: The object is hard-deleted, unless its deletion would trigger the deletion of other objects (via cascade). In that case, the object is only masked (soft-deleted) to prevent the cascade.
    • NO_DELETE: The object is never deleted or masked (use with caution).
  2. Use DELETED_INVISIBLE visibility policy

    master

    This is the default visibility policy. Objects marked as deleted are hidden from most queries, but they remain accessible via direct relationships.

    • Direct Access: If an object is referenced via a OneToOneField or ForeignKey, you can still access it (e.g., article.author works even if the author is masked).
    • Reverse Access: Deleted objects will NOT appear in reverse relationships (e.g., author.article_set will not include the masked article).
  3. Use DELETED_VISIBLE_BY_FIELD visibility policy

    master

    This policy behaves like DELETED_INVISIBLE, with one key exception: deleted objects can still be retrieved if you access them directly using the primary key (or the field specified by _safedelete_visibility_field) via .get() or .filter().

    To use this, set _safedelete_visibility to DELETED_VISIBLE_BY_FIELD. You can customize which field is used for this direct access by setting the _safedelete_visibility_field attribute on the manager.

  4. Configure SafeDelete policies for models

    master

    You can control how objects are removed from your database by setting the _safedelete_policy attribute on your model. This attribute determines whether a delete() call results in a permanent removal (hard delete) or a masking (soft delete), and how cascading affects related objects.

    Available policies:

    • HARD_DELETE: Standard Django behavior. Objects are permanently removed from the database. You can override this manually for a specific instance using obj.delete(force_policy=SOFT_DELETE).
    • SOFT_DELETE: Objects are masked (not deleted) when delete() is called. Related objects are not masked in cascade.
    • SOFT_DELETE_CASCADE: Objects and all related objects are masked when delete() is called. They are masked in cascade.
    • HARD_DELETE_NOCASCADE: Deletes the object from the database if no other objects depend on it. If deleting the object would trigger a cascade deletion of other objects, the object is masked instead of hard-deleted.
    • NO_DELETE: Prevents objects from being masked or deleted via the Django ORM. Removal requires raw SQL.
    class MyModel(SafeDeleteModel):
        _safedelete_policy = 'SOFT_DELETE_CASCADE'
  5. Use SafeDeleteAdmin for Django Admin integration

    master

    To manage soft-deleted objects in the Django admin site, use the SafeDeleteAdmin class from safedelete.admin.

    By default, deleted objects are hidden from the admin site. Using SafeDeleteAdmin provides:

    • Access to deleted objects in the admin interface.
    • An undelete action to restore objects in bulk.
    • Protection for the deleted attribute (it is excluded from editing by default).

    To implement it, inherit from SafeDeleteAdmin in your admin.py file.

    from django.contrib import admin
    from safedelete.admin import SafeDeleteAdmin
    from .models import MyModel
    
    @admin.register(MyModel)
    class MyModelAdmin(SafeDeleteAdmin):
        pass
  6. Configure django-safedelete in Django settings

    master

    After installation, add safedelete to your INSTALLED_APPS. You can also configure specific behaviors in your Django settings file.

    # settings.py
    
    INSTALLED_APPS = [
        'safedelete',
        # ...
    ]
    
    # Optional: If True, update_or_create() returns created=True when a soft-deleted object is 'revived'
    SAFE_DELETE_INTERPRET_UNDELETED_OBJECTS_AS_CREATED = True
    
    # Optional: Override the default field name used to indicate a soft-deleted state (default is 'deleted')
    SAFE_DELETE_FIELD_NAME = 'is_deleted'
  7. Handle field uniqueness with soft-deleted objects

    master

    Standard Django unique=True constraints check against all rows in the database, including soft-deleted ones. This prevents a user from reusing a value (like a username or slug) that belongs to a masked object.

    To allow reusing values from soft-deleted objects, use a partial UniqueConstraint in the model's Meta class that only applies to objects where the deleted field is null.

    from django.db import models
    from django.db.models import Q, UniqueConstraint
    from safedelete.models import SafeDeleteModel
    
    class Post(SafeDeleteModel):
        name = models.CharField(max_length=100)
        
        class Meta:
            constraints = [
                UniqueConstraint(
                    fields=['name'],
                    condition=Q(deleted__isnull=True),
                    name='unique_active_name'
                ),
            ]
  8. Implement soft-delete using SafeDeleteModel

    master

    To use django-safedelete, inherit your models from safedelete.models.SafeDeleteModel and set the _safedelete_policy attribute. This allows you to control whether deletions are physical (hard) or logical (soft/masked).

    from safedelete.models import SafeDeleteModel, HARD_DELETE_NOCASCADE
    from django.db import models
    
    class Article(SafeDeleteModel):
        # This policy will hard-delete the article, or soft-delete it if it would cause a cascade
        _safedelete_policy = HARD_DELETE_NOCASCADE
        name = models.CharField(max_length=100)
    
    class Order(SafeDeleteModel):
        _safedelete_policy = HARD_DELETE_NOCASCADE
        name = models.CharField(max_length=100)
        articles = models.ManyToManyField(Article)
  9. Customize policy delete logic by overwriting action functions

    master

    Each policy provides an overwritable function that allows you to inject custom logic before or after the standard deletion process. To implement this, override the corresponding function in your model class and call super().<function_name>(**kwargs) to ensure the original policy logic is executed.

    PolicyOverwritable Function
    SOFT_DELETEsoft_delete_policy_action
    HARD_DELETEhard_delete_policy_action
    HARD_DELETE_NOCASCADEhard_delete_cascade_policy_action
    SOFT_DELETE_CASCADEsoft_delete_cascade_policy_action
    def soft_delete_policy_action(self, **kwargs):
        # Insert here custom pre delete logic
        delete_response = super().soft_delete_policy_action(**kwargs)
        # Insert here custom post delete logic
        return delete_response
  10. Configure highlight_deleted_field in SafeDeleteAdmin

    master

    To use highlight_deleted_field to show deleted objects in red using a specific field value instead of the __str__ method, follow these steps in your SafeDeleteAdmin class:

    1. Set field_to_highlight = "your_field_name".
    2. Add `
    @admin.register(MyModel)
    class MyModelAdmin(SafeDeleteAdmin):
        field_to_highlight = "name"
        list_filter = ("highlight_deleted_field",)
        
        # Optional: set a custom short description for the filter
        highlight_deleted_field_short_description = "Is Deleted?"
  11. Configure object visibility with SafeDeleteManager

    master

    The SafeDeleteManager determines which objects are included in querysets. You can control how 'masked' (soft-deleted) objects are surfaced by setting the _safedelete_visibility attribute on your manager instance. This allows you to decide whether deleted objects should be hidden from standard queries or remain accessible under certain conditions.

    from safedelete.managers import SafeDeleteManager
    
    class MyModelManager(SafeDeleteManager):
        # Set visibility here
        _safedelete_visibility = 'DELETED_VISIBLE_BY_FIELD'
        _safedelete_visibility_field = 'pk'