开发者

A better pattern for Django form logic?

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

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,
    })

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.


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.


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
        # ...
        return HttpResponseRedirect('/thanks/') # Redirect after POST

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

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/')


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