Hello,
I'm using the following setup to implement soft deletes in Django. I'm not very familiar with Django under the hood so I'd appreciate any feedback on gotchas I might encounter. I'm particular uncomfortable subclassing a QuerySet.
The basic idea is that the first call to delete
on a MyModel
changes MyModel
's date_deleted
to the current datetime. A second delete
will actually delete the object. (Catching a delete
requires two overrides, one on the object and one on the QuerySet
, which can bypass an object's delete
method.) Since the default manager will hide deleted objects, deleted objects disappear and must be explicitly requested via the deleted_objects
manager.
Using this setup requires defining DeletionQuerySet
and DeletionManager
and adding date_deleted
, objects
, and deleted_objects
to your model(s).
Thanks,
P.S., forgot to mention that this method of filtering objects out of the default manager is strongly discouraged!
class DeletionQuerySet(models.query.QuerySet):
def delete(self):
prev_deleted = self.filter(date_deleted__isnull=False)
prev_deleted.actual_delete()
prev_undeleted = self.filter(date_deleted__isnull=True)
prev_undeleted.update(date_deleted=datetime.datetime.now())
def actual_delete(self):
super(DeletionQuerySet, self).delete()
class DeletionManager(models.manager.Manager):
# setting use_for_related_fields to True for a default manager ensures
# that this manager will be used for chained lookups, a la double underscore,
# and therefore that deleted Entities won't popup unexpectedly.
use_for_related_fields = True
def __init__(self, hide_deleted=False, hide_undeleted=False):
super(DeletionManager, self).__init__()
self.hide_deleted = hide_deleted
self.hide_undeleted = hide_undeleted
def get_query_set(self):
qs = DeletionQuerySet(self.model)
if self.hide_deleted:
qs = qs.filter(date_deleted__isnull=True)
if self.hide_undeleted:
qs = qs.filter(date_deleted__isnull=False)
return qs
class MyModel(models.Model):
# Your fields here...
date_deleted = models.DateTimeField(null=True)
#the first manager defined in a Model will be the Model's default manager
objects = DeletionManager(hide_deleted=True)
deleted_objects = DeletionManager(hide_undeleted=True)
def delete(self):
if self.date_deleted is None:
self.date_deleted = datetime.datetime.now()
self.save()
else:
super(Agreement, self).delete()