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 easy interfaces to perform the most commontasks developers 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 just view functions with a bewildering array of options;now, rather than passing in a large amount of configuration in the URLconf,the recommended way to extend generic views is to subclass them, and overridetheir attributes 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 that make generating list and detail views of objectsincredibly easy.

Let's start by looking at some examples of showing a list of objects or anindividual object.

We'll be using these models:

  1. # models.py
  2. from django.db import models
  3.  
  4. class Publisher(models.Model):
  5. name = models.CharField(max_length=30)
  6. address = models.CharField(max_length=50)
  7. city = models.CharField(max_length=60)
  8. state_province = models.CharField(max_length=30)
  9. country = models.CharField(max_length=50)
  10. website = models.URLField()
  11.  
  12. class Meta:
  13. ordering = ["-name"]
  14.  
  15. def __str__(self):
  16. return self.name
  17.  
  18. class Author(models.Model):
  19. salutation = models.CharField(max_length=10)
  20. name = models.CharField(max_length=200)
  21. email = models.EmailField()
  22. headshot = models.ImageField(upload_to='author_headshots')
  23.  
  24. def __str__(self):
  25. return self.name
  26.  
  27. class Book(models.Model):
  28. title = models.CharField(max_length=100)
  29. authors = models.ManyToManyField('Author')
  30. publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
  31. publication_date = models.DateField()

Now we need to define a view:

  1. # views.py
  2. from django.views.generic import ListView
  3. from books.models import Publisher
  4.  
  5. class PublisherList(ListView):
  6. model = Publisher

Finally hook that view into your urls:

  1. # urls.py
  2. from django.urls import path
  3. from books.views import PublisherList
  4.  
  5. urlpatterns = [
  6. path('publishers/', PublisherList.as_view()),
  7. ]

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 just the lowercased version of the model's name.

Note

Thus, when (for example) the APP_DIRS option of a DjangoTemplatesbackend 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 very simple templatemight look like the following:

  1. {% extends "base.html" %}
  2.  
  3. {% block content %}
  4. <h2>Publishers</h2>
  5. <ul>
  6. {% for publisher in object_list %}
  7. <li>{{ publisher.name }}</li>
  8. {% endfor %}
  9. </ul>
  10. {% 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 lower cased version of the model class' name. This isprovided in addition to the default object_list entry, but contains exactlythe same data, 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:

  1. # views.py
  2. from django.views.generic import ListView
  3. from books.models import Publisher
  4.  
  5. class PublisherList(ListView):
  6. model = Publisher
  7. 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 simply need to present some extra information beyond thatprovided by the generic view. For example, think of showing a list ofall the books on each publisher detail page. TheDetailView generic view providesthe publisher to the context, but how do we get additional informationin that template?

The answer is to subclass DetailViewand provide your own implementation of the get_context_data method.The default implementation simply adds the object being displayed to thetemplate, but you can override it to send more:

  1. from django.views.generic import DetailView
  2. from books.models import Book, Publisher
  3.  
  4. class PublisherDetail(DetailView):
  5.  
  6. model = Publisher
  7.  
  8. def get_context_data(self, **kwargs):
  9. # Call the base implementation first to get a context
  10. context = super().get_context_data(**kwargs)
  11. # Add in a QuerySet of all the books
  12. context['book_list'] = Book.objects.all()
  13. 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:

  1. from django.views.generic import DetailView
  2. from books.models import Publisher
  3.  
  4. class PublisherDetail(DetailView):
  5.  
  6. context_object_name = 'publisher'
  7. queryset = Publisher.objects.all()

Specifying model = Publisher is really just shorthand for sayingqueryset = Publisher.objects.all(). However, by using querysetto define a filtered list of objects you can be more specific about theobjects that will be visible in the view (see Making queriesfor more information about QuerySet objects,and see the class-based views referencefor the complete details).

To pick a simple example, we might want to order a list of books bypublication date, with the most recent first:

  1. from django.views.generic import ListView
  2. from books.models import Book
  3.  
  4. class BookList(ListView):
  5. queryset = Book.objects.order_by('-publication_date')
  6. context_object_name = 'book_list'

That's a pretty simple 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:

  1. from django.views.generic import ListView
  2. from books.models import Book
  3.  
  4. class AcmeBookList(ListView):
  5.  
  6. context_object_name = 'book_list'
  7. queryset = Book.objects.filter(publisher__name='ACME Publishing')
  8. 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. Previously, it has just been returning the value of thequeryset attribute, but now we can 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:

  1. # urls.py
  2. from django.urls import path
  3. from books.views import PublisherBookList
  4.  
  5. urlpatterns = [
  6. path('books/<publisher>/', PublisherBookList.as_view()),
  7. ]

Next, we'll write the PublisherBookList view itself:

  1. # views.py
  2. from django.shortcuts import get_object_or_404
  3. from django.views.generic import ListView
  4. from books.models import Book, Publisher
  5.  
  6. class PublisherBookList(ListView):
  7.  
  8. template_name = 'books/books_by_publisher.html'
  9.  
  10. def get_queryset(self):
  11. self.publisher = get_object_or_404(Publisher, name=self.kwargs['publisher'])
  12. return Book.objects.filter(publisher=self.publisher)

As you can see, it's quite easy to add more logic to the queryset selection;if we wanted, we could use self.request.user to filter using the currentuser, or other more complex logic.

We can also add the publisher into the context at the same time, so we canuse it in the template:

  1. # ...
  2.  
  3. def get_context_data(self, **kwargs):
  4. # Call the base implementation first to get a context
  5. context = super().get_context_data(**kwargs)
  6. # Add in the publisher
  7. context['publisher'] = self.publisher
  8. 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:

  1. # models.py
  2. from django.db import models
  3.  
  4. class Author(models.Model):
  5. salutation = models.CharField(max_length=10)
  6. name = models.CharField(max_length=200)
  7. email = models.EmailField()
  8. headshot = models.ImageField(upload_to='author_headshots')
  9. 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:

  1. from django.urls import path
  2. from books.views import AuthorDetailView
  3.  
  4. urlpatterns = [
  5. #...
  6. path('authors/<int:pk>/', AuthorDetailView.as_view(), name='author-detail'),
  7. ]

Then we'd write our new view — get_object is the method that retrieves theobject — so we simply override it and wrap the call:

  1. from django.utils import timezone
  2. from django.views.generic import DetailView
  3. from books.models import Author
  4.  
  5. class AuthorDetailView(DetailView):
  6.  
  7. queryset = Author.objects.all()
  8.  
  9. def get_object(self):
  10. # Call the superclass
  11. object = super().get_object()
  12. # Record the last accessed date
  13. object.last_accessed = timezone.now()
  14. object.save()
  15. # Return the object
  16. return object

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_kwargon the view. More details can be found in the reference forDetailView