views:

249

answers:

1

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()
A: 

I think anything with current in use, popular, technologies, there is no way to have problem domain agnostic, generic soft deletes. I think it is more linked to historical/history oriented database systems than to what we are used. I recommend you to not circumvent django's delete (which is a hard delete). Keep as is.

The "delete" that you most likely will have in our system, is in 90% of the case, a visual delete ...

In this regard, try to find synonyms with delete for your specific domain problem and do this from the start of the project.

Because complain that a IsVisible, IsUnpublished (even IsDeleted) mess up your queries, they complain that you must always be careful to include them...

But this is obviously ignorance of the domain problem, if the domain has objects that can be made invisible, or become unpublished - of course when you query the list of all the objects you want to display, you should FROM THE START, QUERY ALL THE OBJECTS that are not visible and unpublished because this is how your domain problem is solved in a complete form.

Cheers.

gion