I have a Django model that holds settings core to the function of an app. You should never delete this model. I'm trying to enforce this application-wide. I've disabled the delete function in the admin, and also disabled the delete method on the model, but QuerySet has it's own delete method. Example:
MyModel.objects.all()[0].delete() # Overridden, does nothing MyModel.objects.all().delete() # POOF!
Ironically, the Django docs say has this to say about why delete() is a method on QuerySet and not Manager:
This is a safety mechanism to prevent you from accidentally requesting Entry.objects.delete(), and deleting all the entries.
How having to include .all()
is a "safety mechanism" is questionable to say the least. Instead, this effectively creates a backdoor that can't be closed by conventional means (overriding the manager).
Anyone have a clue how to override this method on something as core as QuerySet without monkey-patching the source?
The django querysets where we use filter() function to select multiple rows and use delete() function to delete them is also known as bulk deletion.
This is a set of small classes to make soft deletion of objects. Use the abstract model SoftDeleteModel for adding two new fields: is_deleted - is a boolean field, shows weather of a deletion state of object. deleted_at - is a DateTimeField, serves a timestamp of deletion.
You can override a Manager's
default QuerySet
by overriding the Manager.get_query_set()
method.
class MyQuerySet(models.query.QuerySet): def delete(self): pass # you can throw an exception class NoDeleteManager(models.Manager): def get_query_set(self): return MyQuerySet(self.model, using=self._db) class MyModel(models.Model) field1 = .. field2 = .. objects = NoDeleteManager()
Now, MyModel.objects.all().delete()
will do nothing.
For more informations: Modifying initial Manager QuerySets
https://gist.github.com/dnozay/373571d8a276e6b2af1a
use a similar recipe as @manji posted,
class DeactivateQuerySet(models.query.QuerySet): ''' QuerySet whose delete() does not delete items, but instead marks the rows as not active, and updates the timestamps ''' def delete(self): self.deactivate() def deactivate(self): deleted = now() self.update(active=False, deleted=deleted) def active(self): return self.filter(active=True) class DeactivateManager(models.Manager): ''' Manager that returns a DeactivateQuerySet, to prevent object deletion. ''' def get_query_set(self): return DeactivateQuerySet(self.model, using=self._db) def active(self): return self.get_query_set().active()
and create a mixin:
class DeactivateMixin(models.Model): ''' abstract class for models whose rows should not be deleted but items should be 'deactivated' instead. note: needs to be the first abstract class for the default objects manager to be replaced on the subclass. ''' active = models.BooleanField(default=True, editable=False, db_index=True) deleted = models.DateTimeField(default=None, editable=False, null=True) objects = DeactivateManager() class Meta: abstract = True
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With