Form Validation Reversed - django

Django Version 1.10.5
I can't find this answer in the documentation or the code source. Using the example form below I am expecting my form validation on submit to run in the order of which I have defined.
So validators.RegexValidator, validators.MinLengthValidator, etc... would run in that order. However when submitting the form it would appear that the validators are running in a reversed order.
Where validate_status, validate_employee_id, etc... would run.
Is this expected?
class FormLogin(forms.Form):
# Employee ID
employee_id = forms.CharField(
label=_('employeeId'),
required=True,
widget=forms.TextInput(
attrs={
'id': 'employee_id',
'placeholder': _('employeeId'),
'class': 'form-control'
}
),
validators=[
validators.RegexValidator('^[1-9][0-9]*$', _('validatorStartsWithZero'))
validators.MinLengthValidator(1),
validators.MaxLengthValidator(20),
validate_employee_id,
validate_status
]
)
Currently I have 1 user with the ID of 1.
When I submit the form using 01, the validate_status validator is taking over and returning that a user doesn't even exist. I would have expected the validators.RegexValidator to be returned first because it has a 0 in front.
If I reversed the entire order of validators, then the validation appears to work in the order I wanted. But now code readability isn't clear that's what is actually happening.
Edit 1 Cleaned up example with more information

I wrote this little test code to reproduce this.
from django import forms
def validate_1(value):
print('RUNNING VALIDATOR 1')
raise ValidationError(
'validation error 1',
params={'value': value},
)
def validate_2(value):
print('RUNNING VALIDATOR 2')
raise ValidationError(
'validation error 2',
params={'value': value},
)
class FormLogin(forms.Form):
# Employee ID
employee_id = forms.CharField(validators=[
validate_1,
validate_2
])
Running it:
>>> f = FormLogin({'employee_id': '01'})
>>> f.is_valid()
RUNNING VALIDATOR 1
RUNNING VALIDATOR 2
False
>>> f.errors
{'employee_id': ['validation error 1', 'validation error 2']}
>>>
As you can see, the validators where executed in descending order.
I assume that one of your self written validators does not properly throw a ValidationError so that the list of errors get's messed up or that you do not properly render the errors in your template.
Normally all validators will run and every validation error will be appended to the list of errors. But they run in descending order.

Related

Why does my Django form not raise Validation Error?

I read through most SO cases regarding this issue but all are kind of specific, so I come here for help.
I have impelemented a range filter in my Django project that takes in two inputs set to a low bound and high bound value, and displays the range of data within those bounds.
What I'm trying to do, is make it so when a user inputs a higher number in the low bound than the high bound value, a ValidationError is raised for the user to see on the front end, and results are not displayed. I am a bit new to working with Django forms, but I can supply my code, and maybe someone could provide a solution
forms.py
class PlayerForm(forms.Form):
# player forms
points_high = forms.IntegerField(validators = [MinValueValidator(0)],
min_value=0,
label = 'Reviews',
required = False,
widget = forms.NumberInput(
attrs={'id': 'pointsHigh',
'name': 'pointsHigh',
'href': '#',
'value': '',
'class': "form-control"}))
points_low = forms.IntegerField(validators = [MinValueValidator(0)],
min_value=0,
required = False,
widget = forms.NumberInput(
attrs={'id': 'pointsLow',
'name': 'pointsLow',
'href': '#',
'value': '',
'class': "form-control"}))
def check_bounds(self):
"""custom validation to check if low bound value is higher than high bound value"""
data = self.cleaned_data
player_low = data['player_low']
player_high = data['player_high']
if player_low and player_high:
if player_low > player_high:
raise forms.ValidationError(_("Low bound value cannot be higher than high bound value!"))
return data
views.py
def player_points(request):
players_form = PlayerForm()
players_high = request.GET.get('players_high')
players_low = request.GET.get('players_low')
stat_kwargs = {points__lte: players_high,
points__gte: players_low}
players = Players.objects.filter(**stat_kwargs)
context = {
'players': players
'form': players_form
}
return render(request, 'nba/players.html', context)
Essentially, the goal is to take user input, check it in our check_bounds function, and return the Error if it doesn't work. How can I do that?
The form will not call your function automatically. You will need to add in to your form a place to call your function. One place you could do this is you can override the form clean() function. Something like this:
def clean(self):
cleaned_data = super().clean()
self.check_bounds()
This will call your function when your django form would normally call the clean() function.
Also, I don't really understand the purpose of the line return data in your check_bounds() function. What is the reason you put that line in there?

