This would wrap up solution from the implementation in my app. Some code is form LWN's answer.
There are 4 situations that your data get deleted:
- SQL query
- Calling
delete()
on Model instance: project.delete()
- Calling
delete()
on QuerySet innstance: Project.objects.all().delete()
- Deleted by ForeignKey field on other Model
While there is nothing much you can do with the first case, the other three can be fine grained controlled.
One advise is that, in most case, you should never delete the data itself, because those data reflect the history and usage of our application. Setting on active
Boolean field is prefered instead.
To prevent delete()
on Model instance, subclass delete()
in your Model declaration:
def delete(self):
self.active = False
self.save(update_fields=('active',))
While delete()
on QuerySet instance needs a little setup with a custom object manager as in LWN's answer.
Wrap this up to a reusable implementation:
class ActiveQuerySet(models.QuerySet):
def delete(self):
self.save(update_fields=('active',))
class ActiveManager(models.Manager):
def active(self):
return self.model.objects.filter(active=True)
def get_queryset(self):
return ActiveQuerySet(self.model, using=self._db)
class ActiveModel(models.Model):
""" Use `active` state of model instead of delete it
"""
active = models.BooleanField(default=True, editable=False)
class Meta:
abstract = True
def delete(self):
self.active = False
self.save()
objects = ActiveManager()
Usage, just subclass ActiveModel
class:
class Project(ActiveModel):
...
Still our object can still be deleted if any one of its ForeignKey fields get deleted:
class Employee(models.Model):
name = models.CharField(name, unique=True)
class Project(models.Model):
name = models.CharField(name, unique=True)
manager = purchaser = models.ForeignKey(
Employee, related_name='project_as_manager')
>>> manager.delete() # this would cause `project` deleted as well
This can be prevented by adding on_delete argument of Model field:
class Project(models.Model):
name = models.CharField(name, unique=True)
manager = purchaser = models.ForeignKey(
Employee, related_name='project_as_manager',
on_delete=models.PROTECT)
Default of on_delete
is CASCADE
which will cause your instance deleted, by using PROTECT
instead which will raise a ProtectedError
(a subclass of IntegrityError
). Another purpose of this is that the ForeignKey of data should be kept as a reference.