django-softdelete project on github

Inspired by a post on override the default model manager to support soft-deleting objects in the database, I decided to implement it into one of my projects.  (Yes, I know it’s an old post but it was new to me!).

However, just being able to filter the query sets returned to exclude items which had deleted=true wasn’t enough for what I wanted to do.  Nor was just customizing the django’s admin interface to filter by on whether or not deleted was true or false.  I wanted the modified manager and objects to soft-delete all related objects (models that referenced the object-being-deleted via a ForeignKey).

Thus, the django-softdelete project was born.

The code to handle soft-delete was made into an abstract base class that concrete models can inherit from; thereby giving them the deleted field as well as setting derived classes to use the SoftDeleteQuerySet model manager to act as their model manager.

Once I added the cascading soft-delete, I decided I also wanted an undelete operation that would cascade in a similar fashion.   But just naively undeleting all related objects wouldn’t provide the level of data integrity I was looking for.   I needed some way to track the objects that were part of a cascading soft-delete. So I came up with the ChangeSet model.

class ChangeSet(models.Model):
     created_date = models.DateTimeField(default=datetime.utcnow)
     content_type = models.ForeignKey(ContentType)
     object_id = models.PositiveIntegerField()
     record = generic.GenericForeignKey('content_type', 'object_id')

The ChangeSet links to the “master” object being soft-deleted with a GenericForeignKey.

A ChangeSet encompasses the total cascading soft-delete.  To accomplish this, each time an object is soft-deleted a SoftDeleteRecord is created. Like the ChangeSet, the SoftDeleteRecord uses a GenericForeignKey to link to the object that this record is tracking.

class SoftDeleteRecord(models.Model):
     changeset = models.ForeignKey(ChangeSet, related_name='recordsets')
     created_date = models.DateTimeField(default=datetime.utcnow)
     content_type = models.ForeignKey(ContentType)
     object_id = models.PositiveIntegerField()
     record = generic.GenericForeignKey('content_type', 'object_id')

When you soft-delete an object, any objects referencing it via a ForeignKey, ManyToManyField, or OneToOneField will also be soft-deleted.  This mimics the traditional CASCADE behavior of a SQL DELETE.

When the soft-delete is performed, the system makes a ChangeSet object which tracks all affected objects of this delete request.  Later, when an undelete is requested, this ChangeSet is referenced to do a cascading undelete.

If you are undeleting an object that was part of a ChangeSet, that entire ChangeSet is undeleted.

Once undeleted, the ChangeSet object is removed from the underlying database with a regular (“hard”) delete.

You can get access to the code at the git repository.


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *