Let’s Be Lazy-Productive: Class-Based Generic Views

We, programmers, are lazy. We despise repetitive tasks and are bored by monotonous project requirements. As much as possible, we automate processes and use abstractions for patterns that we always use in our work. And yet, despite being lazy, we also want to be efficient and productive. Can we really be lazy and efficient and productive, all at the same time?

Since Django 1.3, generic views for Django were implemented using classes instead of functions. With these new class-based generic views, writing view code is easier, faster and more organized. In addition to that, we benefit from having a cleaner, more reusable, and more structured code base. We used to write function-based views like this one:

from django.shortcuts import render_to_response
from django.template import RequestContext

def simple_view(request):
    """ This view just displays a template with some context. """

    context = {'a': 1, 'b': 2}

    return render_to_response('simple_template.html', context=RequestContext(request, context))

For views that really do something, we might have ended up writing the following:

from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, render_to_response
from django.templates import RequestContext
from django.views.decorators.csrf import csrf_protect
from .forms import MyObjectModelForm
from .models import MyObject

@login_required
@csrf_protect
def single_object_view(request, object_pk):
    """
    This view displays the details of an object on GET and
    allows logged-in users to edit the object on POST.

    """

    object = get_object_or_404(MyObject, object_pk)
    form = None

    if request.method == 'GET':
        # we just display the details for the object and an edit form

        form = MyObjectModelForm(instance=object)
    elif request.method == 'POST':
        # process object update

        form = MyObjectModelForm(instance=object, data=request.POST, files=request.FILES)
        if form.is_valid():
            object = form.save()
            form = MyObjectModelForm(instance=object)

    context = {'object': object, 'form': form}

    # additional stuff could go here

    return render_to_response('single_object.html', context=RequestContext(request, context))

And that is just for a single model. It’s not uncommon for us to have a slew of models in our projects, each requiring their own detail, list and edit views. With the old function-based views, our view code is doomed to bloat. Django 1.2 tried DRYing views with their generic views, but, in my opinion, this just transferred the bloat from our views to our urlconfs. Django 1.2 generic views also still seemed to be a bad pattern as reusability is very limited.

With class-based generic views, we could write our previous snippet as:

from django.contrib.auth.decorators import login_required
from django.views.generic import UpdateView
from .forms import MyObjectModelForm
from .models import MyObject

class SingleObjectUpdateView(UpdateView):
    form_class = MyObjectModelForm
    template_name = 'single_object.html'

    @login_required
    def dispatch(self, request, *args, **kwargs):
        return super(BaseSingleObjectUpdateView, self).dispatch(request, *args, **kwargs)

Now, that’s compact code—we can clearly see that we have an update view that uses MyObjectModelForm for updating MyObject instances and whose results are rendered using the single_object.html template. Class-based generic views also save us from writing boilerplate code that we would have written with function-based views.

Not only do class-based views provide us a means of writing compact code, they also provide us a means of writing highly reusable view code. Again, suppose we have several models that need an update view. We could just rewrite our implementation above as:

from django.contrib.auth.decorators import login_required
from django.views.generic import UpdateView
from .forms import (MyObjectModelForm, MyObject2ModelForm, MyObject3ModelForm) # and so on...
from .models import MyObject

class BaseUpdateView(UpdateView):
    template_name = 'single_object.html'

    @login_required
    def dispatch(self, request, *args, **kwargs):
        return super(BaseSingleObjectUpdateView, self).dispatch(request, *args, **kwargs)

# subclass all the way!
class MyObjectUpdateView(BaseUpdateView):
    form_class = MyObjectModelForm
    success_url = '/myobject/list/'

class MyObject2UpdateView(BaseUpdateView):
    form_class = MyObject2ModelForm
    success_url = '/myobject2/list/'

# and so on...

That’s all we have to write to create the required update views for the rest of the models that we have. In case we want some custom behavior for MyObject3‘s update view, we could still inherit from the BaseUpdateView that we have and modify the parts that we need to. Writing mixins is also a good way of extending the capabilities of our existing views:

class MyObject3UpdateView(BaseUpdateView, SomeCustomMixin1, SomeCustomMixin2):
    """
    SomeCustomMixin1 could be overriding some of the default methods for
    django.views.generic.UpdateView while SomeCustomMixin2 could be
    decorating the dispatch method with some custom decorator.
    Anything goes here, actually!

    """

    template_name = 'custom_single_object.html'
    success_url = '/myobject3/list'

    def get_form_class(self):
        if condition_1:
            return MyObject3ModelForm1
        else:
            return MyObject3ModelForm2

    # and other custom behavior that you want for this model's update view

The main drawback of Django’s class-based generic views is its learning curve. Developers new to Django who wants to dive immediately into class-based generic views might have a hard time doing so, but once familiar with the inner workings of this feature of the framework, they’ll start writing clean, compact, reusable and customizable view code in no time. I recommend this unofficial documentation site and (of course) Django’s source code as the main references for class-based generic views.

Given the power of Django’s class-based generic views, do we still have room for more laziness? Of course we do, with the help of django-braces! We still often write a lot of boilerplate code even if we already use class-based generic views. Consider our BaseUpdateView above. We would have to override the dispatch methods for similar views that require logged-in users. Luckily, django-braces already implemented a neat mixin for us:

from django.views.generic import UpdateView
from braces.views import LoginRequiredMixin

class BaseUpdateView(LoginRequiredMixin, UpdateView):
    template_name = 'single_object.html'

# the same subclassing stuff follows

Or what if updating of MyObject2 instances is now done via AJAX? Guess what, django-braces also have mixins for this use-case:

from braces.views import JSONResponseMixin, AjaxResponseMixin

class MyObject2UpdateView(JSONResponseMixin, AjaxResponseMixin, BaseUpdateView):
    form_class = MyObject2ModelForm

    def get_ajax(self, request, *args, **kwargs):
        # we could be passing more info if the request is an ajax request
        context_dict = {
            'extra': 'extra info',
            'object': json_serialized_object
        }

        return self.render_json_response(context_dict)

Django-braces has plenty of other mixins that we can plug into our existing views to easily add more functionality to them. The most useful ones that I’ve found are CsrfExemptMixin and FormMessagesMixin for form processing, SuperuserRequiredMixin and the similar StaffuserRequiredMixin for access control, and MessageMixin for displaying messages created using Django’s messages framework. And if those still doesn’t fill the bill, we are free to fork the project to add our own mixins.

Being lazy doesn’t have to mean being unproductive. With the right tools, we could eliminate the boring, repetitive, and inefficient aspects of our work, allowing us to have laser focus on what really matters. What I have mentioned here is just a very small subset of all the available tools that we, Django developers, can use to be more lazy and yet more efficient and productive at the same time.