Should I use save_model to do extra operations before I save a record?
When an error occur, how can I stop the function to save a record and prompt an error on top of the ModelAdmin form?
In most cases it is better to use signals instead of overriding save.
And for the validation part you should define a ModelForm and add your validation rules there.
Form Validation is explained here.
def clean_name(self):
# do something that validates your data
cleaned_data = self.cleaned_data
name = cleaned_data.get("name")
if not name:
raise forms.ValidationError('please add your name')
return name
Related
I have a validation check on a form which depends on more than one field, but it would be good to have the validation error show the user specifically which fields are causing the issue, rather than just an error message at the top of the form. (the form has many fields so it would be clearer to show specifically where the error is).
As a work around I tried to create the same validation in each of the relevant fields clean_field() method so the user would see the error next to those fields. However I only seem to be able to access that particular field from self.cleaned_data and not any other?
Alternatively is it possible to raise a field error from the forms clean() method?
Attempt 1:
def clean_supply_months(self):
if not self.cleaned_data.get('same_address') and not self.cleaned_data.get('supply_months'):
raise forms.ValidationError('Please specify time at address if less than 3 years.')
def clean_supply_years(self):
if not self.cleaned_data.get('same_address') and not self.cleaned_data.get('supply_years'):
raise forms.ValidationError('Please specify time at address if less than 3 years.')
def clean_same_address(self):
.....
If you want to access cleaned data for more than one field, you should use the clean method instead of clean_<field> method. The add_error() method allows you to assign an error to a particular field.
For example, to add the Please specify time at address error message to the same_address field, you would do:
def clean(self):
cleaned_data = super(ContactForm, self).clean()
if not self.cleaned_data.get('same_address') and not self.cleaned_data.get('supply_months'):
self.add_error('same_address', "Please specify time at address if less than 3 years.")
return cleaned_data
See the docs on validating fields that rely on each other for more info.
I'm facing a validation problem.
I need to use form validation and model validation together, but django (1.10) doesn't seem to like this.
Here is a short version of my setup:
class MyModel(models.Model):
fk = models.ForeignKey('ap.Model')
foo = models.CharField(max_length=12)
def clean(self):
if self.fk.som_field != self.foo:
raise ValidationError("This did not validate")
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('fk',)
def view(request):
instance = MyModel(foo='bar')
form = MyModelForm(data=request.POST, instance=instance)
if form.is_valid():
# process
# redirect
# display template
So I have some model field validation in the model itself.
I need this here because it is re-used in other non-form related parts of my application.
And I have some user input validation in the form.
In this case, checking that the provided fk is valid and exists.
But when the form is validated and the user provided 'fk' is not valid, the form is rejected.
But the form also calls MyModel.full_clean add more model validation.
The problem is that calling MyModel.clean() without having any data in the field fk will raise a RelatedObjectDoesNotExist exception.
How can I do this the proper way ?
I feel that MyModel.full_clean() should not be called by the form until the form itself is valid to ensure that the model is validated with at least correct field types in it.
I could embed my MyModel.clean operations in a try/except that would catch a RealtedObjectDoesNotExist, but it has a bad code smell for me.
The fact that MyModel.fk exists should be ensured by the first form layer validation.
As you have found, the model form performs the instance validation (by calling self.instance.full_clean()`) whether or not the form is valid.
You could prevent this behaviour (I might try to override the _post_clean, but I think it's simpler to take account of the behaviour in the clean method.
Rather than catching the exception, it would be simpler to check that self.fk_id is not None before accessing self.fk.
class MyModel(models.Model):
fk = models.ForeignKey('ap.Model')
foo = models.CharField(max_length=12)
def clean(self):
if self.fk_id is not None and self.fk.som_field != self.foo:
raise ValidationError("This did not validate")
I'm trying to do this in Django:
When saving an object in the Admin I want to save also another object of a different type based on one of the fields in my fist object.
In order to do this I must check if that second object already exists and return an validation error only for the particular field in the first object if it does.
My problem is that I want the validation error to appear in the field only if the operation is insert.
How do I display a validation error for a particular admin form field based on knowing if the operation is update or insert?
P.S. I know that for a model validation this is impossible since the validator only takes the value parameter, but I think it should be possible for form validation.
This ca be done by writing a clean_[name_of_field] method in a Django Admin Form. The insert or update operation can be checked by testing self.instance.pk.
class EntityAdminForm(forms.ModelForm):
def clean_field(self):
field = self.cleaned_data['field']
insert = self.instance.pk == None
if insert:
raise forms.ValidationError('Some error message!')
else:
pass
return field
class EntityAdmin(admin.ModelAdmin):
form = EntityAdminForm
You have to use then the EntityAdmin class when registering the Entity model with the Django admin:
admin.site.register(Entity, EntityAdmin)
You can write your custom validation at the model level:
#inside your class model ...
def clean(self):
is_insert = self.pk is None
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
#do your business rules
if is_insert:
...
if __some_condition__ :
raise ValidationError('Dups.')
Create a model form for your model. In the clean method, you can set errors for specific fields.
See the docs for cleaning and validating fields that depend on each other for more information.
That is (probably) not an exact answer, but i guess it might help.
Django Admin offers you to override save method with ModelAdmin.save_model method (doc is here)
Also Django api have a get_or_create method (Doc is here). It returns two values, first is the object and second one is a boolean value that represents whether object is created or not (updated an existing record).
Let me say you have FirstObject and SecondObject
In your related admin.py file:
class FirstObjectAdmin(admin.ModelAdmin):
...
...
def save_model(self, request, obj, form, change):
s_obj, s_created = SecondObject.objects.get_or_create(..., defaults={...})
if not s_created:
# second object already exists... We will raise validation error for our first object
...
For the rest, I do not have a clear idea about how to handle it. Since you have the form object at hand, you can call form.fields{'somefield'].validate(value) and write a custom validation for admin. You will probably override clean method and try to trigger a raise ValidationError from ModelAdmin.save_model method. you can call validate and pass a value from there...
You may dig django source to see how django handles this, and try to define some custom validaton steps.
I am trying to move all business-logic-related validations to models, instead of leaving them in forms. But here I have a tricky situation, for which I like to consult with the SO community.
In my SignupForm (a model form), I have the following field-specific validation to make sure the input email does not exist already.
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email
If I were to move this validation to the models, according to the official doc, I would put it in clean() of the corresponding model, ExtendedUser. But the doc also mentions the following:
Any ValidationError exceptions raised by Model.clean() will be stored
in a special key error dictionary key, NON_FIELD_ERRORS, that is used
for errors that are tied to the entire model instead of to a specific
field
That means, with clean(), I cannot associate the errors raised from it with specific fields. I was wondering if models offer something similar to forms' clean_<fieldname>(). If not, where would you put this validation logic and why?
You could convert your clean method into a validator and include it when you declare the field.
Another option is to subclass the model field and override its clean method.
However there is no direct equivalent of defining clean_<field name> methods as you can do for forms. You can't even assign errors to individual fields, as you can do for forms
As stated in the comment I believe you should handle this validation at the modelform level. If you still feel like it would be better to do it closer to the model, and since they can't be changed, I would advise a change directly at the db level:
ALTER TABLE auth_user ADD UNIQUE (email)
Which is the poor way to add the unique=True constraint to the User model without monkey patching auth.
As requested, I think that a good way to go about customizing different forms should be done by inheriting from a base modelform. A good example of this is found in django-registration. The only difference is that instead of the parent form inheriting from forms.Form you would make it a modelForm:
class MyBaseModelForm(ModelForm):
class Meta:
model = MyModel
You could then inherit from it and make different forms from this base model:
class OtherFormWithCustomClean(MyBaseModelForm):
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email
Am I thinking about this all wrong, or am I missing something that's really obvious?
Python's style guide say's less code is better (and I don't think that's subjective... It's a fact), so consider this.
To use forms for all validation means that to write a model field subclass with custom validation, you'd have to:
Subclass models.Field
Subclass forms.Field
Add custom validation to your forms.Field subclass
Set your custom form field as the default form field for the custom model field
Always use a django model form if you want to perform validation
With all validation in the model you'd just have to:
Subclass models.Field
Add custom validation to your models.Field subclass
Now you could use your model field in an API that bypasses all use of web forms, and you'd still have validation at lowest level. If you used web forms, the validation would propagate upwards.
Is there a way to do this without having to write the Django team and wait for them to fix this error?
This sort of thing is already possible in Django's development version:
There are three steps in validating a
model, and all three are called by a
model’s full_clean() method. Most of
the time, this method will be called
automatically by a ModelForm. You should only need to
call full_clean() if you plan to
handle validation errors yourself.
See the docs.
e.g. (as part of your model class):
def clean(self):
from django.core.exceptions import ValidationError
# Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None:
raise ValidationError('Draft entries may not have a publication date.')
# Set the pub_date for published items if it hasn't been set already.
if self.status == 'published' and self.pub_date is None:
self.pub_date = datetime.datetime.now()
The ModelForm's validation process will call into your model's clean method, per this:
As part of its validation process,
ModelForm will call the clean() method
of each field on your model that has a
corresponding field on your form. If
you have excluded any model fields,
validation will not be run on those
fields. Also,
your model's clean() method will be
called before any uniqueness checks
are made. See Validating objects for
more information on the model's
clean() hook.
It is already being fixed, and will be in Django 1.2.