Django Soft-Delete
Soft delete" in database lingo means that you set a flag on an existing table which indicates that a record has been deleted, instead of actually deleting the record.
Let's say you want to implement soft-delete for a model defined in a django application. Let's imagine you have a model Item:
from django.db import models class Item(models.Model): title = models.CharField(max_length=255) content = models.TextField()
Now you want to add ability to use soft delete for this model. First we need to add a field that will tell us if our Item is deleted. We have at least 2 options here: we can add a boolean is_deleted
field or a datetime deleted_at
field. We will use the latter in this example. Let's create an abstract model for simplicity:
from django.db import models from django.utils import timezone class SoftDeleteModel(models.Model): deleted_at = models.DateTimeField(blank=True, null=True) def delete(self, *args, **kwargs): self.deleted_at = timezone.now() self.save() def restore(self): self.deleted_at = None self.save() def hard_delete(self, *args, **kwargs): super(SoftDeleteModel, self).delete(*args, **kwargs) class Meta: abstract = True
Next, we need to implement a custom queryset object, so we can do same operations with querysets:
from django.utils import timezone from django.db.models.query import QuerySet class SoftDeleteQuerySet(QuerySet): def delete(self): return super(SoftDeleteQuerySet, self).update(deleted_at=timezone.now()) def restore(self): return super(SoftDeleteQuerySet, self).update(deleted_at=None) def hard_delete(self): return super(SoftDeleteQuerySet, self).delete() def not_deleted(self): return self.filter(deleted_at=None) def deleted(self): return self.exclude(deleted_at=None)
Quite easy. Now we need to define a custom object manager and use queryset that we created in previous code block:
from django.db import models from softdelete.querysets import SoftDeleteQuerySet class SoftDeleteManager(models.Manager): def __init__(self, *args, **kwargs): self.all_objects = kwargs.pop('all_objects', False) super(SoftDeleteManager, self).__init__(*args, **kwargs) def get_queryset(self): if self.all_objects: return SoftDeleteQuerySet(self.model) return SoftDeleteQuerySet(self.model).not_deleted() def hard_delete(self): return self.get_queryset().hard_delete()
As a final change we just change our Item model so it inherits from SoftDeleteModel, and it's done!
from softdelete.models import SoftDeleteModel class BlogPost(SoftDeleteModel): title = models.CharField(max_length=255) content = models.TextField()