Cancel saving model when using pre_save in django - python

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?

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')

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.

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.

Related

Best way to raise validation error before save at model level in Django

I have a background job wherein I am creating MyModel using get_or_create method from a data in dictionary.
I have a condition to check and if not satisfied just log the error.
Basically I am looping through a list of dict of data and calling the get_or_create.
If it is at the form level, then I am using clean method but since this is being done in the backend what would be the best way to validate before save and log if validation fails?
I would like to know how to validate on both - before create or update.
Should I use pre_save signal or just clean_field_name or clean method?
Django get_or_create method use save method to create the model. so clean methods will not call here. your best chance is to override .save() of your model and raise your validation:
def save(self, *args, **kwargs):
if not some_condition:
raise ValidationError
else:
super(Model, self).save(*args, **kwargs)

Super method from Django documentation

Here is one piece from Django documentation:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs):
do_something()
super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
do_something_else()
My hesitation is focus on the save method.
Why the author separates do_something from do_something_else?
Because of the existence of 'Call the "real" save() method',what's the meaning of do_something(),which seems to be "false" ?I even can do the manipulation:
def save(self, *args, **kwargs):
super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
do_something()
do_something_else()
Am I right?
Yes, you're right.
There is no deep meaning.
The sentences just mean "You can write some methods which you want to do before super().save or after super().save()."
However, I slightly doubt that you know super() in detail, which is one of the syntax in python.
do_something() and do_something_else() aren't real functions. They aren't defined. They are just hints for you to do something there, then, if you need, do something else, implementing even functions and calling them there, if you need.
Usually you need some field to automatically update/get a value when another field is saved. One way to do this is by overriding the model save method. And usually you do this before you call super().save().
Well, it is just showing that you can fall functions (or write code) before or after calling a parent or sibling method with super.
But first of all, you must know what super is...
In your example, you e calling save with super, which triggers the django based methods and functions and do many other stuff that is within ModelBase.save().
You are doing this to follow standart django save procedure.
But, you may want to make some checks or make some pre-save work, so you can call a method to do stuff that should be done before saving the model instance. Maybe you want to log the record time to a file and you just write a method and call it to log timestamp before you call super().save()
The same is also valid as post-save actions.

Why overriden save() method is bypassed?

In a Django model, I want to avoid doubles so I wrote this:
class Points(TimeStampedModel):
....
def save(self, *args, **kwargs):
if self.pk:
super(Points, self).save(*args, **kwargs)
else: # save() is a creation here, not an update
if Points.objects.filter(benef_card=self.benef_card,
spendable_at=self.spendable_at).exists():
pass
else:
super(Points, self).save(*args, **kwargs)
I was very surprised to find this result in my database:
I suppose there is something wrong with my code, but I'd like to know how these doubles could exist inspite of the protection I wrote in my save() method?
I think what you want instead is:
class Points(TimeStampedModel):
# ...
class Meta:
unique_together = ('benef_card', 'spendable_at')
Then you don't need to override save -- the uniqueness will be handled by a DB constraint and it is generally the way to go. This approach is better because save is not always called (example: bulk operations) so you might get different behavior across your app.
You might also want to check out update_or_create which just returns an object with attributes you need, creating it if it doesn't exist.
You could use Django signals instead to check before save.

Why doesn't django's model.save() call full_clean()?

