Skip to content

From Function Views to Generic Views

A rescue can have hundreds of animals, and the list page shouldn't query and render all of them at once. Every animal that isn't adopted is on our one page now, so we need to split the list into pages.

Generic Views

Every list page runs a query and renders a template. Every detail page gets one row, or a 404, and renders a template. This quickly introduces a lot of duplicated code.

Django's generic views are class-based views that aim to provide boilerplate for these patterns.

  • ListView handles a list of a model's rows and provides pagination.
  • DetailView handles one row's page.

To use a class-based view, subclass it and override attributes and methods with your own values.

Generic views are built from mixins: classes that add methods to another class through inheritance, and aren't used alone.

ListView

We'll replace animal_list() in animals/views.py with a class. First, import ListView at the top of the file:

animals/views.py
from django.shortcuts import get_object_or_404, render
from django.views.generic import ListView

from . import queries
from .models import Animal

Then replace animal_list():

animals/views.py
class AnimalListView(ListView):
    paginate_by = 12

    def get_queryset(self):
        return queries.listed_animals()

Until the URL pattern uses the class, the running server stops with an AttributeError.

When a GET request comes in, ListView runs these steps, and we can override each one:

  1. get_queryset(): Returns the rows to list. By default it's every row of the class's model. Ours returns our query. In a method, the request is self.request.
  2. paginate_by: Splits the rows into pages of 12, and reads the page number from the query string, such as ?page=2.
  3. get_context_data(): Builds the template's context, with the current page as page_obj.
  4. The template: Its name comes from the model: animals/animal_list.html, the app's label, then the model's name with _list.

Each generic view has its own attributes and methods. Django's docs list what must be set and what we can override. The community resource Classy Class-Based Views has a nicer interface, and shows each attribute's default value. A view that's missing a required attribute raises ImproperlyConfigured.

Our template is already named animals/animal_list.html. path() needs a function to call, so the URL pattern uses as_view(), which returns one.

In animals/urls.py, replace the list's pattern:

animals/urls.py
    path("", views.AnimalListView.as_view(), name="list"),

In animal_list.html, loop over the page instead of the whole list:

animals/templates/animals/animal_list.html
    {% for animal in page_obj %}

Then add the page links below the closing </ul>:

animals/templates/animals/animal_list.html
  <nav>
    {% if page_obj.has_previous %}
      <a href="{% querystring page=page_obj.previous_page_number %}">Previous</a>
    {% endif %}
    Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
    {% if page_obj.has_next %}
      <a href="{% querystring page=page_obj.next_page_number %}">Next</a>
    {% endif %}
  </nav>
  • has_previous and has_next: Whether there's a page before or after this one.
  • number and paginator.num_pages: This page's number, and how many pages there are.
  • {% querystring %}: Writes the current query string and sets the parameters we pass, so page=2 changes the page and keeps the rest.

Reload http://127.0.0.1:8000/. Page links are below the animals, and Next goes to ?page=2.

We'll replace animal_detail() with a DetailView in the exercise.