views:

482

answers:

7

The suggested pattern for processing a form in a view seems overly complex and non-DRY to me:

def contact(request):
    if request.method == 'POST': # If the form has been submitted...
        form = ContactForm(request.POST) # A form bound to the POST data
        if form.is_valid(): # All validation rules pass
            # Process the data in form.cleaned_data
            # ...
            return HttpResponseRedirect('/thanks/') # Redirect after POST
    else:
        form = ContactForm() # An unbound form

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

That's a lot of conditionals, it repeats the ContactForm() construction, and the whole block is repeated everywhere a view needs to process a form. Isn't there a better way of doing it?

+2  A: 

The boilerplate way of processing forms mixes two concerns: presenting a form to edit and processing the results. You could break this into two methods, which would introduce some duplication in the form of identical render_to_response() calls. By the time you refactored that, you might end up with something that's less readable than the single-method form above.

When I look at the boilerplate method, I don't see duplication. The two uses of ContactForm() are distinctly different. The two conditionals seem to me to fairly cleanly show the state transitions involved in processing a form (present a blank form, accept submissions until one is valid, process-and-redirect).

Dave W. Smith
A: 

One could write a function that handles the conditionals for all forms. You could do this by passing in a function specific to that form after "is_valid", such as:

def FormHandler(request, CleaningFunction, redirecturl):
    if request.method = 'POST':
        if request.method == 'POST': # If the form has been submitted...
            form = ContactForm(request.POST) # A form bound to the POST data
            if form.is_valid(): # All validation rules pass
                CleaningFunction(form) # Process the data in form.cleaned_data
                return HttpResponseRedirect('/thanks/') # Redirect after POST
     else:
         form = ContactForm() # An unbound form
     return form

Then you would call FormHandler from your view. Note this isn't tested and may have errors.

Technical Bard
A: 

Django provides several generic views for creating, editing, and deleting objects. Perhaps you could try these.

Fragsworth
+7  A: 

You can avoid the repetition, of course. Mostly, you need to pass in as arguments the class of form and template name to use, a callable to process the cleaned data when a valid form is submitted, and a destination for the redirect after such processing; plus, you need a little extra code to call the form class just once, to produce either a bound or unbound form, and deal with it properly. I.e.:

def process_any_form(request, 
                     form_class, template_file_name,
                     process_data_callable, redirect_destination):

    form = form_class(request.POST if request.method == 'POST' else None)

    if form.is_bound and form.is_valid():
        process_data_callable(form.cleaned_data)
        return HttpResponseRedirect(redirect_destination)

    return render_to_response(template_file_name, {'form': form})
Alex Martelli
This works. If the template wants more than just 'form', you'll need to widen the argument list to include, say, a hash of values.
Dave W. Smith
s/hash/dict/, but, yes, good idea: you can pass in a semi-pre-populated dict d for the context, and use dict(d, form=form) as the context for rendering, this makes the solution even more general.
Alex Martelli
+1  A: 

Alex's generic handler beat me to it, but FWIW we tend toward a less-generic version of his suggestion:

def contact(request):
    post_data = request.POST if request.method == 'POST' else None
    form = ContactForm(post_data)
    if request.method == 'POST':
        # perform normal validation checking, etc

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

If post_data is None, then the form is instantiated as being unbounded. Otherwise, bound processing continues as normal. It avoids a duplicated construction of ContactForm, but I agree with Dave's answer that the duplicate construction doesn't bother me as being a duplicate precisely because the construction parameters are different.

Jarret Hardie
+1  A: 

I got so tired of this that i wrote my own generic views to handle it. In the process, I discovered that django already has underdocumented generic views for forms processing. They are fairly direct analogues of the documented generic views, but accept forms, and basically follow the same template you used in your example. Ultimately, I found them too inflexible and stupid for my use (I don't want a create_or_update view, I wan't to treat those two actions seperately.)

Edit: You didn't like Fragsworth's answer, which points to the same thing i'm talking about, I assume you wont' like mine either. Here's an example for how it works.

# in urls.py
urlpatterns += patterns("", 
    (u'^...$', 'django.views.generic.create_update.update', {
        'form_class': ContactForm })
)

ContactForm must have a save() method, and thats where your form processing logic goes.

TokenMacGuy
A: 

You can bypass django's forms module and just do it the old fashion way, you get more flexibility without too much loss IMHO.

Last time I looked at django forms was quite a while ago, I don't know if things have changed, but for instance, it doesn't really allow you build an ajax-style form; at least not easily.

hasen j