Greg Aker

How does Django's class-based ListView work?

Filed in: Django

April 20, 2012

Yesterday we looked at the generic class-based TemplateView. For this installment of looking under the hood at Django class-based views, let's look at the List View. (take a peek at that class now)

Check it out, there's nothing to it, or is there?

It subclasses BaseListView and mixes MultipleObjectTemplateResponseMixin. Let's visualize how that works.

MultipleObjectTemplateResponseMixin

subclasses: TemplateResponseMixin

methods: get_template_names

class properties: template_name_suffix

This is pretty straightforward. We know what TemplateResponseMixin does, so we can skip that.

However, look at get_template_names() in that mixin. It looks at the type of object we pass to it, and tries to automatically come up with a template name. So for instance, if your app is 'blog' and your model is 'Post', unless you tell it otherwise, Django assumes the template you are using is going to be blog/post_list.html.

BaseListView

subclasses: MultipleObjectMixin and View

methods: get

We already know that a GET or HEAD request will hit this get method. So let's see what it is doing here.

  1. Assigns the result of self.get_queryset() to self.object_list. (We'll get to this in a minute)
  2. Checks to see if we allow an empty queryset and raises a 404 if it does.
  3. Calls to get_context_data and adds object_list (which is the queryset result) to the context.
  4. returns self.render_to_response

MultipleObjectMixin

subclasses: ContextMixin

methods:

get_queryset
paginate_queryset
get_paginate_by
get_paginator
get_allow_empty
get_context_object_name
get_context_data

class properties:

allow_empty
queryset
model
paginate_by
context_object_name
paginator_class

Okay, this one is a bit of a beast.

get_queryset

  1. It looks for a class property called queryset. If that exists, there's not a lot to do, so it returns it.
  2. If queryset is None, it looks for a class property called model. If that exists it calls ModelName.objects.all().
  3. If neither of these exist, raise an error.

I'm going to gloss over paginate_queryset, get_paginate_by, and get_paginator. But you should read them.

get_context_data

This sets up paginator, page_obj, is_paginated and object_list in the context. It also checks to see if the queryset should be adjusted for pagination.

One neat thing here is you can set a custom name for the object list in the context. You do this by specifying self.context_object_name. If that's set you can iterate over something like posts instead of the default object_list. This isn't a huge deal, but can help to make things more descriptive for designers working in the templates.

Examples

There are a few ways we can use this list view. Let's look at a couple.

urls.py

from django.views import generic
from myproject.apps.blog import models as blog_models

urlpatterns = patterns('',
    url(r'^blog/$', generic.ListView.as_view(
        queryset=blog_models.Post.objects.all(),
        paginate_by=25),
        name='blog-list-view')
)

Here we setup queryset and paginate_by. Django is going to assume we are using a template called blog/post_list.html.

views.py

Let's do something a bit more complex. Say we want to show a list of only the posts from the currently logged in user. We wrote a LoggedInMixin last time to help us out, so we'll use that as well.

// views.py

from django.views import generic
from myproject.apps.users.models import LoggedInMixin
from myproject.apps.blog.models import Post

class PostListView(LoggedInMixin, generic.ListView):
    template_name = 'blog/post_list.html'
    paginate_by = 25
    context_object_name = 'posts'

    def get_queryset(self):
        return Post.objects.filter(author=self.request.user)

// urls.py

from myproject.apps.blog.views import PostListView

urlpatterns = patterns('',
    url(r'^blog/$', PostListView.as_view()),
    name='blog-list-view')

Still, we have an amazingly powerful view that before could have taken quite a bit of code down to 6-7 lines. Pretty sick, isn't it?

If you're grooving on this, use iPDB or PDB to go through some of these views and trace them.

Shameless plug

Want to learn some more Python? Check out my screencasts at Mijingo.