In the first instalment of this short series, I introduced the theory behind Django class-based views and the reason why in this context classes are more powerful than pure functions. I also introduced one of the generic views Django provides out of the box, which is ListView.

In this second post I want to talk about the second most used generic view, DetailView, and about custom querysets and arguments. Last, I'm going to introduce unspecialised class-based views that allow you to build more complex Web pages. To fully understand DetailView, however, you need to grasp two essential concepts, namely querysets and view parameters. So I'm sorry for the learn-by-doing readers, but this time too I'm going to start with some pure programming topics.

QuerySets or the art of extracting information

One of the most important parts of Django is the ORM (Object Relational Mapper), which allows you to access the underlying database just like a collection of Python objects. As you know, Django provides tools to simplify the construction of DB queries; they are managers (the .objects attribute of any models, for example) and query methods (get, filter, and so on). Pay attention because things here are slightly more complicated than you can think at a first glance.

When you use one of the methods of a manager you get as a result a QuerySet, which most of the time is used as a list, but is more than this. You can find the documentation about queries here and the documentation about QuerySet here. Both are very recommended readings.

What I want to stress here is that quesysets are not evaluated until you perform an action that access the content like slicing or iterating on it. This means that we can build querysets, pass them to functions, store them, and even build them programmatically or metaprogramming them without the DB being hit. If you think at querysets as recipes you are not far from the truth: they are objects that store how you want to retrieve the data of your interest. Actually retrieving them is another part of the game. This separation between the definition of something and its execution is called lazy evaluation.

Let me give you a very trivial example to show why the lazy evaluation of querysets is important.

[...]

def get_oldest_three(queryset):
    return queryset.order_by['id'][0:2]

old_books = get_oldest_three(Book.objects.all())
old_hardcover_books = \
    get_oldest_three(Book.objects.filter('type=Book.HARDCOVER'))

As you can see the get_oldest_three method is just filtering an incoming QuerySet (which can be of any type); it simply orders the objects and gets the first three inserted in the DB. The important thing here is that we are using querysets like pure algorithms, or descriptions of a procedure. When creating the old_books variable we are just telling the get_oldest_three method "Hey, this is the way I extract the data I'm interested in. May you please refine it and return the actual data?"

Being such flexible objects, querysets are an important part of generic views, so keep them warm for the upcoming banquet.

Being flexible: parametric views

URLs are the API of our Web site or service. This can be more or less evident for the user that browses through the pages, but from the programmer's point of view, URLs are the entry points of a Web-based service. As such, they are not very different from the API of a library: here, static pages are just like constants, or functions that always return that same value (such as a configuration parameter), while dynamic pages are like functions that process incoming data (parameters) and return a result.

So URLs can accept parameters, and our underlying view shall do the same. You basically have two methods to convey parameters from the browser to your server using HTTP. The first method is named query string and lists parameters directly in the URL through a universal syntax. The second method is storing parameters in the HTTP request body, which is what POST requests do. We will discuss this method in a later post about forms.

The first method has one big drawback: most of the time URLs are long (and sometimes too long), and difficult to use as a real API. To soften this effect the concept of clean URL arose, and this is the way Django follows natively (though, if you want, you can also stick to the query string method).

Now, the Django official documentation on URL dispatcher tells you how you can collect parameters contained in the URL parsing it with a regular expression; what we need to discover is how class-based views receive and process them.

In the previous post we already discussed the as_view method that shall instance the class and return the result of dispatch (CODE).

    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        # sanitize keyword arguments
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

Now look at what the view wrapper function actually does with the instanced class (CODE); not surprisingly it takes the request, args and kwargs passed by the URLconf and converts them into as many class attributes with the same names. Remember that URLconf is given this function itself, not the result of the call, which is the result of dispatch.

This means that anywhere in our CBVs we can access the original call parameters simply reading request, args and kwargs, where *args and **kwargs are the unnamed and named values extracted by the URLconf regular expression.

Getting details

Just after listing things, one of the most useful things a Web site does is giving details about objects. Obviously any e-commerce site is made for the most part by pages that list products and show product details, but also a blog is made of one or more pages with a list of posts and a page for each of them. So building a detailed view of the content of our database is worth learning.

To help us in this task Django provides DetailView, which indeed deals, as the name suggests, with the details of what we get from the DB. While ListView's basic behaviour is to extract the list of all objects with a given model, DetailView extracts a single object. How does it know what object shall be extracted?

When dispatch is called on an incoming HTTP request the only thing it does is to look at the method attribute, which for HttpRequest objects contains the name of the HTTP verb used (e.g. 'GET'); then dispatch looks for a method of the class with the lowercase name of the verb (e.g. 'GET' becomes get) (CODE). This handler is then called with the same parameters of dispatch, namely the request itself, *args and **kwargs (CODE).

DetailView has no body and inherits everything from two classes, just like happened for ListView; the first parent class is the template mixin, while the second one, BaseDetailView, implements the get method (CODE).

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)

As you can see, this method extracts the single object that it shall represent calling get_object, then calls get_context_data (that we already met in the previous post) and last the familiar render_to_response. The method get_object is provided by BaseDetailView's ancestor SingleObjectMixin (CODE): the most important parts of its code, for the sake of our present topic are

