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
Related
I have an integer field in a form that must be in the range 100 to 1000. However, if a value other than 100, 200, 381 (don't ask), 525 or 1000 is entered, it is quite probable that this is erroneous.
What I'd like to do is what I'm calling "soft validation" -- I don't know if there is any standard terminology. The first time, the form would raise errors and then "Confirm?" boolean field(s) would be added, default false. If it comes back again with the confirm field(s) true, then unlikely data will be accepted.
The answer Warnings (or even info messages) instead of only errors in Django shows how to accomplish this with custom code added field-by-field and form-by-form -- but I'd rather not do it this way. Does anybody know whether this has already been done in open-source code in a generalized, re-usable way? In my dreams, I would raise ValidationWarning ... (which doesn't exist) and the rest would look after itself.
I don't know what to search for with Google. I can't find anything obvious at https://djangopackages.org/
Here you find a minimal working example similar to the one given in the link in the comment. You only have to be carefully to not include the soft validation in the clean_MagicNumber(self) function - this is prone to race conditions because the clean_data attributes are only set.
Instead use the overwritten clean(self) or validate_unique(self) function.
from django.core.exceptions import ValidationError
from django.db import models
from django.contrib import admin
from django import forms
from django.db import models
class Autor(models.Model):
Name = models.CharField(max_length=64)
MagicNumber = models.IntegerField()
class AutorAdminForm(forms.ModelForm):
ConfirmMagicNumber = forms.BooleanField(label="I confirm it is the right magic number.",
required=False,
widget=forms.HiddenInput)
def _field_is_active(self, field):
if self._meta.exclude is not None and field in self._meta.exclude:
return False
if self._meta.fields is not None and field in self._meta.fields:
return True
return False
def clean_MagicNumber(self):
if self.cleaned_data['MagicNumber'] < 100 or self.cleaned_data['MagicNumber'] > 1000:
raise ValidationError("Magic number outside of allowed range between 100 to 1000.")
return self.cleaned_data['MagicNumber']
def validate_unique(self):
"""
We use validate unique because it is quite late in the validation process,
after we got the values of all fields and
actually I used it, because I was really warning for a possible duplicate
"""
super().validate_unique()
# only check if number does not have errors and is in form data
if 'MagicNumber' not in self._errors and 'MagicNumber':
# only check if confirm checkbox is given and not checked
if ('ConfirmMagicNumber' in self.cleaned_data and 'ConfirmMagicNumber' in self.fields
and self.cleaned_data['ConfirmMagicNumber'] is not True and self._field_is_active('ConfirmMagicNumber')):
if self.cleaned_data['MagicNumber'] not in (100, 200, 381, 525, 1000):
error_msg = "The magic number looks wrong. Please confirm it below."
self.add_error('MagicNumber', error_msg)
self.fields['ConfirmMagicNumber'].widget = forms.CheckboxInput()
self.fields['ConfirmMagicNumber'].required = True
#admin.register(Autor)
class AutorAdmin(admin.ModelAdmin):
form = AutorAdminForm
I have been adapting this article How to Add Custom Action Buttons to Django Admin to my particular circumstance and things are working pretty well except for this one strange error I am getting. In my forms.py I have defined class DenyForm: (Note the request parameter in form_action is not the usual API request. I have a model class Request.) What should form_action be returning?
class DenyForm(ApproveOrDenyForm):
def form_action(self, request, admin_approver ):
print "forms DenyForm Called."
justification = self.cleaned_data['justification']
request.admin_deny(admin_approver=admin_approver,justification=justification)
return [request]
#return request.admin_deny(admin_approver=admin_approver,justification=justification)
This results in a message of
“‘Please correct the errors below.’ need more than 1 value to unpack”.
I have tried different endings to my form_action method. If my last line is:
return request
I get “‘Please correct the errors below.’ ‘Request’ object is not iterable”.
If the last line is:
return request.admin_deny(admin_approver=admin_approver,justification=justification)
I get this … “‘Please correct the errors below.’ ‘NoneType’ object is not iterable”. That is because admin_deny() does not return anything. What should form_action be returning?
Update: Here is the code that call form_action():
class ApproveOrDenyForm(forms.Form):
justification = forms.CharField(
required=True,
widget=forms.Textarea,
)
def save(self, req, login ):
try:
user = User.objects.filter(login=login).get()
req, action = self.form_action(req, user )
except Exception as e:
error_message = str(e)
self.add_error(None, error_message)
raise
return req, action
When you call form_action you are expecting it to return an iterable of two items - req and action. So you need to ensure that form_action() returns a list or tuple of exactly those two things.
It is not clear to me from the post you linked to, or your code, what the action returned is supposed to be - maybe just a result of the action performed. You need to check that to decide whether your action method needs to return something else.
Something like this should work:
def form_action(self, request, admin_approver ):
justification = self.cleaned_data['justification']
action = request.admin_deny(admin_approver=admin_approver, justification=justification)
# action will be None if admin_deny does not return anything,
# but the code calling this function expects it, so return it anyway.
return (request, action)
Newbie question:
I need to accept a parameter in a form from a method in views.py but it gave me troubles. In the view I created a method with following snippet:
def scan_page(request):
myClient = request.user.get_profile().client
form = WirelessScanForm(client = myClient) # pass parameter to the form
and in the forms.py I defined the following form:
class WirelessScanForm(forms.ModelForm):
time = forms.DateTimeField(label="Schedule Time", widget=AdminSplitDateTime())
def __init__(self,*args,**kwargs):
myClient = kwargs.pop("client") # client is the parameter passed from views.py
super(WirelessScanForm, self).__init__(*args,**kwargs)
prob = forms.ChoiceField(label="Sniffer", choices=[ x.sniffer.plug_ip for x in Sniffer.objects.filter(client = myClient) ])
But django keeps giving me error saying: TemplateSyntaxError: Caught NameError while rendering: name 'myClient' is not defined(This error happens in the query)
I'm afraid it would be something stupid missing here, but I cannot really figure out why. Please help, thanks.
Assuming I've corrected your formatting properly, you have an indentation issue: prob is outside __init__, so doesn't have access to the local myClient variable.
However if you bring it inside the method, it still won't work, as there are two other issues: first, simply assigning a field to a variable won't set it on the form; and second, the choices attribute needs a list of 2-tuples, not just a flat list. What you need is this:
def __init__(self,*args,**kwargs):
myClient = kwargs.pop("client") # client is the parameter passed from views.py
super(WirelessScanForm, self).__init__(*args,**kwargs)
self.fields['prob'] = forms.ChoiceField(label="Sniffer", choices=[(x.plug_ip, x.MY_DESCRIPTIVE_FIELD) for x in Sniffer.objects.filter(client = myClient)])
Obviously replace MY_DESCRIPTIVE_FIELD with the actual field you want displayed in the choices.
from django.core.exceptions import FieldError
#This is a method of a class
def _order_item_list(self, item_list, order_items_by, previous_order_by):
if order_items_by == previous_order_by:
order_items_by = '-' + order_items_by
try:
result = item_list.order_by(order_items_by)
except FieldError:
result = item_list
return result, order_items_by
Now when I order by valid fields following the generated link,everything works perfect. When I edit a link and add some dummy fieldnames for ordering, it should be catched by this exception and the original list should be returned. But it is not happening, instead I always get a FieldError from django.
FieldError at ...
Cannot resolve keyword u'fgsdffds' into field. Choices are: ...
The reason the exception is not caught is because the QuerySet has not been evaluated yet.
To validate an arbitrary (user-specified) value used for a model field or order_by value, simply check to see if that model has a field by that name.
For example, say you have a model called Ticket and an arbitrary GET parameter called field_name. Here's how you might handle creating a valid QuerySet in views.py:
from django.db.models import FieldDoesNotExist
from myapp.models import Ticket
def index(request):
default_field = 'id'
field_name = request.GET.get('field_name', default_field)
try:
Ticket._meta.get_field_by_name(field_name)
except FieldDoesNotExist:
field_name = default_field
tickets = Ticket.objects.all().order_by(field_name)
return ...
This means there's a typo, or the exception happens elsewhere. Insert a debug line:
import pdb; pdb.set_trace()
before the try-except and see how the code is executed. Try PUDB or IPDB debuggers instead of the standard one. Many questions disappear when you have a debugger and can see exactly what goes wrong.
I faced the same problem and surely it was because the exception is later. In order to raise exception in try-catch block I modified the code in following manner:
try:
result = item_list.order_by(order_items_by)
**result = list(result)**
except FieldError:
result = item_list
This worked for me.
I am trying to follow the code listed on https://github.com/alex/django-ajax-validation/blob/master/ajax_validation/views.py
I have been able to understand a small chunk of it. I have added comments stating my understanding of what is happening.
I would really appreciate some assistance on questions I listed in comments next to the lines I couldn't quite follow.
def validate(request, *args, **kwargs):
# I thing it is some sort of initializations but I cannot really understand what's happening
form_class = kwargs.pop('form_class')
defaults = {
'data': request.POST
}
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
kwargs = extra_args_func(request, *args, **kwargs)
defaults.update(kwargs)
form = form_class(**defaults)
if form.is_valid(): #straightforward, if there is no error then the form is valid
data = {
'valid': True,
}
else:
# if we're dealing with a FormSet then walk over .forms to populate errors and formfields
if isinstance(form, BaseFormSet): #I cannot really understand what is BaseFromSet
errors = {}
formfields = {}
for f in form.forms: # I am guessing that this is for when there are multiple form submitted for validation
for field in f.fields.keys(): # I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
formfields[f.add_prefix(field)] = f[field]
for field, error in f.errors.iteritems():
errors[f.add_prefix(field)] = error
if form.non_form_errors():
errors['__all__'] = form.non_form_errors() # what is the '__all__'?
else:
errors = form.errors
formfields = dict([(fieldname, form[fieldname]) for fieldname in form.fields.keys()])
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'): # I am having a hard time understanding what this if statement does.
fields = request.POST.getlist('fields') + ['__all__']
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
final_errors = {} # here the author of this code totally lost me.
for key, val in errors.iteritems():
if '__all__' in key:
final_errors[key] = val
elif not isinstance(formfields[key].field, forms.FileField):
html_id = formfields[key].field.widget.attrs.get('id') or formfields[key].auto_id
html_id = formfields[key].field.widget.id_for_label(html_id)
final_errors[html_id] = val
data = {
'valid': False or not final_errors,
'errors': final_errors,
}
json_serializer = LazyEncoder() # Why does the result have to be returned in json?
return HttpResponse(json_serializer.encode(data), mimetype='application/json')
validate = require_POST(validate) # a decorator that requires a post to submit
LazyEncoder
class LazyEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
form_class = kwargs.pop('form_class')
This is simply pulling the keyword argument, form_class, that was passed in via the URL conf.
(r'^SOME/URL/$', 'ajax_validation.views.validate',
{'form_class': ContactForm}, # this keyword argument.
'contact_form_validate')
BaseFormSet is simply the formset class doing the work behind the scenes. When you don't know, search the source! grep -ri "baseformset" . It's an invaluable tool.
Take a look at at django.forms.formsets to see how formset_factory produces new "formset" classes based on the BaseFormSet, hence the factory part!
I am guessing that this is for when there are multiple form submitted for validation
Yes, that's exactly what a formset is for (dealing with multiple forms)
I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
Yes, that would be looping through the field names.
add_prefix() is for prefixing form field names with a specific form. Because a formset repeats form elements multiple times, each field needs a unique prefix, such as 0-field1, 1-field1, etc.
formfields is just an empty dictionary defined a few lines above.
what is the 'all'?
__all__ is defined at the top of django.forms.forms
NON_FIELD_ERRORS = '__all__'
It's just what non field specific errors (such as constraints across 2 fields) are stored under in the errors dictionary as opposed to errors[fieldname].
I am having a hard time understanding what this if statement does.
The author has left a note:
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'):
It's checking if you specified any specific fields to validate in your URLConf, this is not django but ajax_validation.
You can see that he's overwriting his errors dictionary based on only the fields specified, thus passing on the validation only for those fields.
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
here the author of this code totally lost me.
The author has mapped a custom errors and fields dictionary to specific field names with prefixes, (as opposed to the usual FormSet with each form having its own errors dictionary, unaware of the formset itself) which he presumably uses in the AJAX response to validate all fields.
Normally, you can iterate over a formset and go through the errors on a form by form basis, but not so if you need to validate all of them through ajax.
The line pulling html_id should be straight forward most of the time, but it's there because form widgets CAN add interesting things to the end of the ID's based on whether or not the widget is a radio select for example.
From source comments :
# RadioSelect is represented by multiple <input type="radio"> fields,
# each of which has a distinct ID. The IDs are made distinct by a "_X"
# suffix, where X is the zero-based index of the radio field. Thus,
# the label for a RadioSelect should reference the first one ('_0').
Why does the result have to be returned in json?
Because it's an ajax request and javascript easily eats json.
2- could you go through these lines of code...
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
Either return a keyword argument named 'callback' (which if passed in, is supposed to be a function that accepts request and return a dictionary), and if it wasn't, return a lambda function that only returns an empty dictionary.
I'm not sure what the specific use is for the extra context. You could use it to run arbitrary snippets of code without modifying or subclassing ajax_validation...
It might help you to run this code, and put a debugger breakpoint in somewhere so you can step through and examine the variables and methods. You can do this by simply putting this line where you want to break:
import pdb; pdb.set_trace()
and you will be dumped into the debugger in the console.