Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django cannot delete single object after rewriting model.Manager method

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)
like image 313
The Hog Avatar asked Mar 16 '17 08:03

The Hog


2 Answers

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.

like image 86
SomeTypeFoo Avatar answered Nov 09 '22 20:11

SomeTypeFoo


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
like image 27
Kevin Christopher Henry Avatar answered Nov 09 '22 22:11

Kevin Christopher Henry