Django Introduction Part6 - Generic list and detail views
In this article we’re going to complete the first version of the movie website by adding list and detail pages.
Movie list page
The movielist page will display a list of all the available movie records in the page with the title being a hyperlink to the associated movie detail page.
URL mapping
Open /catalog/urls.py and copy in the line shown in bold below. As for the index page, this path() function defines a pattern to match against the URL (‘movies/’), a view function that will be called if the URL matches (views.MovieListView.as_view()), and a name for this particular mapping.
1 | urlpatterns = [ |
For Django class-based views we access an appropriate view function by calling the class method as_view(). This does all the work of creating an instance of the class, and making sure that the right handler methods are called for incoming HTTP requests.
View (CBV)
Now, Let Us Use generic views: Less code is better.
We could quite easily write the movie list view as a regular function, which would query the database for all movies, and then call render() to pass the list to a specified template. Instead, however, we’re going to use a class-based generic list view (ListView) — a class that display a list of objects.
Because the generic view already implements most of the functionality we need and follows Django best-practice, we will be able to create a more robust list view with less code, less repetition, and ultimately less maintenance.
Open movie/views.py, and copy the following code into the bottom of the file:
1 | from django.views import generic |
That’s it! The generic view will query the database to get all records for the specified model (Movie) ,within the template you can access the list of movies with the template variable named object_list OR movie_list (i.e. generically “*the_model_name*_list”).
The view passes the context (list of movies) by default as
object_listandbook_listaliases; either will work.
Note: This awkward path for the template location isn’t a misprint — the generic views look for templates in /*application_name*/*the_model_name*_list.htmlinside the application’s /*application_name*/templates/ directory (/movie/templates/).
You can add attributes to change the default behaviour above. For example, you can specify another template file Possibly the most useful variation is to change/filter the subset of results that are returned — so instead of listing all movies you might list top 5 movies that were read by other users.
1 | from django.views.generic import View |
While we don’t need to do so here, you can also override some of the class methods.
For example, we can override the get_queryset() method to change the list of records returned. This is more flexible than just setting the queryset attribute as we did in the preceding code fragment (though there is no real benefit in this case):
1 | class MovieListView(generic.ListView): |
We might also override get_context_data() in order to pass additional context variables to the template (e.g. the list of movies is passed by default). The fragment below shows how to add a variable named “some_data” to the context (it would then be available as a template variable).
1 | class MovieListView(generic.ListView): |
When doing this it is important to follow the pattern used above:
- First get the existing context from our superclass.
- Then add your new context information.
- Then return the new (updated) context.
Movie detail page
The movie detail page will display information about a specific movie, , we’ll also list the ratings of the the related movies including the watching status.
URL mapping
In this case we use '<int:pk>' to capture the movieid, which must be a specially formatted string and pass it to the view as a parameter named pk (short for primary key). This is the id that is being used to store the movieuniquely in the database, as defined in the movieModel.
Important: The generic class-based detail view expects to be passed a parameter named pk. If you’re writing your own function view you can use whatever parameter name you like, or indeed pass the information in an unnamed argument.
1 | urlpatterns = [ |
Note that the The
DetailViewgeneric view expects the primary key value captured from the URL to be called"pk", so the name patterns has changed from<movie_id>to<pk>.
The pattern matching provided by path() is simple and useful for the (very common) cases where you just want to capture any string or integer. If you need more refined filtering (for example, to filter only strings that have a certain number of characters) then you can use the re_path() method.
This method is used just like path() except that it allows you to specify a pattern using a Regular expression. For example, the previous path could have been written as shown below:
1 | re_path(r'^movie/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'), |
(?P<name>…) - Capture the pattern (indicated by …) as a named variable (in this case “name”). The captured values are passed to the view with the name specified. Your view must therefore declare an argument with the same name!
Let’s consider a few real examples of patterns:
| Pattern | Description |
|---|---|
| r’^movie/(?P |
This is the RE used in our URL mapper. It captures all the digits (?Pmovie/1234 , and send a variable pk='1234' to the view. |
| r’^movie/(\d+)$' | This matches the same URLs as the preceding case. |
| r’^movie/(?P |
This matches a string that has book/ at the start of the line (^movie/), then has one or more characters that are either a ‘-’ or a word character ([-\w]+), and then ends. |
One feature that we haven’t used here, but which you may find valuable, is that you can declare and pass additional options to the view.
1 | path('url/', views.my_reused_view, {'my_template_name': 'some_path'}, name='aurl'), |
Slug
A “slug” is a way of generating a valid URL, generally using data already obtained. For instance, a slug uses the title of an article to generate a URL.
1 | # models.py |
View (CBV)
Open catalog/views.py, and copy the following code into the bottom of the file:
1 | class MovieDetailView(generic.DetailView): |
That’s it! All you need to do now is create a template called movie_detail.html, and the view will pass it the database information for the specific Movie record extracted by the URL mapper. Within the template you can access the list of movies with the template variable named object OR movie(i.e. generically “*the_model_name*”).
If a requested record does not exist then the generic class-based detail view will raise an
Http404exception for you automatically — in production, this will automatically display an appropriate “resource not found” page, which you can customise if desired.
Just to give you some idea of how this works, the code fragment below demonstrates how you would implement the class-based view as a function if you were not using the generic class-based detail view.
1 | def movie_detail_view(request, primary_key): |
Alternatively, we can use the get_object_or_404() function as a shortcut to to raise an Http404 exception if the record is not found.
1 | from django.shortcuts import get_object_or_404 |
Pagination
If you’ve just got a few records, our movie list page will look fine. However, as you get into the tens or hundreds of records the page will take progressively longer to load (and have far too much content to browse sensibly). The solution to this problem is to add pagination to your list views, reducing the number of items displayed on each page.
1 | from django.core.paginator import Paginator |
Django has excellent inbuilt support for pagination. Even better, this is built into the generic class-based list views so you don’t have to do very much to enable it!
Views(CBV)
Open movie/views.py, and add the paginate_by line shown in bold below.
1 | from django.views.generic import ListView |
With this addition, as soon as you have more than 10 records the view will start paginating the data it sends to the template.
Templates
Now that the data is paginated, we need to add support to the template to scroll through the results set. Because we might want to do this in all list views, we’ll do this in a way that can be added to the base template.
1 | {% include 'pagination.html' with page=page_obj %} |
Open /templates/pagination.html and copy in the following pagination code.
1 | {% block pagination %} |
The page_obj is a Paginator object that will exist if pagination is being used on the current page. It allows you to get all the information about the current page, previous pages, how many pages there are, etc.
We use {{ request.path }} to get the current page URL for creating the pagination links. This is useful because it is independent of the object that we’re paginating.
But since you can’t use sort_by() and pass a parameter (the same with filter() described above) you will have to choose between three choices:
- Add a
orderinginside aclass Metadeclaration on your model. - Add a
querysetattribute in your custom class-based view, specifying aorder_by(). - Adding a
get_querysetmethod to your custom class-based view and also specify theorder_by().






