class-based views

You are currently browsing articles tagged class-based views.

I recently realized that class-based views are a beautiful, powerful tool to build clean, streamlined views that are easy to understand and maintain.

Let me make my case with a trivial example. Let’s have a look at the canonical way to deal with a form in function-based views:

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

What we have here is a good deal of boilerplate code, complete with an ugly nested if to deal with the submit and validation logic. Hardly the kind of style I would promote.

With class-based views, we can instead write:

class Contact(FormView):
    template_name = 'contact.html'
    form_class = ContactForm
    success_url = '/thanks/'
    def form_valid(self, form):
        # Process the data in form.cleaned_data
        return super(Contact, self).form_valid(form)

The boilerplate code has been abstracted away, we can express the easy cases in a declarative style and we can concern ourselves with just the application logic. Of course, there are more advantages than this simple example can show: pre-made views, a good deal of pre-made composable mixins and, of course, the full power of OOP.

Yet, discovering and adopting the new style is more difficult than it should be. The documentation is filled with function-based view examples that should really be ported to class-based views, and even the section on class-based view is a dump of lists with little to none information on how the details come together.

So, expect to have to dive into the source code, ask around and look for sample code (I plan to post some here in my blog as I discover things). And, if you’re a long-time Django user, or if you’re just approaching Django and you rely a lot on Google and code samples, expect to find a mismatch between old best practices and class-based views.

For example, class-based views provide an obvious means to compose view functionality: multiple inheritance and mixins. And function-based views did provide an obvious means to compose view functionality: function decorators. While class-based views do support old function decorators, when I’m using the two extensively, I’m constantly aware that I am mixing two paradigms, I’m constantly looking out for potential trouble at the intersection of the two worlds, and I’m aware that I need to make a conscious choice between the two.

Should these difficulties stop you from adopting class-based views in your current project? It depends on your context. Should these difficulties stop you from looking into adopting class-based views for your current or your next project? Not really.

Tags: , , ,

At times, you need to perform some postprocessing after a view has been rendered. It could be some serious work, in which case you’d better arrange for it to be carried over by a separate thread, a separate process or even a separate server, or it could be just a quick update of the database. In either case, you need to ensure that whatever has to be done does not influence the response.

Of course, in a manually  implemented view, rendering to the response need not be the last action. You can always write:

def my_view(request):
    do_some_work()
    response = render_to_response('my_view.html')
    do_extra_work()
    return response

But, when using a class-based generic view, you might be left wondering for a while, especially if you happen to focus too much on the wrong part of the documentation.

Turns out that the answer is quite simple and elegant. Class-based generic views return a new kind of response introduced in Django 1.3, the TemplateResponse, which provides a hook to perform extra work after the template has been rendered. Armed with that knowledge, you can easily write a generic mixin like this:

class PostprocessorMixin(object):
    def get(*args, **kwargs):
        resp = super(PostprocessorMixin, self).get(*args, **kwargs)
        resp.add_post_render_callback(self.postprocess)
        return resp

When you later need to perform extra work after rendering the response, mix this in your custom derived views and implement your postprocessing in postprocess.

Tags: , , ,