Handling Invalid Survey Submissions with Django

Building rigorously tested and bug-free Django applications

What would make a survey submission invalid? The only likely error case for our QuestionVoteForm is if no answer is chosen. What happens, then, if we attempt to submit a survey with missing answers? If we try it, we see that the result is not ideal:

Handling Invalid Survey Submissions with Django

There are at least two problems here. First, the placement of the error messages, above the survey questions, is confusing. It is hard to know what the first error message on the page is referring to, and the second error looks like it is associated with the first question. It would be better to move the error messages closer to where the selection is actually made, such as between the question and answer choice list.

Second, the text of the error message is not very good for this particular form. Technically the list of answer choices is a single form field, but to a general user the word field in reference to a list of choices sounds odd. We will correct both of these errors next.

Coding custom error message and placement

Changing the error message is easy, since Django provides a hook for this. To override the value of the error message issued when a required field is not supplied, we can specify the message we would like as the value for the required key in an error_messages dictionary we pass as an argument in the field declaration. Thus, this new definition for the answer field in QuestionVoteForm will change the error message to Please select an answer below:

class QuestionVoteForm(forms.Form):
answer = forms.ModelChoiceField(widget=forms.RadioSelect,
'Please select an answer below:'})

Changing the placement of the error message requires changing the template. Instead of using the as_p convenience method, we will try displaying the label for the answer field, errors for the answer field, and then the answer field itself, which displays the choices. The {% for %} block that displays the survey forms in the survey/active_survey.html template then becomes:

{% for qform in qforms %}
{{ qform.answer.label }}
{{ qform.answer.errors }}
{{ qform.answer }}
{% endfor %}

How does that work? Better than before. If we try submitting invalid forms now, we see:

Handling Invalid Survey Submissions with Django

While the error message itself is improved, and the placement is better, the exact form of the display is not ideal. By default, the errors are shown as an HTML unordered list. We could use CSS styling to remove the bullet that is appearing (as we will eventually do for the list of choices), but Django also provides an easy way to implement custom error display, so we could try that instead.

To override the error message display, we can specify an alternate error_class attribute for QuestionVoteForm, and in that class, implement a __unicode__ method that returns the error messages with our desired formatting. An initial implementation of this change to QuestionVoteForm and the new class might be:

class QuestionVoteForm(forms.Form):
answer = forms.ModelChoiceField(widget=forms.RadioSelect,
'Please select an answer below:'})

def __init__(self, question, *args, **kwargs):
super(QuestionVoteForm, self).__init__(*args, **kwargs)
self.fields['answer'].queryset = question.answer_set.all()
self.fields['answer'].label = question.question
self.error_class = PlainErrorList

from django.forms.util import ErrorList
class PlainErrorList(ErrorList):
def __unicode__(self):
return u'%s' % ' '.join([e for e in sefl])

The only change to QuestionVoteForm is the addition of setting its error_class attribute to PlainErrorList in its __init__ method. The PlainErrorList class is based on the django.form.util.ErrorList class and simply overrides the __unicode__ method to return the errors as a string with no special HTML formatting. The implementation here makes use of the fact that the base ErrorList class inherits from list, so iterating over the instance itself returns the individual errors in turn. These are then joined together with spaces in between, and the whole string is returned.

Note that we're only expecting there to ever be one error here, but just in case we are wrong in that assumption, it is safest to code for multiple errors existing. Although our assumption may never be wrong in this case, it's possible we might decide to re-use this custom error class in other situations where the single possible error expectation doesn't hold. If we code to our assumption and simply return the first error in the list, this may result in confusing error displays in some situations where there are multiple errors, since we will have prevented reporting all but the first error. If and when we get to that point, we may also find that formatting a list of errors with just spaces intervening is not a good presentation, but we can deal with that later. First, we'd like to simply verify that our customization of the error list display is used.

Debug page: Another TemplateSyntaxError

What happens if we try submitting an invalid survey now that we have our custom error class specified? An attempt to submit an invalid survey now returns:

Handling Invalid Survey Submissions with Django

Oops, we have made another error. The exception value displayed on the second line makes it pretty clear that we've mistyped self as sefl, and since the code changes we just made only affected five lines in total, we don't have far to look in order to find the typo. But let's take a closer look at this page, since it looks a little different than the other TemplateSyntaxError we encountered.

What is different about this page compared to the other TemplateSyntaxError? Actually, there is nothing structurally different; it contains all the same sections with the same contents. The notable difference is that the exception value is not a single line, but is rather a multi-line message containing an Original Traceback. What is that? If we take a look at the traceback section of the debug page, we see it is rather long, repetitive, and uninformative. The end portion, which is usually the most interesting part of a traceback, is:

Handling Invalid Survey Submissions with Django

Every line of code cited in that traceback is Django code, not our application code. Yet, we can be pretty sure the problem here was not caused by the Django template processing code, but rather by the change we just made to QuestionVoteForm. What's going on?

What has happened here is that an exception was raised during the rendering of a template. Exceptions during rendering are caught and turned into TemplateSyntaxErrors. The bulk of the stack trace for the exception will likely not be interesting or helpful in terms of solving the problem. What will be more informative is the stack trace from the original exception, before it was caught and turned into a TemplateSyntaxError. This stack trace is made available as the Original Traceback portion of the exception value for the TemplateSyntaxError which is ultimately raised.

A nice aspect of this behavior is that the significant part of what is likely a very long traceback is highlighted at the top of the debug page. An unfortunate aspect is that the significant part of the traceback is no longer available in the traceback section itself, thus the special features of the traceback section of the debug page are not available for it. It is not possible to expand the context around the lines identified in the original traceback, nor to see the local variables at each level of the original traceback. These limitations will not cause any difficulty in solving this particular problem, but can be annoying for more obscure errors.

Note that Python 2.6 introduced a change to the base Exception class that causes the Original Traceback information mentioned here to be omitted in the display of the TemplateSyntaxError exception value. Thus, if you are using Python 2.6 and Django 1.1.1, you will not see the Original Traceback included on the debug page. This will likely be corrected in newer versions of Django, since losing the information in the Original Traceback makes it quite hard to debug the error. The fix for this problem may also address some of the annoyances previously noted, related to TemplateSyntaxErrors wrapping other exceptions.

Fixing the second TemplateSyntaxError

Fixing this second TemplateSyntaxError is straightforward: simply correct the sefl typo on the line noted in the original traceback. When we do that and again try to submit an invalid survey, we see in response:

Handling Invalid Survey Submissions with Django

That is not a debug page, so that is good. Furthermore, the error messages are no longer appearing as HTML unordered lists, which was our goal for this change, so that is good. Their exact placement may not quite be exactly what we want, and we may want to add some CSS styling so that they stand out more prominently, but for now they will do.


In this article, on encountering the debug page, we learned about all of the different sections of the debug page and what information is included in each. For the debug page encountered, we used the information presented to locate and correct the coding error.

If you have read this article you may be interested to view :

Books to Consider

Instant Django 1.5 Application Development Starter
$ 12.99
Web Development with Django Cookbook
$ 29.99
comments powered by Disqus

An Introduction to 3D Printing

Explore the future of manufacturing and design  - read our guide to 3d printing for free