Is there a better, more "Django-istic", way to put Django form error messages in a particular order than the technique shown below? I have a form with ten fields in it. If the user doesn't fill out a required field or if they enter invalid data, I iterate through the form's error fields which I've put in a custom error list by displaying a red "x" and the error message(s) at the top and a red "x" next to the invalid field(s) below:
{# template.html #}
<form method="post" action=".">
{% csrf_token %}
{# Non-field errors removed for clarity #}
{# Iterate through error messages in custom ordered error list #}
{% for error_message in ordered_error_list %}
<img src='/static/img/red_x.png' alt='Error'></img>
{{ error_message }}<br>
{% endfor %}
<br>
{% for field in form.visible_fields %}
<div>
{{ field.label_tag }}<br />
{{ field }}
{% if field.errors %}
<img src='/static/img/red_x.png' alt='Error'></img>
{% endif %}
</div>
{% endfor %}
{# Hidden fields removed for clarity #}
<p><input type="submit" value="Continue" /></p>
</form>
Here's my view and helper function:
# views.py
def create_member_profile(request):
if request.method == "POST":
form = CreateMemberProfileForm(request.POST)
if form.is_valid():
# Process form data and redirect to next page...
return HttpResponseRedirect(reverse('review-member-profile'))
# Before I re-POST the form and display the errors, I'll put the errors in order
else:
ordered_error_list = put_member_profile_errors_in_order(form)
else:
form = CreateMemberProfileForm()
return render_to_response(template, locals(), context_instance=RequestContext(request))
def put_member_profile_errors_in_order(form):
errors_in_order = [] # List
for error in form['field_1'].errors:
errors_in_order.append(error)
for error in form['field_2'].errors:
errors_in_order.append(error)
for error in form['field_3'].errors:
errors_in_order.append(error)
# ...
for error in form['field_10'].errors:
errors_in_order.append(error)
return errors_in_order
The reason this all seems necessary is that form.errors is a dictionary and Python dictionaries, by definition, are unordered. However, as I said, I want any error messages at the top to be displayed in the same order as the form fields they refer to. I couldn't find any form.errors attributes that would allow me to re-order the form errors. I don't like the "for error in form[]" blocks but they seem to be required if I want to strip the HTML tag from the beginning of each error. Also note that I'm not checking for errors by putting each "for" loop inside an "if form['field'].errors" block because omitting this doesn't make a difference and I want this code to run as fast as possible.
Is there a better way to do this? Thanks!
You could do something like this. Same idea s you are using (two for loops).
<!-- near the top -->
<tr>
{% for field in form %}
<td>{{ field.label_tag }}: {{ field.errors }}</td>
{% endfor %}
</tr>
<!-- your form -->
{% for field in form %}
<tr>
<td>{{ field.label_tag }}:</td>
<td>
{{ field }}
{{ field.help_text }}
</td>
</tr>
{% endfor %}
Format this as you wish, but I think that kind of gives you an idea. You can also customize the error messages in your form clean:
from django import forms
YourForm(forms.Form):
....
def field_clean(self): # or use clean(self) for multiple fields
....
raise forms.ValidationError(u'Your custom error')
....
More on form field validation from Django docs.
Related
I've been doing some custom forms with django but I don't get how to access attributes that a specific form field has attached via the forms.py.
def putErrorInTitle (cls):
init = cls.__init__
def __init__ (self, *args, **kwargs):
init(self, *args, **kwargs)
if self.errors:
for field_error in self.errors:
self.fields[field_error].widget.attrs['title'] = self.errors[field_error][0]
self.fields[field_error].widget.attrs['class'] = "help_text error_field"
cls.__init__ = __init__
return cls
That's how I attached the attibutes to the field.
<dl class="clearfix two">
<dd>
<label for="id_diagnosis">Diagnostico:</label>
<select class="{{form.id_diagnosis.class}}" id="id_equipment_activity-{{ forloop.counter0 }}-id_diagnosis" name="equipment_activity-{{ forloop.counter0 }}-id_diagnosis">
{% for x,y in form.fields.id_diagnosis.choices %}
<option value="{{ x }}" {% ifequal form.id_diagnosis.data|floatformat x|floatformat %}selected="selected"{% endifequal %}>{{ y }}</option>
{% endfor %}
<option value="1000" {% ifequal form.id_diagnosis.data|floatformat '1000'|floatformat %}selected="selected"{% endifequal %}>Otro</option>
</select>
</dd>
<dd class="vertical_center" id="optional_diagnosis"><label for="optional_diagnosis">Diagnostico opcional:</label>{{ form.optional_diagnosis }}</dd>
</dl>
I've been trying to access its attributes:
class="{{form.id_diagnosis.class}}", class="{{form.id_diagnosis.widget.class}}"
And I don't seem to find clear documentation about what's accessible and what's not. Really I would rather have old fashion documentation than django "friendly" one
In other cases it can can be useful to set and get field attributes.
Setting in form's init function:
self.fields['some_field'].widget.attrs['readonly'] = True
... and accessing it in a template:
{{ form.some_field.field.widget.attrs.readonly }}
It looks like you just want to display form errors for each field.
After the form is cleaned or validated in the view, the fields should contain
the error messages. So that you can display them in the template like so:
<form action='.' method='post'>
...
<div class='a-field'>
{{ form.field_1.errors|join:", " }}
{{ form.field_1.label_tag }}
{{ form.field_1 }}
</div>
...
</form>
If however you really want to display the form field attributes then you
can try something like:
{{ form.field_1.field.widget.attrs.maxlength }}
The above answers are correct, however, I'd like to add a note for those who are accessing form fields in a loop.
If you're doing this in a loop like this
{% for field in form %}
{{ field.field.widget.attrs.placeholder }} # field.field is the key here
{% endfor %}
I have something like this:
forms.py:
AVATAR_CHOICES = (('1','option1'), ('2','option2'), ('3','option3'),
('4','option4'))
class RegistrationForm(forms.Form):
avatar = ChoiceField(widget=RadioSelect, choices=AVATAR_CHOICES)
I'm passing form from my view:
views.py
form = RegistrationForm()
return render_to_response('registration/register.html', {'form': form},
context_instance=RequestContext(request))
into a template like this:
registration.html:
{% for radio in form.avatar %}
<label class="radio">{{ radio.tag }}</label>
{% endfor %}
but I'd like to include images in front of these option fields as a way to let the users choose an avatar. However, I don't know how to access keys or values from the tuple AVATAR_CHOICES. The main idea is to be able to do something like this:
{% for radio in form.avatar %}
<label class="radio">{{ radio.tag }}</label>
<img src="{{ STATIC_URL }}img/avatars/{{ radio.tag.value }}.jpg"/>
{% endfor %}
How can I do this?
Thanks in advance.
You could try
{{ radio.choice_value }}{# value of the choice of the current input #}
{{ radio.choice_label }}{# label of the choice of the current input #}
{{ radio.choice_index }}{# 0-based index #}
Only choice_label gets documented, but you could safely use it so far.
I have a search page where user can submit queries that try to match objects in a database. When the user has submitted the form and no results have been found for his/her query, I want to give the message "No results found for this query". The problem I'm having now is that the message is shown even when the user has not submitted the form yet.
This is what my code looks like:
Review Template
<div> What do you want to review? </div>
<form action="/newreview/" method="get">
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
<input type="submit" value="Submit" name="submit" />
{% if results %}
{{ results.id }}
{% else %}
<li> There is no results for this search </li>
{% endif %}
</form>
And I do the following for the View:
def newreview(request):
if 'submit' in request.GET: # If the form has been submitted...
form = LookforPlace(request.GET) # A form bound to the GET data
if form.is_valid(): # All validation rules pass
name = form.cleaned_data['name']
city = form.cleaned_data['city']
try:
results = Place.objects.get(name__icontains=name)
except Place.DoesNotExist:
results = None
else:
results = []
form = LookforPlace() # An unbound form
return render_to_response('newreview.html', {
'form': form, 'results': results,
})
I thought that by doing the conditional on the results list, I could check whether the form has been submitted or not (empty list from the view).
Right now this code is giving me "There is no results for this search" even if there has not been any submission. Any thoughts?
Why not put a flag in your context to determine whether or not it has been submitted?
if 'submit' in request.GET:
submitted = True
...
else:
submitted = False
...
{% if submitted %}
{% if results %}
{{ results.id }}
{% else %}
<li> There are no results for this search </li>
{% endif %}
{% endif %}
now im learning to validate form, "all" is working, im showing the erros of empty fields, but i have 2 questions:
how ill show the value in the filled fields when there are errors in another fields?, like <input ... value= {{ value }} > the problem is that my fields are not html forms fields.
how ill show the error exactly over the empty fields?
how i have this:
form.py
class NuevaDiligenciaForm(forms.Form):
titulo = forms.CharField(max_length=70)
tipo = forms.ChoiceField(choices=TIPO)
vias= forms.TypedChoiceField(widget=forms.RadioSelect(), choices=CHOICES)
view.py
def add(request):
form = NuevaDiligenciaForm()
errors =[]
if request.method =='POST':
if not request.POST.get('titulo',''):
errors.append('Titulo es requerido')
if not request.POST.get('vias',''):
errors.append('Vias es requerido')
#if not errors:
return render_to_response('account/add.html', { 'formulario':form ,'errors':errors},context_instance = RequestContext(request))
template.html
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% if message %}
<p style="color: red;">{{ message }}</p>
{% endif %}
<form action='.' method='POST' class="nueva-diligencia">
{{ formulario.as_p }}
<input type="submit" value="Continuar">
</form>
Thanks again :)
You form code looks fine here but your view needs to change to this:
def add(request):
if request.method =='POST':
form = NuevaDiligenciaForm(request.POST)
if form.is_valid():
clean_data = form.cleaned_data
# Now do something with the cleaned data...
else:
form = NuevaDiligenciaForm()
return render_to_response('account/add.html', { 'formulario': form }
and your template should look like this:
{% if message %}
<p style="color: red;">{{ message }}</p>
{% endif %}
<form action='.' method='POST' class="nueva-diligencia">
{{ formulario.as_p }}
<input type="submit" value="Continuar">
</form>
Now what happens is that if there is bad data from the POST, form.is_valid() will fail and the view will return the validated form, which will include errors next to fields that have them. Django takes care of all the error handling for you here! Try it out and let me know if it works as you expect it to.
This is a pretty good resource if you'd like to see how/why this simplified version actually works better: http://www.djangobook.com/en/2.0/chapter07/
I have an inline formset for a model, which has a unique_together constraint. And so, when I input data, which doesn't fulfill this constraint, it displays:
__all__Please correct the duplicate values below.
The code, which does this is:
{% for error in formset.errors %}
{{ error }}<br/>
{% endfor %}
I don't much like the __all__ at the beginning of the error and it is quite clearly the dictionary key, so I tried:
{% for key, error in formset.errors %}
{{ key }}: {{ error }}<br/>
{% endfor %}
But then all I get is:
__all__:
{{ error }} won't display at all. So what's going on here? And how do I display the error correctly?
I think the problem here is that formset.errors is a list of dictionaries, not a single dictionary.
From the Django docs page on formsets:
>>> formset.errors
[{}, {'pub_date': [u'This field is required.']}]
See if something like this fixes the problem: (Updated based on the askers comments)
{% for dict in formset.errors %}
{% for error in dict.values %}
{{ error }}
{% endfor %}
{% endfor %}
If that fails, I'd try using manage.py shell, and try to reproduce your situation in the python shell... that way it will be easy to inspect the various values and figure out what you need to do.
The for loops are unnecessary, these errors should be correctly displayed with the following:
{{ formset.non_form_errors }}
Here is a clarification for anyone encountering similar issues of errors not being rendered in template:
If you have and error regarding the formset as a whole, use:
{{ formset.non_form_errors }}
this basically returns errors in __all__ entry from formset.errors. It is documented as:
"""
Returns an ErrorList of errors that aren't associated with a particular
form -- i.e., from formset.clean(). Returns an empty ErrorList if there
are none.
"""
However if you are rendering forms from formset and some errors are not being renderd, you are probably missing:
{% for form in formset.forms %}
{# ... #}
{{ form.non_field_errors }}
{% endfor %}
this returns errors in __all__ entry from form.errors. Those are, analogous to the non_form_errors, the errors that aren't associated with a particular field, but rather with the field relations. For example if you had a form with fields From and To, and you validate if From value is smaller then To value, the tag {{ form.non_field_errors }} could render the following error:
'The From value must be smaller than the To value'
Django 1.6 formsets have a new method, BaseFormSet.total_error_count. Using this in a template conditional ensures you only will output errors and markup if at least one error exists in the formset.
{% if formset.total_error_count %}
<ul class="errorList">
{% for dict in formset.errors %}
{% for error in dict.values %}
<li>{{ error }}</li>
{% endfor %}
{% endfor %}
</ul>
{% endif %}
See the Django docs page for v1.6+.