84👍
Most Django views e.g. the Django admin will not be able to handle a validation error in the save
method, so your users will get 500 errors.
You should do validation on the model form, on the model’s clean
method, or by adding validators to the model’s fields. Then call save()
only if the model form data is valid, in which case it is ‘good enough to save’.
33👍
Bastian, I explain to you my code templating, I hope that helps to you:
Since django 1.2 it is able to write validation code on model. When we work with modelforms, instance.full_clean() is called on form validation.
In each model I overwrite clean()
method with a custom function (this method is automatically called from full_clean() on modelform validation ):
from django.db import models
class Issue(models.Model):
....
def clean(self):
rules.Issue_clean(self) #<-- custom function invocation
from issues import rules
rules.connect()
Then in rules.py
file I write bussiness rules. Also I connect pre_save()
to my custom function to prevent save a model with wrong state:
from issues.models import Issue
def connect():
from django.db.models.signals import post_save, pre_save, pre_delete
#issues
pre_save.connect(Issue_pre_save, sender = Incidencia )
post_save.connect(Issue_post_save, sender = Incidencia )
pre_delete.connect(Issue_pre_delete, sender= Incidencia)
def Incidencia_clean( instance ): #<-- custom function
import datetime as dt
errors = {}
#dia i hora sempre informats
if not instance.dia_incidencia: #<-- business rules
errors.setdefault('dia_incidencia',[]).append(u'Data missing: ...')
#dia i hora sempre informats
if not instance.franja_incidencia:
errors.setdefault('franja_incidencia',[]).append(u'Falten Dades: ...')
#Només es poden posar incidències més ennlà de 7 dies
if instance.dia_incidencia < ( dt.date.today() + dt.timedelta( days = -7) ):
errors.setdefault('dia_incidencia 1',[]).append(u'''blah blah error desc)''')
#No incidències al futur.
if instance.getDate() > datetime.now():
errors.setdefault('dia_incidencia 2',[]).append(u'''Encara no pots ....''')
...
if len( errors ) > 0:
raise ValidationError(errors) #<-- raising errors
def Issue_pre_save(sender, instance, **kwargs):
instance.clean() #<-- custom function invocation
Then, modelform calls model’s clean method and my custon function check for a right state or raise a error that is handled by model form.
In order to show errors on form, you should include this on form template:
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
{{error}}
{% endfor %}
{% endif %}
The reason is that model validation erros ara binded to non_field_errors error dictionary entry.
When you save or delete a model out of a form you should remember that a error may be raised:
try:
issue.delete()
except ValidationError, e:
import itertools
errors = list( itertools.chain( *e.message_dict.values() ) )
Also, you can add errors to a form dictionary on no modelforms:
try:
#provoco els errors per mostrar-los igualment al formulari.
issue.clean()
except ValidationError, e:
form._errors = {}
for _, v in e.message_dict.items():
form._errors.setdefault(NON_FIELD_ERRORS, []).extend( v )
Remember that this code is not execute on save() method: Note that full_clean() will not be called automatically when you call your model’s save() method, nor as a result of ModelForm validation. Then, you can add errors to a form dictionary on no modelforms:
try:
#provoco els errors per mostrar-los igualment al formulari.
issue.clean()
except ValidationError, e:
form._errors = {}
for _, v in e.message_dict.items():
form._errors.setdefault(NON_FIELD_ERRORS, []).extend( v )
- [Django]-Django proxy model and ForeignKey
- [Django]-What is the difference render() and redirect() in Django?
- [Django]-Use Python standard logging in Celery
9👍
I think this is more clear way to do that for Django 1.2+
In forms it will be raised as non_field_error, in other cases, like DRF you have to check this case manual, because it will be 500 error.
class BaseModelExt(models.Model):
is_cleaned = False
def clean(self):
# check validation rules here
self.is_cleaned = True
def save(self, *args, **kwargs):
if not self.is_cleaned:
self.clean()
super().save(*args, **kwargs)
- [Django]-What is the difference between cached_property in Django vs. Python's functools?
- [Django]-Annotate a queryset with the average date difference? (django)
- [Django]-Having Django serve downloadable files
2👍
In the Django documentation they raise the ValueError
in the .save
method, it’s maybe useful for you.
- [Django]-Python + Django page redirect
- [Django]-How to dynamically compose an OR query filter in Django?
- [Django]-Django F() division – How to avoid rounding off
0👍
Edit: This answer assumes that you have a scenario that does not allow you to edit the currently implemented User
class, because you are not starting a project from scratch, the current implementation does not already use a custom User class, and you instead have to figure out how to accomplish this task by modifying Django’s built in User model behavior.
You can just stick a clean
method to your model most of the time, but you don’t have that option necessarily with the built in auth.User
model. This solution will allow you to create a clean
method for the auth.User
model in such a way that ValidationError
s will propagate to forms where the clean method is called (including admin forms).
The below example raises an error if someone attempts to create or edit an auth.User
instance to have the same email address as an existing auth.User
instance. Disclaimer, if you are exposing a registration form to new users, you do not want your validation error to call out usernames as mine does below.
from django.contrib.auth.models import User
from django.forms import ValidationError as FormValidationError
def clean_user_email(self):
instance = self
super(User, self).clean()
if instance.email:
if User.objects.filter(id=instance.id, email=instance.email).exists():
pass # email was not modified
elif User.objects.filter(email=instance.email).exists():
other_users = [*User.objects.filter(email=instance.email).values_list('username', flat=True)]
raise FormValidationError(f'At least one other user already has this email address: '
f'{", ".join(other_users)}'
, code='invalid')
# assign the above function to the User.clean method
User.add_to_class("clean", clean_user_email)
I have this at the bottom of my_app.models
but I am sure it would work as long as you stick it somewhere that is loaded before the form in question.
- [Django]-How to set environment variables in PyCharm?
- [Django]-Django-Admin: CharField as TextArea
- [Django]-Django get the static files URL in view
0👍
If you want to do validation on the model, you can use the clean()
or clean_fields
methods on the model.
EDIT:
These are called by django prior to executing save()
and Validation errors are handled in a user friendly way is incorrect, thanks for picking that up @Brad.
These clean
and clean_fields
methods are called by Django’s Form validators prior to saving a model (e.g. in django admin, in which case your validation error is handled nicely), but are not called on save()
, automatically by DRF serialisers or if you’re using custom views, in which case you have to ensure they’re called (or validate another way, e.g. by putting the logic into your serializer’s validations).
Worth highlighting: If you put custom validation logic directly into save()
and raise a ValidationError
from there, that doesn’t play nicely with forms (e.g. breaks the admin with a 500 error), which makes things a real pain if you want both django-admin and DRF to work well together… you basically have to either duplicate the validation logic in both the serializers and the clean*
methods or find some awkward way doing validation that can be shared with both.
- [Django]-Exclude fields in Django admin for users other than superuser
- [Django]-Django REST framework: non-model serializer
- [Django]-OSError: [Errno 18] Invalid cross-device link
-2👍
def clean(self):
raise ValidationError("Validation Error")
def save(self, *args, **kwargs):
if some condition:
#do something here
else:
self.full_clean()
super(ClassName, self).save(*args, **kwargs)
- [Django]-Can't compare naive and aware datetime.now() <= challenge.datetime_end
- [Django]-Getting Django admin url for an object
- [Django]-Django storages aws s3 delete file from model record