Built-in class-based generic views
Writing Web applications can be monotonous, because we repeat certain patternsagain and again. Django tries to take away some of that monotony at the modeland template layers, but Web developers also experience this boredom at the viewlevel.
Django’s generic views were developed to ease that pain. They take certaincommon idioms and patterns found in view development and abstract them so thatyou can quickly write common views of data without having to write too muchcode.
We can recognize certain common tasks, like displaying a list of objects, andwrite code that displays a list of any object. Then the model in question canbe passed as an extra argument to the URLconf.
Django ships with generic views to do the following:
- Display list and detail pages for a single object. If we were creating anapplication to manage conferences then a
TalkListView
and aRegisteredUserListView
would be examples of list views. A singletalk page is an example of what we call a “detail” view. - Present date-based objects in year/month/day archive pages,associated detail, and “latest” pages.
- Allow users to create, update, and delete objects – with orwithout authorization.Taken together, these views provide interfaces to perform the most common tasksdevelopers encounter.
Extending generic views
There’s no question that using generic views can speed up developmentsubstantially. In most projects, however, there comes a moment when thegeneric views no longer suffice. Indeed, the most common question asked by newDjango developers is how to make generic views handle a wider array ofsituations.
This is one of the reasons generic views were redesigned for the 1.3 release -previously, they were view functions with a bewildering array of options; now,rather than passing in a large amount of configuration in the URLconf, therecommended way to extend generic views is to subclass them, and override theirattributes or methods.
That said, generic views will have a limit. If you find you’re struggling toimplement your view as a subclass of a generic view, then you may find it moreeffective to write just the code you need, using your own class-based orfunctional views.
More examples of generic views are available in some third party applications,or you could write your own as needed.
Generic views of objects
TemplateView
certainly is useful, butDjango’s generic views really shine when it comes to presenting views of yourdatabase content. Because it’s such a common task, Django comes with a handfulof built-in generic views to help generate list and detail views of objects.
Let’s start by looking at some examples of showing a list of objects or anindividual object.
We’ll be using these models:
- # models.py
- from django.db import models
- class Publisher(models.Model):
- name = models.CharField(max_length=30)
- address = models.CharField(max_length=50)
- city = models.CharField(max_length=60)
- state_province = models.CharField(max_length=30)
- country = models.CharField(max_length=50)
- website = models.URLField()
- class Meta:
- ordering = ["-name"]
- def __str__(self):
- return self.name
- class Author(models.Model):
- salutation = models.CharField(max_length=10)
- name = models.CharField(max_length=200)
- email = models.EmailField()
- headshot = models.ImageField(upload_to='author_headshots')
- def __str__(self):
- return self.name
- class Book(models.Model):
- title = models.CharField(max_length=100)
- authors = models.ManyToManyField('Author')
- publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
- publication_date = models.DateField()
Now we need to define a view:
- # views.py
- from django.views.generic import ListView
- from books.models import Publisher
- class PublisherList(ListView):
- model = Publisher
Finally hook that view into your urls:
- # urls.py
- from django.urls import path
- from books.views import PublisherList
- urlpatterns = [
- path('publishers/', PublisherList.as_view()),
- ]
That’s all the Python code we need to write. We still need to write a template,however. We could explicitly tell the view which template to use by adding atemplate_name
attribute to the view, but in the absence of an explicittemplate Django will infer one from the object’s name. In this case, theinferred template will be "books/publisher_list.html"
– the “books” partcomes from the name of the app that defines the model, while the “publisher”bit is the lowercased version of the model’s name.
Note
Thus, when (for example) the APP_DIRS
option of a DjangoTemplates
backend is set to True in TEMPLATES
, a template location couldbe: /path/to/project/books/templates/books/publisher_list.html
This template will be rendered against a context containing a variable calledobject_list
that contains all the publisher objects. A template might looklike the this:
- {% extends "base.html" %}
- {% block content %}
- <h2>Publishers</h2>
- <ul>
- {% for publisher in object_list %}
- <li>{{ publisher.name }}</li>
- {% endfor %}
- </ul>
- {% endblock %}
That’s really all there is to it. All the cool features of generic views comefrom changing the attributes set on the generic view. Thegeneric views reference documents all thegeneric views and their options in detail; the rest of this document willconsider some of the common ways you might customize and extend generic views.
Making “friendly” template contexts
You might have noticed that our sample publisher list template stores all thepublishers in a variable named object_list
. While this works just fine, itisn’t all that “friendly” to template authors: they have to “just know” thatthey’re dealing with publishers here.
Well, if you’re dealing with a model object, this is already done for you. Whenyou are dealing with an object or queryset, Django is able to populate thecontext using the lowercased version of the model class’ name. This is providedin addition to the default object_list
entry, but contains exactly the samedata, i.e. publisher_list
.
If this still isn’t a good match, you can manually set the name of thecontext variable. The context_object_name
attribute on a generic viewspecifies the context variable to use:
- # views.py
- from django.views.generic import ListView
- from books.models import Publisher
- class PublisherList(ListView):
- model = Publisher
- context_object_name = 'my_favorite_publishers'
Providing a useful context_object_name
is always a good idea. Yourcoworkers who design templates will thank you.
Adding extra context
Often you need to present some extra information beyond that provided by thegeneric view. For example, think of showing a list of all the books on eachpublisher detail page. The DetailView
generic view provides the publisher to the context, but how do we getadditional information in that template?
The answer is to subclass DetailView
and provide your own implementation of the get_context_data
method.The default implementation adds the object being displayed to the template, butyou can override it to send more:
- from django.views.generic import DetailView
- from books.models import Book, Publisher
- class PublisherDetail(DetailView):
- model = Publisher
- def get_context_data(self, **kwargs):
- # Call the base implementation first to get a context
- context = super().get_context_data(**kwargs)
- # Add in a QuerySet of all the books
- context['book_list'] = Book.objects.all()
- return context
Note
Generally, get_context_data
will merge the context data of all parentclasses with those of the current class. To preserve this behavior in yourown classes where you want to alter the context, you should be sure to callget_context_data
on the super class. When no two classes try to define thesame key, this will give the expected results. However if any classattempts to override a key after parent classes have set it (after the callto super), any children of that class will also need to explicitly set itafter super if they want to be sure to override all parents. If you’rehaving trouble, review the method resolution order of your view.
Another consideration is that the context data from class-based genericviews will override data provided by context processors; seeget_context_data()
foran example.
Viewing subsets of objects
Now let’s take a closer look at the model
argument we’ve beenusing all along. The model
argument, which specifies the databasemodel that the view will operate upon, is available on all thegeneric views that operate on a single object or a collection ofobjects. However, the model
argument is not the only way tospecify the objects that the view will operate upon – you can alsospecify the list of objects using the queryset
argument:
- from django.views.generic import DetailView
- from books.models import Publisher
- class PublisherDetail(DetailView):
- context_object_name = 'publisher'
- queryset = Publisher.objects.all()
Specifying model = Publisher
is shorthand for saying queryset =Publisher.objects.all()
. However, by using queryset
to define a filteredlist of objects you can be more specific about the objects that will be visiblein the view (see Making queries for more information aboutQuerySet
objects, and see theclass-based views reference for thecomplete details).
To pick an example, we might want to order a list of books by publication date,with the most recent first:
- from django.views.generic import ListView
- from books.models import Book
- class BookList(ListView):
- queryset = Book.objects.order_by('-publication_date')
- context_object_name = 'book_list'
That’s a pretty minimal example, but it illustrates the idea nicely. Of course,you’ll usually want to do more than just reorder objects. If you want topresent a list of books by a particular publisher, you can use the sametechnique:
- from django.views.generic import ListView
- from books.models import Book
- class AcmeBookList(ListView):
- context_object_name = 'book_list'
- queryset = Book.objects.filter(publisher__name='ACME Publishing')
- template_name = 'books/acme_list.html'
Notice that along with a filtered queryset
, we’re also using a customtemplate name. If we didn’t, the generic view would use the same template as the“vanilla” object list, which might not be what we want.
Also notice that this isn’t a very elegant way of doing publisher-specificbooks. If we want to add another publisher page, we’d need another handful oflines in the URLconf, and more than a few publishers would get unreasonable.We’ll deal with this problem in the next section.
Note
If you get a 404 when requesting /books/acme/
, check to ensure youactually have a Publisher with the name ‘ACME Publishing’. Genericviews have an allow_empty
parameter for this case. See theclass-based-views reference for moredetails.
Dynamic filtering
Another common need is to filter down the objects given in a list page by somekey in the URL. Earlier we hard-coded the publisher’s name in the URLconf, butwhat if we wanted to write a view that displayed all the books by some arbitrarypublisher?
Handily, the ListView
has aget_queryset()
method wecan override. By default, it returns the value of the queryset
attribute,but we can use it to add more logic.
The key part to making this work is that when class-based views are called,various useful things are stored on self
; as well as the request(self.request
) this includes the positional (self.args
) and name-based(self.kwargs
) arguments captured according to the URLconf.
Here, we have a URLconf with a single captured group:
- # urls.py
- from django.urls import path
- from books.views import PublisherBookList
- urlpatterns = [
- path('books/<publisher>/', PublisherBookList.as_view()),
- ]
Next, we’ll write the PublisherBookList
view itself:
- # views.py
- from django.shortcuts import get_object_or_404
- from django.views.generic import ListView
- from books.models import Book, Publisher
- class PublisherBookList(ListView):
- template_name = 'books/books_by_publisher.html'
- def get_queryset(self):
- self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher'])
- return Book.objects.filter(publisher=self.publisher)
Using get_queryset
to add logic to the queryset selection is as convenientas it is powerful. For instance, if we wanted, we could useself.request.user
to filter using the current user, or other more complexlogic.
We can also add the publisher into the context at the same time, so we canuse it in the template:
- # ...
- def get_context_data(self, **kwargs):
- # Call the base implementation first to get a context
- context = super().get_context_data(**kwargs)
- # Add in the publisher
- context['publisher'] = self.publisher
- return context
Performing extra work
The last common pattern we’ll look at involves doing some extra work beforeor after calling the generic view.
Imagine we had a last_accessed
field on our Author
model that we wereusing to keep track of the last time anybody looked at that author:
- # models.py
- from django.db import models
- class Author(models.Model):
- salutation = models.CharField(max_length=10)
- name = models.CharField(max_length=200)
- email = models.EmailField()
- headshot = models.ImageField(upload_to='author_headshots')
- last_accessed = models.DateTimeField()
The generic DetailView
class, of course, wouldn’t know anything about thisfield, but once again we could easily write a custom view to keep that fieldupdated.
First, we’d need to add an author detail bit in the URLconf to point to acustom view:
- from django.urls import path
- from books.views import AuthorDetailView
- urlpatterns = [
- #...
- path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),
- ]
Then we’d write our new view – get_object
is the method that retrieves theobject – so we override it and wrap the call:
- from django.utils import timezone
- from django.views.generic import DetailView
- from books.models import Author
- class AuthorDetailView(DetailView):
- queryset = Author.objects.all()
- def get_object(self):
- obj = super().get_object()
- # Record the last accessed date
- obj.last_accessed = timezone.now()
- obj.save()
- return obj
Note
The URLconf here uses the named group pk
- this name is the defaultname that DetailView
uses to find the value of the primary key used tofilter the queryset.
If you want to call the group something else, you can set pk_url_kwarg
on the view. More details can be found in the reference forDetailView