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.
- Assigns the result of
self.get_queryset()toself.object_list. (We'll get to this in a minute) - Checks to see if we allow an empty queryset and raises a 404 if it does.
- Calls to
get_context_dataand addsobject_list(which is the queryset result) to the context. - 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
- It looks for a class property called
queryset. If that exists, there's not a lot to do, so it returns it. - If
querysetisNone, it looks for a class property calledmodel. If that exists it callsModelName.objects.all(). - 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.