django form - raising specific field validation error from clean() - django

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.

Related

Django Emailfield fixed suffix

In my Contact model I use an EmailField to assure I'm saving a valid email address.
In my form however, I only want the user to be able to change the part before the #, the domain part is not user defined. (e.g. every email needs to end with #gmail.com)
I have looked at a lot of questions about modifying Django form field values before and after validation and display phase, but a lot of the answers are kind of 'hacky' or seem to much of a workaround for my case.
Is there some support of kind of a display filter, which manages a fixed prefix/suffix of a Django form field, without having to edit POST values or needing to suppress certain validations?
Use the clean method in your form to validate the email. It'll be something like this
def clean(self):
"""Check if valid email for institution."""
super().clean()
cleaned_data = self.cleaned_data
if not '#gmail.com' in cleaned_data['email']
msg = forms.ValidationError("Your email must be #gmail")
self.add_error('email', msg)

KeyError when setting field specific error on def clean()

I've got a multiple choice field in my form.
paymentoption = forms.MultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple)
def __init__(self, *args, **kwargs):
super(BasisOfPricingForm, self).__init__(*args, **kwargs)
self.fields['paymentoption'].choices = [(t.id, t.title) for t in PaymentOption.objects.all()]
def clean(self):
cleaned_data = super(BasisOfPricingForm, self).clean()
paymentoption = cleaned_data.get('paymentoption')
if paymethod1 == 3 and len(paymentoption) == 0:
self.add_error('paymentoption', 'You must select at lease one Offline Payment Option if users must pay in person or over the phone.')
When the conditions of the error are met, I get a "KeyError" on field ''paymentoption'.
I will give you a couple of hints:
First, most of the time you don't have (and don't want to mess with the form's initializer)
Instead of initializing the values of your paymentoption choices in the form's __init__ is better to create a CustomMultipleChoiceFiled for that matter.
For example:
class PaymentOptions(MultipleChoiceField):
def __init__(self, choices=None, **kwargs):
super(PaymentOptions, self).__init__(choices=choices, **kwargs)
if choices is None:
self.choices = # ...
then you can use it without messing around with the Form's __init__.
Second, do not reimplement clean if not absolutely necessary you can add a method called clean_paymentoption and perform there all the necessary validation.
You can read about this in the documentation:
The clean_<fieldname>() method is called on a form subclass – where <fieldname> is replaced with the name of the form field attribute. This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is. This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
For example, if you wanted to validate that the contents of a CharField called serialnumber was unique, clean_serialnumber() would be the right place to do this. You don’t need a specific field (it’s just a CharField), but you want a formfield-specific piece of validation and, possibly, cleaning/normalizing the data.
The return value of this method replaces the existing value in cleaned_data, so it must be the field’s value from cleaned_data (even if this method didn’t change it) or a new cleaned value.
In your case it could be:
def clean_paymentoption(self):
paymentoption = self.cleaned_data.get('paymentoption', None)
if paymethod1 == 3 and len(paymentoption) == 0:
self.add_error('paymentoption', 'You must select at lease one Offline Payment Option if users must pay in person or over the phone.')
And again you avoid another super call, and super calls are a good thing to avoid when you start to get errors hard to track like this you just posted here.

Enabling multiple versions of Django's default form validation errors

A Django form is getting two types of users: user A and user B. These users fill out a solitary text field and press the submit button.
In case the users leave the text field empty and press submit, Django's in-built required validation error fires.
My requirement is to have two different required validation errors. User A would see the regular required validation error, whereas user B would see the different version.
I'm unable to implement this. I passed a special argument to the __init__ method of the form class via self.user_type = kwargs.pop('user_type',None). However, I can't seem to use that to override the required validation error.
Why? Because here's how the required validation error is currently defined:
class TextForm(forms.Form):
text = forms.CharField(error_messages={'required':'Please write something'})
I don't know how to access self.user_type available within error_messages in order to pull off what I want.
Can a more experienced developer chime in? Thanks in advance and let me know in case you need more information.
I've found that one can actually access the error_messages dictionary in the __init__ method via: self.fields['username'].error_messages. One can then use special arguments to differentiate the validation error along various cases, e.g.:
def __init__(self, *args, **kwargs):
self.user_type = kwargs.pop('user_type',None)
super(TextForm, self).__init__(*args, **kwargs)
self.fields['text'].error_messages = {'required': custom_validation_err_string(utype=self.user_type)}
custom_validation_err_string() simply returns the desired string given the value of user_type. This is quite a simple solution and scales well.

Django form field validation - How to tell if operation is insert or update?

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.

Django ModelAdmin perform extra operations

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