Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding QuerySet.delete() in Django

Tags:

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?

like image 537
Chris Pratt Avatar asked Jun 23 '11 19:06

Chris Pratt


People also ask

How do I delete a Queryset in Django?

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.

What is soft delete in Django?

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.


2 Answers

You can override a Manager's default QuerySet by overriding the Manager.get_query_set() method.

Example:

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

like image 183
manji Avatar answered Oct 15 '22 16:10

manji


mixin approach

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 

other interesting stuff

  • http://datahackermd.com/2013/django-soft-deletion/
  • https://github.com/hearsaycorp/django-livefield
like image 31
dnozay Avatar answered Oct 15 '22 17:10

dnozay