Take the 2-minute tour ×

The standard pattern for the view logic for Django forms is thus:

def contact(request):
        if form.is_valid(): # All validation rules pass
            # Process the data in form.cleaned_data
            # ...
        form = ContactForm() # An unbound form

    return render_to_response('contact.html', {
        'form': form,

This is fine in simple cases but quite easily descends into a complex mass of nested IF statements if your application logic gets a bit more complex.

Can anyone share their own cleaner approaches that avoid nested IF's and logic that depends on fall-through cases?

I'd be particularly interested in answers that don't rely on additional 3rd party apps.

share|improve this question
add comment

5 Answers

One of the included class based views is FormView (documentation). The two main methods you'd be concerned with are form_valid and form_invalid.

from django.views.generic import FormView

from myapp.forms import MyForm

class MyView(FormView):

    template_name = 'edit_something.html'
    form_class = MyForm
    success_url = '/success/' # you should use `reverse`, but let's stay focused.

    def form_valid(self, form):
        This is what's called when the form is valid.

        return super(MyView, self).form_valid(form)

    def form_invalid(self, form):
        This is what's called when the form is invalid.

        return self.render_to_response(self.get_context_data(form=form))

Alternatively you can override post, get or put methods and handle the form according to each type of request.

share|improve this answer
I don't think my specific case fits any of the existing classes too neatly but reading their source is going to be a great inspiration for a better structure... –  andybak May 5 '11 at 7:34
What's your specific case? I'm finding the provided classes offer a lot and the trick is to mix and match various ones to get a desired result and making any extensions is minimal/easy. –  Derek Reynolds May 5 '11 at 18:17
Using reverse in the success_url definition has caused me some problems, especially when the views.py is imported before the urlconf is defined –  synic Nov 24 '11 at 0:04
Instead of directly doing returning HttpResponseRedirect() in your version, you should instead use return super(MyView, self).form_valid(form) at the tail end of your form_valid() and form_invalid(). This will do your operations upon the data before then running Django's version of form_valid() on it as well (which does nothing but return the redirect). This way, if Django ever changes what happens inside of form_valid(), you haven't completely removed it. –  Bryson Apr 18 '12 at 5:36
@synic: use reverse_lazy() instead of reverse(). –  Erik Aug 26 '12 at 22:09
add comment

For complex scenarios, you should really consider overriding various methods of your form class which are involved in the validation/sanitizing process. The BaseForm class defines a _post_clean() method with no behavior which is particularly meant to be overridden. Overriding _clean() properly is also pretty straight forward, you could do that too if you wanted to do something before clean() is called.

Reading the source of django.forms will give you a much clearer insight on how the forms work and discover the right pattern for your needs.

share|improve this answer
add comment

This is shortest approach I found:

def contact(request):
    # if it's POST request it'll have data else it'll  be unbound
    form = ContactForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        # Process the data in form.cleaned_data
        # ...


of course if you don't like nested if you could try reversing logic:

def contact(request):
    template = 'contact.html'
    if request.method != 'POST': # GET return:
        return render_to_response(template, {'form': ContactForm()})

    form = ContactForm(request.POST)
    if not form.is_valid(): # FAIL return:
        return render_to_response(template, {'form': form})

    # here logic if user posted form and it's clean:

    return HttpResponseRedirect('/thanks/')
share|improve this answer
upvoted - your first example is the nicest way to do forms in a Django view I think. Note that you don't need the if request.method == 'POST' check though... the form can't be valid if you've passed None to the constructor, this is the same as doing ContactForm() –  Anentropic May 14 '13 at 19:29
add comment

Here is what i tried so far to make view funcs cleaner:

  • Separate view functions for GET / POST
  • Obviously is_valid logic in form as state in prev post.
  • Overriding model save() method to make if form.is_valid() part cleaner ( you can hide other related objects creates inside one save() )
  • Move common parts of view-funcs code into separeate function.
  • Also if your GET part is not CPU or database intense you can do it above if req == POST.. in other words do it always.. I for example often do: ctx['form'] = ContactForm() at the top.. and then again ContactForm(req.POST) inside if req == POST

BUT django 1.3 Now have class based view.. And this give one a big opportunity to make view extendable.. to make mixins.. not to write the save over and over.
I personally want to give it a shot and will try write django stuff in a new style with classes.

share|improve this answer
I'd be interested to see a class based version of the standard form handling logic. Also - could you elaborate on your third bullet point? –  andybak May 3 '11 at 15:51
Often in form.is_valid(): goes something like this: my_obj = form.save( commit = False ); my_obj.some_attr = datetime.now(); OtherObj = OtherModel( my_obj = my_obj, ... ); If this is common operation.. or just for beauty you can hide all this stuff in MyModel.save() or MyModel.custom_save() –  Turnaev Evgeny May 3 '11 at 17:12
add comment

The main source of complexity in these cases is handling GETs and POSTs in the same view function. Separate the logic for each method into its own view and things become clearer.

The only duplication will be rendering the form html, but that particular part is concise and meaningful for both cases (i.e. not boilerplate), so that's acceptable.

share|improve this answer
add comment

Your Answer


