« Kevin Ngo

Bootstrapping CRUD-Heavy Django Apps with CBVs

22 Apr 2012

CRUD-heavy web applications usually implement similar operations on many similar objects. It is especially important to practice DRY (don’t repeat yourself) in these kinds of codebases. Maintaining code becomes much easier by having a generic base code that can be subclassed or extended to fulfill specific needs. Using these concepts can simplify code down to zero-LOC templates and zero-LOC views.


Django Class-Based Generic Views

Django class-based generic views (CBVs) are like a set of prebuilt views that ships with a lot of commonly used features that can easily be overidden. For example, TemplateView, the most basic CBV simply displays a template which is simple enough to place in the urls.py.

# urls.py
from django.views.generic import TemplateView

urlpatterns = patterns('',
    url(r'^$', TemplateView.as_view(template_name='index.html'))
)

When we point our urls.py towards a view, we invoke the as_view function to sort of turn the class into a view. For a CRUD app, there are several very useful generic views.

Implementing a Generic View

Since the generic views have a lot in similar in the way they’re used, we’ll use ListView as an example. Since this is a post about inheritance, let’s create a base ListView that other apps’ ListViews can inherit from.

# base/views.py
from django.views.generic import ListView

class BaseListView(ListView):
    template_name = 'base/base_list.html'
    paginate_by = 30

And that’s it! Short, right? It could be even shorter by omitting the template_name variable; the CBVs by default look for a template at APPNAME/APPNAME_VIEWNAME if not template name is given. So for this ListView in the base app, it would look at ‘base/base_list.html’, and DetailView would look at ‘base/base_detail.html’ and so on.

In the urls.py, simply import the view and call as_view() on it like with the TemplateView before.

Writing the Template

For ListView, if paginate_by is defined, Django will paginate the list of objects if necessary. In the template, we can refer to the list of objects with object_list and the pagination object as ‘page_obj’. Ignore the my_header for now.

{% extends "base.html" %}

{% block title %}
    Generic Objects
{% endblock %}

{% block header %} {{ my_header }} {% endblock %}

{% if page_obj.has_next() %} {% endif %} {% for obj in object_list %} {{ obj }} {% endfor %}

Extending our BaseListView

Take for example an app called books that lets us CRUD books in a database. Let’s use our BaseListView to create a ListView for books.

# book/views.py
from app.base.views import BaseListView
from app.book.models import Book

class BookView(object):
    model = Book
    queryset = Book.objects.all()

class BookListView(BookView, BaseListView):
    """ """

We can even get away with only this but we say we want to specify some fine-grained details. Let’s add a context variable to access in the template.

# book/views.py
class BookListView(BookView, BaseListView):

    def get_context_data(self, **kwargs:
        context = super(BaseListView, self).get_context_data(**kwargs)
        context['my_header'] = 'List of Books'
        return context

We overwrote the parent’s get_context_data which contains the variables to be passed into the templates to add another variable. Here, we’ll add an arbitrary name for the page header. Now we can leave it and have it point to the base list template we wrote earlier, or we can have it look for it within the books app. Paired along with template inheritance, we barely have to write any code once the infrastructure is set up.

{% extends "base.html" %}

{% block title %}
    Books
{% endblock %}

Django CBVs have been in the dark for some time and are becoming increasingly popular. Throw away all of those function-based views that seem very similar to each other and go generic.