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.
ListViewhandles a list of a model's rows and provides pagination.DetailViewhandles 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:
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():
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:
get_queryset(): Returns the rows to list. By default it's every row of the class'smodel. Ours returns our query. In a method, the request isself.request.paginate_by: Splits the rows into pages of 12, and reads the page number from the query string, such as?page=2.get_context_data(): Builds the template's context, with the current page aspage_obj.- 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:
path("", views.AnimalListView.as_view(), name="list"),
In animal_list.html, loop over the page instead of the whole list:
{% for animal in page_obj %}
Then add the page links below the closing </ul>:
<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_previousandhas_next: Whether there's a page before or after this one.numberandpaginator.num_pages: This page's number, and how many pages there are.{% querystring %}: Writes the current query string and sets the parameters we pass, sopage=2changes 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.