def get_object(self, queryset=None):

    [...]

    if queryset is None:
        queryset = self.get_queryset()

    pk = self.kwargs.get(self.pk_url_kwarg, None)

    [...]

    if pk is not None:
        queryset = queryset.filter(pk=pk)

    [...]

    try:
        obj = queryset.get()

    [...]

    return obj

Warning: I removed many lines from the previous function to improve readability; please check the original source code for the complete implementation.

The code shows where DetailView gets the queryset from; the get_queryset method is provided by SingleObjectMixin itself and basically returns queryset if present, otherwise returns all objects of the given model (acting just like ListView does). This queryset is then refined by a filter and last by a get. Here get is not used directly (I think) to manage the different error cases and raise the correct exceptions.

The parameter pk used in filter comes directly from kwargs, so it is taken directly from the URL. Since this is a core concept of views in general I want to look at this part with some extra care.

The DetailView class is called by an URLconf that provides a regular expression to parse the URL, for example url(r'^(?P<pk>\d+)/$',. This regex extracts a parameter and gives it the name pk, so kwargs of the view will contain pk as key and the actual number in the URL as value. For example the URL 123/ will result in {'pk': 123}. The default behaviour of DetailView is to look for a pk key and use it to perform the filtering of the queryset, since pk_url_kwarg is 'pk' (CODE).

So if we want to change the name of the parameter we can simply define the pk_url_kwarg of our class and provide a regex that extract the primary key with the new name. For example url(r'^(?P<key>\d+)/$', extracts it with the name key, so we should define pk_url_kwarg = 'key' in our class.

From this quick exploration we learned that a class inheriting from DetailView:

  • provides a context with the object key initialized to a single object
  • must be configured with a model class attribute, to know what objects to extract
  • can be configured with a queryset class attribute, to refine the set of objects where the single object is extracted from
  • must be called from a URL that includes a regexp that extracts the primary key of the searched object as pk
  • can be configured to use a different name for the primary key through the pk_url_kwarg class attribute

The basic use of DetailView is exemplified by the following code.

class BookDetail(DetailView):
    model = Book

urlpatterns = patterns('',
    url(r'^(?P<pk>\d+)/$',
        BookDetail.as_view(),
        name='detail'),
    )

The view extracts a single object with the Book model; the regex is configured with the standard pk name.

As shown for ListView in the previous post, any CBV uses get_context_data to return the context dictionary to the rendering engine. So views that inherit from DetailView can add data to the context following the same pattern. Suppose we have a function get_similar_books that given a book, returns similar ones according to some criteria.

class BookDetail(DetailView):
    model = Book

    def get_context_data(self, **kwargs):
        context = super(BookDetail, self).get_context_data(**kwargs)
        context['similar'] = get_similar_books(self.object)
        return context

urlpatterns = patterns('',
    url(r'^(?P<pk>\d+)/$',
        BookDetail.as_view(),
        name='detail'),
    )

As explained before, you can access the object being shown through object, which in the above example is passed to a service function we implemented somewhere in our code.

Using the base views

Sometimes, when dealing with complex pages, the generic display CBVs that Django provides are not the right choice. This usually becomes evident when you start overriding method to prevent the view to perform its standard behaviour. As an instance say that you want to show detailed information of more than one object: probably DetailView will soon show its limits, having been built to show only one object.

In all those cases that cannot be easily solved by one of the generic display CBVs, your have to build your own starting from one of the base views: RedirectView, TemplateView, or View (DOCS, CODE).

I'm not going to fully describe those views; I want however to briefly point out some peculiarities.

View is by now an old friend of us; we met it when we discussed the as_view and dispatch methods. It is the most generic view class and can be leveraged to perform very specialized tasks such as rendering pages without templates (for example when returning JSON data).

TemplateView is the best choice to render pages from a template, maintaining a great level of freedom when it comes to the content of the context dictionary. Chances are that this is going to be the view you will use the most after ListView and DetailView. Basically you just need to inherit from it and define the get_context_data method. As you can see from the CODE TemplateView answers to GET requests only.

RedirectView, as the name implies, is used to redirect a request. The redirection mechanism is very simple: its get method returns a HttpResponseRedirect to the URL defined by the url class attribute. The class exhibits a very interesting behaviour (CODE) when called with HTTP methods other than GET (namely HEAD, POST, OPTIONS, DELETE, PUT, or PATCH): it "converts" the method to GET simply calling get from the respective method (head, post, and so on). In the next post I'll show how to leverage this simple technique to show the user a pre-filled form.

Date-based views

Django provides other class-based views that simplify dealing with objects extracted or ordered by date. As a programmer, you know that sometimes dealing with dates is awkward, to say the least; views such as YearArchiveView or DayArchiveView (CODE) aim to help you to tame your date-based objects; any object that contains a date (e.g. post date for articles, birth date for people, log date for messages, etc.) can be processed by these views. You can find the official documentation here.

Remember that date-based views are CBVs, so they are based on View, just like ListView or TemplateView. So, apart from their specialization on date processing, they behave the same (using get_context_data, get, dispatch, and so on).

Final words

In this post we covered DetailView in deep and, more superficially, all the remaining base and data-based views. I showed you how DetailView uses the given model and the querystring parameters to find the requested object, and how you can change its default behaviour. In the next post we will step into the rich (and strange) world of forms.

Feedback

Feel free to reach me on Twitter if you have questions. The GitHub issues page is the best place to submit corrections.