Tuesday, March 28, 2017

Django cannot delete single object after rewriting model.Manager method

Leave a Comment

I am trying to rewrite get_by_natural_key method on django manager (models.Manager). After adding model (NexchangeModel) I can delete all() objects but single - cannot.

Can:

SmsToken.objects.all().delete() 

Cannot:

SmsTokent.objects.last().delete() 

Code:

from django.db import models from core.common.models import SoftDeletableModel, TimeStampedModel, UniqueFieldMixin  class NexchangeManager(models.Manager):     def get_by_natural_key(self, param):         qs = self.get_queryset()         lookup = {qs.model.NATURAL_KEY: param}         return self.get(**lookup)   class NexchangeModel(models.Model):     class Meta:         abstract = True     objects = NexchangeManager()  class SmsToken(NexchangeModel, SoftDeletableModel, UniqueFieldMixin):     sms_token = models.CharField(         max_length=4, blank=True)     user = models.ForeignKey(User, related_name='sms_token')     send_count = models.IntegerField(default=0) 

2 Answers

Answers 1

While you are calling: SmsToken.objects.all().delete() you are calling the queryset's delete method.

But on SmsTokent.objects.last().delete() you are calling the instance's delete method.

After django 1.9 queryset delete method returns no of items deleted. REF

Changed in Django 1.9: The return value describing the number of objects deleted was added.

But on instance delete method Django already knows only one row will be deleted.

Also note that querset's delete method and instance's delete method are different.

The delete()[on a querset] method does a bulk delete and does not call any delete() methods on your models[instance method]. It does, however, emit the pre_delete and post_delete signals for all deleted objects (including cascaded deletions).

So you cannot rely on the response of the method to check if the delete worked fine or not. But in terms of python's philosophy "Ask for forgiveness than for permission". That means you can rely on exceptions to see if a delete has worked properly the way it should. Django's ORM will raise proper exceptions and do proper rollbacks in case of any failure.

So you can do this:

try:     instance.delete()/querset.delete() except Exception as e:     # some code to notify failure / raise to propagate it     # you can avoid this try..except if you want to propagate exceptions as well. 

Note: I am catching generic Exception because the only code in my try block is delete. If you wish to have some other code then you must catch specific exceptions only.

Answers 2

I assume that SoftDeletableModel comes from the django-model-utils package? If so, the purpose of that model is to mark instances with an is_removed field rather than actually deleting them. So it's to be expected that calling delete() on a model instance—which is what you get from last()—wouldn't actually delete anything.

SoftDeletableModel provides an objects attribute with a manager that limits its results to non-removed objects, and overrides delete() to mark objects as removed instead of actually deleting them.

The problem is that you've defined your own manager as objects, so the SoftDeletableModel manager isn't being used. Your custom manager is actually bulk deleting objects from the database, contrary to the goal of doing a soft delete. The way to resolve this is to have your custom manager inherit from SoftDeletableManagerMixin:

class NexchangeManager(SoftDeletableManagerMixin, models.Manager):     # your custom code 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment