Validation not running in django form - django

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.

Related

How to extract Django Form errors message without the HTML tags

I need to extract the messages and field.
For Example, I have this django form error result
<ul class="errorlist">
<li>__all__
<ul class="errorlist nonfield">
<li>Pointofsale with this Official receipt and Company already exists.</li>
</ul>
</li>
</ul>
from the output of this code
def post_sale(request):
sale_form = request["data"]
if sale_form.is_valid():
save_form.save()
else:
print save_form.errors
But what i need to achieve is to get the message without the tags, so i could just return those message in plain string/text.
def post_sale(request):
sale_form = request["data"]
if sale_form.is_valid():
save_form.save()
else:
# This is just pseudo code
for field in save_form.errors:
field = str(field["field"})
message = str(field["error_message"])
print "Sale Error Detail"
print field
print message
error_message = { 'field':field,'message':message }
error_messages.append(error_message )
The output would be:
Sale Error Detail
(the field where form error exists)
Pointofsale with this Official receipt and Company already exists.
Explored Questions and Documentations
displaying django form error messages instead of just the field name
Getting a list of errors in a Django form
django form errors. get the error without any html tags
How do I display the Django '__all__' form errors in the template?
https://docs.djangoproject.com/en/1.10/ref/forms/api/
https://docs.djangoproject.com/en/1.10/topics/forms/
Thanks, please tell if something is amiss or something needs clarification so i could fix it.
The errors property of a bound form will contain all errors raised by that form, as a dictionary. The key is a field or other special values (such as __all__), and the value is a list of one or more errors.
Here is a simple example on how this works:
>>> from django import forms
>>> class MyForm(forms.Form):
... name = forms.CharField()
... email = forms.EmailField()
...
>>> f = MyForm() # Note, this is an unbound form
>>> f.is_valid()
False
>>> f.errors # No errors
{}
>>> f = MyForm({}) # Now, the form is bound (to an empty dictionary)
>>> f.is_valid()
False
>>> f.errors # dictionary of errors
{'name': [u'This field is required.'], 'email': [u'This field is required.']}
In your view, depending on what you want you can just return the value of form.errors, or parse it to whatever structure your need.
for field, errors in form.errors.items():
print('Field: {} Errors: {}'.format(field, ','.join(errors))
For the specific error you have mentioned, it is a custom error raised as a result of overriding the clean() method - which is why it is listed under the special identifier __all__ and not under a specific field.
This is mentioned in the forms reference, under validation:
Note that any errors raised by your Form.clean() override will not be
associated with any field in particular. They go into a special
“field” (called __all__), which you can access via the
non_field_errors() method if you need to. If you want to attach errors
to a specific field in the form, you need to call add_error().

Django Rest framwork validation: Mark field as error

I started using django-rest-framework for my application and I have a question regarding the serializer validaton.
In the docs I found this example for object validation:
from rest_framework import serializers
class EventSerializer(serializers.Serializer):
description = serializers.CharField(max_length=100)
start = serializers.DateTimeField()
finish = serializers.DateTimeField()
def validate(self, attrs):
"""
Check that the start is before the stop.
"""
if attrs['start'] > attrs['finish']:
raise serializers.ValidationError("finish must occur after start")
return attrs
This returns the following:
{"non_field_errors": ["finish must occur after start"]}
My question is, how can I find out which fields are responsible for the failed validation?
In this case attrs['start'] and attrs['finish'].
In the end I want something like this:
{"non_field_errors": ["finish must occur after start"],
"start": ["finish must occur after start"],
"finish": ["finish must occur after start"]}
So that I can mark the responsible form fields.
I hope the question is clear. Thanks!
When you are creating the ValidationError, you can pass in a dictionary instead of a string. The dictionary expects that the key is the field name, and the value is the error string.
def validate(self, attrs):
"""
Check that the start is before the stop.
"""
if attrs['start'] > attrs['finish']:
raise serializers.ValidationError({"finish": "finish must occur after start"})
return attrs
This should produce output along the lines of:
{"start": ["finish must occur after start"],
"finish": ["finish must occur after start"]}
Which sounds like what you are looking for.

Django ValidationError

According to https://docs.djangoproject.com/en/dev/ref/forms/validation/
# Good
ValidationError(
_('Invalid value: %(value)s'),
params={'value': '42'},
)
# Bad
ValidationError(_('Invalid value: %s') % value)
The docs doesnt really explain why it is bad / good. Can someone give a concrete example?
Furthermore, when I inspect form.errors, I get something like 'Invalid: %(value)s'. How do I get the params from the Validation error and interpolate them into the error msg?
Edited
So is this considered good?
ValidationError(
_('Invalid value: %(value)s') % {'value': '42'},
)
I think the real question is: why pass the variables separately via the params argument? Why not interpolate directly into the error msg (ignore named or positional interpolation for now)???
Edited
Ok, From the source # https://github.com/django/django/blob/stable/1.5.x/django/forms/forms.py
I don't think there is any way to retrieve ValidationError's params since the Form does not even save the ValidationError object itself. See code below.
class ValidationError(Exception):
"""An error while validating data."""
def __init__(self, message, code=None, params=None):
import operator
from django.utils.encoding import force_text
"""
ValidationError can be passed any object that can be printed (usually
a string), a list of objects or a dictionary.
"""
if isinstance(message, dict):
self.message_dict = message
# Reduce each list of messages into a single list.
message = reduce(operator.add, message.values())
if isinstance(message, list):
self.messages = [force_text(msg) for msg in message]
else:
self.code = code
self.params = params
message = force_text(message)
self.messages = [message]
class Form:
....
def _clean_fields(...):
....
except ValidationError as e:
self._errors[name] = self.error_class(e.messages) # Save messages ONLY
if name in self.cleaned_data:
del self.cleaned_data[name]
If you have multiple parameters, they might appear in a different order when you translate the error message.
Named arguments allow you to change the order in which the arguments appear, without changing params. With a tuple of arguments, the order is fixed.
Note that you are linking to the development version of the Django docs. The validation error is not interpolating the parameters because you are using Django 1.5 or earlier. If you try your code in the 1.6 beta, then the parameters are interpolated into the error message.
ValidationError is caught by the form validation routine and though it can just show a message, it's better to save the possibility of getting params of error; eg. field name, value that caused error and so on. It's stated just before the example you've provided.
In order to make error messages flexible and easy to override

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.

Django - Change the max_length error message on a CharField TextInput

I am trying to customize the error message displayed when a user inputs text longer than the max length for a CharField in my model. The model defines the field as follows:
name = models.CharField(max_length=200)
I have a ModelForm for this model that defines the form field as follows:
name = forms.CharField(widget=forms.TextInput(attrs={'class':"span8", 'placeholder':'e.g. How do I setup a wi-fi network? How to sync my iPhone with iCloud?'}),
error_messages={'required': 'Please enter a question.', 'max_length': 'Your question is too long.'})
This works fine for the 'required' error message, but the 'max_length' error message still uses Django's default message here. Am I doing something wrong or should this text be customized in some other way?
The 'max_length' is checked by 'django.core.validators.MaxLengthValidator', which has hard-coded error message. You could replace it w/ your own version by:
# after the name or ModelForm definition
name.validators[-1].message = 'Your question is too long.'
I'm not sure from which version it is working, but now you can do this:
class YouForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['you_field'].error_messages = {'max_length': 'Ensure this value has at most %(limit_value)d character (it has %(show_value)d).'}
You can create subclass of 'django.core.validators.MaxLengthValidator'
from django.core.validators import MaxValueValidator
class MyValidator(MaxValueValidator):
message = 'Your question is too long.'