26

I have a model:

class A(models.Model):
    number = models.IntegerField()

But when I call A.save(), I want to ensure that number is a prime (or other conditions), or the save instruction should be cancelled.

So how can I cancel the save instruction in the pre_save signal receiver?

@receiver(pre_save, sender=A)
def save_only_for_prime_number(sender, instance, *args, **kwargs):
    # how can I cancel the save here?
halfer
  • 19,824
  • 17
  • 99
  • 186
Alfred Huang
  • 17,654
  • 32
  • 118
  • 189

3 Answers3

27

See my another answer: https://stackoverflow.com/a/32431937/2544762

This case is normal, if we just want to prevent the save, throw an exception:

from django.db.models.signals import pre_save, post_save

@receiver(pre_save)
def pre_save_handler(sender, instance, *args, **kwargs):
    # some case
    if case_error:
        raise Exception('OMG')
Community
  • 1
  • 1
Alfred Huang
  • 17,654
  • 32
  • 118
  • 189
  • 7
    Be aware that if you use use update on a queryset, this hook is not triggered. e.g. `MyModel.objects.filter(some_case=1).update(someval=2)` Just prevent to use it. – Alfred Huang May 16 '17 at 00:28
  • 1
    @AlfredHuang, any way to raise an error when updating a queryset? – TheJKFever Jun 16 '17 at 23:41
  • @TheJKFever AFAK, there is no straight forward solution, because queryset update will use one sql update on many rows. If you want to do so, traverse the queryset and update it one by one. – Alfred Huang Jun 18 '17 at 22:25
  • I think you could handle it with the post_save signal for updates since that's called on update. So a combination of the two signals would probably be the right answer here. – Vishal Rao Jan 29 '19 at 18:39
  • 2
    I need to do this but raising exception breaks the flow. I don't want to break the flow. Also I can't override save method or use post_save. – Jayesh Apr 25 '20 at 18:26
  • 1
    Where's the best place to catch the exception raised in `pre_save`? I want to prevent the save, but don't want to result in a 500 error. – justin Mar 26 '21 at 16:59
14

I'm not sure you can cancel the save only using the pre_save signal. But you can easily achieve this by overriding the save method:

def save(self):
    if some_condition:
        super(A, self).save()
    else:
       return   # cancel the save

As mentioned by @Raptor, the caller won't know if the save was successful or not. If this is a requirement for you, take look at the other answer which forces the caller to deal with the "non-saving" case.

Community
  • 1
  • 1
Seb D.
  • 5,046
  • 1
  • 28
  • 36
4

If the data's always coming from a Form and you have a straightforward test for whether or not the save should occur, send it through a validator. Note, though, that validators aren't called for save() calls originating on the backend. If you want those to be guarded as well, you can make a custom Field, say class PrimeNumberField(models.SmallIntegerField) If you run your test and raise an exception in the to_python() method of that custom field, it will prevent the save. You can also hook into the validation of a specific field by overriding any of several other methods on the Field, Form, or Model.

Sarah Messer
  • 3,592
  • 1
  • 26
  • 43
  • After a long time of practice, I found making a validator to restrict the field and always save before full_clean is a good architecture. You solution is the most enlightening. – Alfred Huang Sep 23 '15 at 06:09