Reputation: 17216
We've developed quick way to do soft deletes in Django by:
Works well in practice, Transfers are hidden when they're deleted and queries don't return them.
The only problem is we'd like these still to show up when being referenced by foreign key in another model. For example the Transaction model references Transfer such as transaction.transfer, which is now None to Django.
Any ideas?
Upvotes: 3
Views: 2557
Reputation: 575
In addition to @kevin-parker's answer i want to left this code based on idea from article Soft Deletion in Django. I made changes to support polymorphic models, but did not test this code. Changed my mind to use django-polymorphic.
from django.db import models
from django.utils import timezone
from polymorphic.models import PolymorphicModel
from polymorphic.managers import PolymorphicManager
from polymorphic.query import PolymorphicQuerySet
class SoftPolymorphicQuerySet(PolymorphicQuerySet):
def delete(self):
return super().update(deleted_at=timezone.now())
def hard_delete(self):
return super().delete()
def alive(self):
return self.filter(deleted_at=None)
def dead(self):
return self.exclude(deleted_at=None)
class SoftPolymorphicDeletionManager(PolymorphicManager):
queryset_class = SoftPolymorphicQuerySet
def __init__(self, *args, **kwargs):
self.alive_only = kwargs.pop('alive_only', True)
super().__init__(*args, **kwargs)
def get_queryset(self):
qs = super().get_queryset()
if self.alive_only:
return qs.filter(deleted_at=None)
return qs
def hard_delete(self):
return self.get_queryset().hard_delete()
class SoftPolymorphicDeletionModel(PolymorphicModel):
deleted_at = models.DateTimeField(blank=True, null=True)
class Meta(PolymorphicModel.Meta):
abstract = True
objects = SoftPolymorphicDeletionManager()
all_objects = SoftPolymorphicDeletionManager(alive_only=False)
# For backward compatibility keep original arguments
def delete(self, *args, **kwargs):
self.deleted_at = timezone.now()
self.save()
# Pass arguments to original delete method
def hard_delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
Upvotes: 0
Reputation: 17216
We ended up fixing this, just call objects_soft when we want only the soft objects... This way related lookups already use the default object manager and find related fk's. For our polymorphic models this ends up being:
objects = PolymorphicManager()
objects_soft = SoftPolymorphicObjectManager()
Query them using
TransferBase.objects_soft.filter()...
Where the model extends soft delete functions:
class SoftDeleteFunctions(object):
def soft_delete(self):
self.deleted_on = now()
self.save()
class SoftPolymorphicObjectManager(PolymorphicManager):
def get_queryset(self):
queryset = self.queryset_class(self.model, using=self._db)
return queryset.filter(deleted_on=None)
class SoftObjectManager(Manager):
def get_queryset(self):
queryset = super(Manager, self).get_queryset()
return queryset.filter(deleted_on=None)
Upvotes: 0
Reputation: 871
As long as the manager doesn't have:
use_for_related_fields = True
as a class attribute, the foreign key lookup
transaction.transfer
will still populate a model instance if it exists.
The other thing to note here is that you should be aware that deleting model objects that reference or are referenced by other models will need to be handled delicately so you don't lose any data accidentally when calling delete
.
e.g.
The delete normally works like:
Transfer.objects.filter(id=my_id).delete()
--> Deletes all other objects related to it by default, sets to null if you like
But if you added soft delete, that is application level logic that makes it so if you do:
Transaction.objects.filter(id=another_id).delete()
You may unintentionally delete the objects you didn't want to delete!
If the non-soft-delete models know that deleting is inappropriate, you should be okay (the set null behavior, overriding the delete method on the model class and model manager).
For reference, see how it was done with pinax-models
Upvotes: 2