Displaying a list of objects¶
To continue the example of an e-commerce site, let’s implement our product listing page. Our first version of the code is just this:
def product_list(request):
return TemplateResponse(request, 'shop/product_list.html', {
'products': Product.objects.all(),
})
A typical product listing page normally would have some more requirements, often including at least pagination. Django comes with a helpful Paginator class, with helpful docs showing you how to use it. With that added, you have something like this:
from django.core.paginator import Paginator
def product_list(request):
products = Product.objects.all()
paginator = Paginator(products, 5) # Show 5 products per page.
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return TemplateResponse(request, 'shop/product_list.html', {
'page_obj': page_obj,
})
That’s basically it! The Paginator.get_page method returns a Page object that has everything you need in the template — refer to the linked docs for all the details.
Your real view might have additional needs, like filtering
and ordering. These can be handled by responding to query string parameters and
modifying your products
QuerySet above.
There is a bit of boilerplate here for doing pagination. If you have a
standardised convention of using page
as your query string parameter for
paging, you could pull some of this boilerplate out into a utility like
paged_object_list_context
(left as an exercise for you) to produce something
a bit shorter:
def product_list(request):
products = Product.objects.all()
context = {
# ...
} | paged_object_list_context(request, products, paginate_by=5)
return TemplateResponse(request, 'shop/product_list.html', context)
Next up: Custom logic at the start — delegation
Discussion: Discovering re-usable units of code¶
I very deliberately left “write some code yourself” as an exercise. Adding these kind of utilities to your code is what every developer should learn to do, and in a project you would expect to have a small library of this kind of thing that is specific to your project and encapsulates your own patterns and conventions. This is far superior to contorting your code with method overrides that are necessary only because of the structure handed to you by someone else.
Yes, using paged_object_list_context
is very slightly longer than just
adding a paginate_by
attribute to a ListView
. But the benefits are huge
— you stay in full control of your view function and it remains readable and
extremely easy to debug or further customise. You also have a utility that is
separately testable, with a well-defined interface that means its very unlikely
to interact badly with the different contexts you might use it in.
The ListView alternative for this code is tempting, but it is short-sighted laziness (as opposed to long-sighted laziness that is an essential quality of every developer). Any real world view will quickly develop enough logic that you lose even in terms of code length, and much more so in terms of code complexity.
As well as paged_object_list_context
, we should also mention Paginator
,
which is a great example of the kind of re-usable functionality that you should
be looking for in your own projects. It has a single responsibility — it handles
pagination. It has a clearly defined interface that can be documented and
understood, and separately tested, and used outside of a web context.
I have various similar utility functions and classes in my own Django projects:
things like ExcelFormatter
and OdsFormatter
(simple abstractions over
creating spreadsheets, that share an interface so that the user can choose
between XLS or ODS files); small HTTP-level utilities that do redirections or
closing of popups; small glue utilities that encapsulate some small convention
or decision that needs to be applied in several places.
In my experience, these are the kinds of things that are less likely to come
out if you use mixins for re-using functionality. Mixins encourage big classes
with many methods that hide the layers of your code. I encourage you again to
watch Brandon Rhode’s analysis of the
Python stdlib socketserver
library, or his section on mixins in his article
on The Composition Over Inheritance Principle.