forms.ValidationError bug?

this is my third day in django and I'm working on my validation form and i came across this weird bug where my validation form didn't do anything so here's the code
class RegisterForm(forms.ModelForm):
email = forms.EmailField(label="E-Mail", error_messages={'required': 'Please enter your name'})
class Meta:
model = LowUser
fields =['fullname', 'email', 'password', 'bio']
widgets = {
'password' : forms.PasswordInput()
}
def clean_fullname(self):
data = self.cleaned_data.get("fullname")
if 'ab' in data:
raise forms.ValidationError('invalid')
else:
return data
if i input "ac" to the fullname it works perfectly fine it adds the input to the database. But if i input "ab" it didn't do anything it doesn't give me any errors nor add the input to my database. And I'm pretty sure my forms.ValidationError is bugging because if i change my raise forms.ValidationError('invalid') to raise NameError('Test') like this
def clean_fullname(self):
data = self.cleaned_data.get("fullname")
if 'ab' in data:
raise NameError('Test')
else:
return data
and i input "ab". It works completely fine and it gave me this page
and I'm using django 2.1.5 if you're wondering i would appreciate any help
thank you in advance
If i input "ac" to the fullname it works perfectly fine it adds the input to the database. But if i input "ab" it didn't do anything it doesn't give me any errors nor add the input to my database.
That is expected behavior, ValidationErrors are used to collect all errors.
The idea is that you raise ValidationErrors. These are all collected, and when one such error is present, form.is_valid() will return False, and the form.errors will contain a dictionary-like object with all the errors. The reason this is done is to collect problems with all fields in one single pass, such that it does not only report the first error of the form data.
Imagine that you have five fields with mistakes, and the form only reports problems with the first field. Then it takes five rounds before all fields are validated. By collecting all errors, it can show multiple ones. You can even return multiple errors on the same field.
For more information, see the raising ValidationError` section of the Django documentation.
Thanks to Willem i realized that the problem was in my views.py.
def registerForm(request):
regisform = RegisterForm()
cntxt = {'mainregisform': regisform, 'tst': ''}
if request.method == 'POST':
regisform = RegisterForm(request.POST)
if regisform.is_valid():
regisform.save()
return render(request, 'userregister.html', cntxt)
i thought that the ValidationError wasn't giving me any errors because usually there's an error message on top of my input box, but it actually did gave me an error. the problem was i define the mainregisform before the regisform got re-rendered therefore i never got error message

Validation not running in django form

I want to run field validatin on my form, as in form and field validation- using validation in practice.
My form looks like this:
from kapsule.validators import name_zero_min_length, name_max_length
class NameUpdateForm(forms.Form):
name = forms.CharField(
validators=[
name_zero_min_length,
name_max_length
]
)
My validators:
from django.core.exceptions import ValidationError
def name_zero_min_length(name_field):
# Check minimum length
if not len(name_field) > 0:
print('firing zero length')
raise ValidationError(
"My custom error message name must be at least one character"
)
def name_max_length(name_field):
# Check that the name is under the max length
MAX_LENGTH = 200
if len(name_field) > MAX_LENGTH:
print('raising')
raise ValidationError(
"My custom error message name cannot be more than {} characters".format(MAX_LENGTH)
)
My view like this:
def edit_kapsule_name(request, kapsule_pk):
kapsule = Kapsule.objects.get(pk=kapsule_pk)
form = NameUpdateForm(request.POST)
response = {}
print('pre-validation')
if form.is_valid():
print('VALID')
name = form.data.get('name')
kapsule.name = name
kapsule.save(update_fields=['name'])
else:
print('INVALID') # INVALID
print('json') # json
errors = form._errors.as_json()
print(errors) # {"name": [{"message": "This field is required.", "code": "required"}]}
My output is commented in the above code (invalid, and giving a different error that that which I expected).
Why is my custom validation not running?
This seems to match with my model validation (working), and the second reponse here
Well in the comment from your code I can see that the form is invalid and it is complaining about a required field. That might be the cause your validators are not running, according to the docs:
The clean() method on a Field subclass is responsible for running to_python(), validate(), and run_validators() in the correct order and propagating their errors. If, at any time, any of the methods raise ValidationError, the validation stops and that error is raised. This method returns the clean data, which is then inserted into the cleaned_data dictionary of the form.
On the other hand, if the field is required, the validation not len(name_field) > 0 has no much sense.
Try calling your validators as part of the clean_name method in your form.

Check form errors in Django testing

I want to test field errors using assertFormError. How can I test it?
In forms.py
password = forms.RegexField(max_length=254,
error_messages={'required': _('This is required.')
'invalid': _('It is invalid)}
)
In tests.py
form = UserForm(data=data)
self.assertContains(form['password'].errors, 'It is invalid')
Here is one way you can test it. Note that you need to call the is_valid function:
self.assertFalse(form.is_valid())
self.assertEqual(form.errors['password'][0], 'It is invalid')
first you need to call is_valid method and then chceck if errors list has specific error like:
self.assertFalse(form.is_valid())
self.assertIn('password', form.errors.keys())
self.assertIn('It is invalid', form.errors['password'])

Django custom validation of multiple fields

for which I want to validate a number of fields in a custom clean method.
I have this so far:
class ProjectInfoForm(forms.Form):
module = forms.ModelChoiceField(
queryset=Module.objects.all(),
)
piece = forms.CharField(
widget=forms.Select(),
required=False,
)
span = forms.IntegerField(
max_value=100,
initial=48
)
max_span = forms.IntegerField(
max_value=100,
initial=0
)
def clean(self):
span = self.cleaned_data['span']
max_span = self.cleaned_data['max_span']
piece = self.cleaned_data.['piece']
# validate piece
try:
Piece.objects.get(pk=m)
except Piece.DoesNotExist:
raise forms.ValidationError(
'Illegal Piece selected!'
)
self._errors["piece"] = "Please enter a valid model"
# validate spans
if span > max_span:
raise forms.ValidationError(
'Span must be less than or equal to Maximum Span'
)
self._errors["span"] = "Please enter a valid span"
return self.cleaned_data
However, this only gives me one of the messages if both clauses invalidate. How can I get all the invalid messages. Also I do not get the field-specific messages - how do I include a message to be displayed for the specific field?
Any help much appreciated.
Store the errors and don't raise them until the end of the method:
def clean(self):
span = self.cleaned_data['span']
max_span = self.cleaned_data['max_span']
piece = self.cleaned_data.['piece']
error_messages = []
# validate piece
try:
Piece.objects.get(pk=m)
except Piece.DoesNotExist:
error_messages.append('Illegal Piece selected')
self._errors["piece"] = "Please enter a valid model"
# validate spans
if span > max_span:
error_messages.append('Span must be less than or equal to Maximum Span')
self._errors["span"] = "Please enter a valid span"
if len(error_messages):
raise forms.ValidationError(' & '.join(error_messages))
return self.cleaned_data
You should write a custom clean_FIELDNAME method in this case. That way, field centric validation errors can later be displayed as such when using {{form.errors}} in your template. The clean method o.t.h. is for validating logic that spans more than one field. Take a look through the link I posted above, everything you need to know about validating django forms is in there.
It happens because you are using raise.
Try replace it by these two line in your code:
del self.cleaned_data['piece']
and
del self.cleaned_data['span']
It appears this has changed in later versions of Django (this seems to work in 2.1 and later):
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject and "help" not in subject:
msg = "Must put 'help' in subject when cc'ing yourself."
self.add_error('cc_myself', msg)
self.add_error('subject', msg)
https://docs.djangoproject.com/en/dev/ref/forms/validation/#raising-multiple-errors has more details.