I'm just curious if anyone knows if there's good reason why django's orm doesn't call 'full_clean' on a model unless it is being saved as part of a model form.
Note that full_clean() will not be called automatically when you call your model’s save() method. You’ll need to call it manually when you want to run one-step model validation for your own manually created models.
django's full clean doc
(NOTE: quote updated for Django 1.6... previous django docs had a caveat about ModelForms as well.)
Are there good reasons why people wouldn't want this behavior? I'd think if you took the time to add validation to a model, you'd want that validation run every time the model is saved.
I know how to get everything to work properly, I'm just looking for an explanation.
AFAIK, this is because of backwards compatibility. There are also problems with ModelForms with excluded fields, models with default values, pre_save() signals, etc.
Sources you might be intrested in:
http://code.djangoproject.com/ticket/13100
http://groups.google.com/group/django-developers/browse_frm/thread/b888734b05878f87
Because of the compatibility considering, the auto clean on save is not enabled in django kernel.
If we are starting a new project and want the default save method on Model could clean automatically, we can use the following signal to do clean before every model was saved.
from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save
#receiver(pre_save)
def pre_save_handler(sender, instance, *args, **kwargs):
instance.full_clean()
The simplest way to call the full_clean method is just to override the save method in your model:
class YourModel(models.Model):
...
def save(self, *args, **kwargs):
self.full_clean()
return super(YourModel, self).save(*args, **kwargs)
Commenting on #Alfred Huang's answer and coments on it. One might lock the pre_save hook down to an app by defining a list of classes in the current module (models.py) and checking against it in the pre_save hook:
CUSTOM_CLASSES = [obj for name, obj in
inspect.getmembers(sys.modules[__name__])
if inspect.isclass(obj)]
#receiver(pre_save)
def pre_save_handler(sender, instance, **kwargs):
if type(instance) in CUSTOM_CLASSES:
instance.full_clean()
If you have a model that you want to ensure has at least one FK relationship, and you don't want to use null=False because that requires setting a default FK (which would be garbage data), the best way I've come up with is to add custom .clean() and .save() methods. .clean() raises the validation error, and .save() calls the clean. This way the integrity is enforced both from forms and from other calling code, the command line, and tests. Without this, there is (AFAICT) no way to write a test that ensures that a model has a FK relation to a specifically chosen (not default) other model.
class Payer(models.Model):
name = models.CharField(blank=True, max_length=100)
# Nullable, but will enforce FK in clean/save:
payer_group = models.ForeignKey(PayerGroup, null=True, blank=True,)
def clean(self):
# Ensure every Payer is in a PayerGroup (but only via forms)
if not self.payer_group:
raise ValidationError(
{'payer_group': 'Each Payer must belong to a PayerGroup.'})
def save(self, *args, **kwargs):
self.full_clean()
return super().save(*args, **kwargs)
def __str__(self):
return self.name
Instead of inserting a piece of code that declares a receiver, we can use an app as INSTALLED_APPS section in settings.py
INSTALLED_APPS = [
# ...
'django_fullclean',
# your apps here,
]
Before that, you may need to install django-fullclean using PyPI:
pip install django-fullclean
A global pre_save signal can work well if you want to always ensure model validation. However it will run into issues with Django's auth in current versions (3.1.x) and could cause issues with models from other apps you are using.
Elaborating on #Peter Shannon's answer, this version will only validate models inside the module you execute it in, skips validation with "raw" saves and adds a dispatch_uid to avoid duplicate signals.
from django.db.models.signals import pre_save
import inspect
import sys
MODELS = [obj for name, obj in
inspect.getmembers(sys.modules[__name__], inspect.isclass)]
def validate_model(sender, instance, **kwargs):
if 'raw' in kwargs and not kwargs['raw']:
if type(instance) in MODELS:
instance.full_clean()
pre_save.connect(validate_model, dispatch_uid='validate_models')

Django - how do I _not_ dispatch a signal?

I wrote some smart generic counters and managers for my models (to avoid select count queries etc.). Therefore I got some heavy logic going on for post_save.
I would like to prevent handling the signal when there's no need to.
I guess the perfect interface would be:
instance.save(dispatch_signal=False)
How can I accomplish this?
Update
More information about what I'm doing, if anyone's interested:
Generic counters are stored in a separate table
Every time Django paginates an object list, it calls overriden count() method of my custom manager, which basically retrieves the static counter value for appropriate object class.
Signals trigger the logic of counters update, which is a bit complicated since it checks many aspects of related models (i.e. it has to generate a visibility property based on a nested category tree). I can't put this logic in Model.save() because one counter depends on many different models. I'd like to have that logic in one piece, instead of fragments spread around.
I am denormalizing some of my models, so I rewrite (duplicate) certain values across tables.
For testing purposes I run my little command-extension -- Dilla, to populate random data around.
I've noticed unwanted signals triggering, therefore I'd like them to run conditionally.
Hope it's clear enough. Excuse my language mistakes.
I found simple and easy solution:
MyModel.objects.filter(pk=instance.id).update(**data)
It is due to (https://docs.djangoproject.com/en/1.5/ref/models/querysets/#update):
Finally, realize that update() does an update at the SQL level and,
thus, does not call any save() methods on your models, nor does it
emit the pre_save or post_save signals (which are a consequence of
calling Model.save()).
You can disconnect and reconnect the signal. Try using a with: statement with this utility class:
class SignalBlocker(object):
def __init__(self, signal, receiver, **kwargs):
self.signal = signal
self.receiver = receiver
self.kwargs = kwargs
def __enter__(self, *args, **kwargs):
self.signal.disconnect(self.receiver)
def __exit__(self, *args, **kwargs):
self.signal.connect(self.receiver, **self.kwargs)
You can now use:
with SignalBlocker(post_save, my_post_save_handler):
instance.save()
A quick and dirty solution would be:
from django.db.models.signals import post_save
from somewhere_in_my_app import my_post_save_handler
post_save.disconnect(my_post_save_handler)
instance.save()
post_save.connect(my_post_save_handler)
But otherwise i strongly recommend moving your logic into the save() method of your model.
You can also call instance.save_base(raw=True) and check for the raw argument in your pre_save or post_save signal handler:
def my_post_save_handler(instance, raw, **kwargs):
if not raw:
heavy_logic()
You can add some sugar and get your perfect interface:
class MyModel:
def save(self, dispatch_signal=True, **kwargs):
self.save_base(raw=not dispatch_signal, **kwargs)
Note that save_base() is not part of the public API of Django, so it might change in a future version.

Categories