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?

John
  • 29,546
  • 11
  • 78
  • 79
  • Django has Generic Views since several years. This question and all answers I see are outdated. See: https://docs.djangoproject.com/en/1.9/ref/class-based-views/generic-editing/ – guettli Jan 29 '16 at 20:36

8 Answers8

10

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
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 2
    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 Jul 18 '09 at 23:19
  • 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 Jul 18 '09 at 23:58
7

You are right it could be better, here is a better alternative (but keep reading):

def contact(request):
     form = ContactForm(request.POST or None) # 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

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

This snippet comes from a talk called Advanced Django Form Usage from DjangoCon11.

Note that this will process an empty form as valid (even before submission) if all the fields are optional and you don't use CSRF protection. So to eliminate that risk, you better use this one:

    def contact(request):
     form = ContactForm(request.POST or None) # A form bound to the POST data
     if request.method == 'POST' and form.is_valid(): # All validation rules pass
        # Process the data in form.cleaned_data
        # ...
        return HttpResponseRedirect('/thanks/') # Redirect after POST

     return render_to_response('contact.html', {
        'form': form,
     })
Juande Carrion
  • 750
  • 8
  • 11
2

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
  • 24,318
  • 4
  • 40
  • 46
1

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
  • 95,172
  • 10
  • 132
  • 126
1

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.

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
0

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
  • 4,395
  • 7
  • 31
  • 32
0

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

Wladimir Palant
  • 56,865
  • 12
  • 98
  • 126
Fragsworth
  • 33,919
  • 27
  • 84
  • 97
0

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
  • 161,647
  • 65
  • 194
  • 231