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:

  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 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 template might looklike the this:

  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 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:

  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 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 DetailViewgeneric view provides the publisher to the context, but how do we getadditional information in that template?

The answer is to subclass DetailViewand 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:

  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 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:

  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 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:

  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. 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:

  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)

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:

  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 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. obj = super().get_object()
  11. # Record the last accessed date
  12. obj.last_accessed = timezone.now()
  13. obj.save()
  14. 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_kwargon the view. More details can be found in the reference forDetailView