Paginate the Main Feed
Now that we have the social feed, let's add pagination. Pagination works by splitting up a list or QuerySet into multiple equal-size chunks, which can then each be displayed on their own page.
The primary benefit of this is performance. If there were thousands or more instances of Photo
and Blog
in the database and we tried to load them all into the page at once, the load times would be astronomical. Therefore, it is best to paginate. Even sites that implement an infinite scroll will still load and display their resources in batches. Let's look at it together !
To paginate a page, use the django.core.paginator.Paginator
class.
You can provide Paginator
with any iterable (i.e., a list or a QuerySet) plus the number of instances you require per page. The Paginator
class will then split the iterable into Page
objects, each of which will hold the results for an individual page.
The page you are on is then stored as a query string in the URL.
What’s a query string?
A query string is a key-value pair that is stored in the URL. It is designated as coming after a?
.
If you take the URL https://fotoblog.com?page=5, the key is page
, and the value is 5
.
In Django, you can access this in the view via the request.GET
object.
So if you call request.GET.get('page')
, it would return 5
.
You then use the Paginator.get_page
method to return an object representing the page you are on.
Let’s have a go with our main feed.
from django.core.paginator import Paginator
def home(request):
blogs = models.Blog.objects.filter(
Q(author__in=request.user.follows) | Q(starred=True))
photos = models.Photo.objects.filter(
uploader__in=request.user.follows).exclude(
blog__in=blogs
)
blogs_and_photos = sorted(
chain(blogs, photos),
key=lambda instance: instance.date_created,
reverse=True
)
paginator = Paginator(blogs_and_photos, 6)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {'page_obj': page_obj}
return render(request, 'blog/home.html', context=context)
Now update the template to use the paginator. Instead of iterating through the original list, you now iterate through page_obj
instead.
{% extends 'base.html' %}
{% load blog_extras %}
{% block content %}
<h2>Your Feed</h2>
{% for instance in page_obj %}
{% if instance|model_type == 'Blog' %}
{% include 'blog/partials/blog_snippet.html' with blog=instance %}
{% elif instance|model_type == 'Photo' %}
{% include 'blog/partials/photo_snippet.html' with blog=instance %}
{% endif %}
{% endfor %}
{% endblock content %}
Let’s see how this looks.
The feed is now limited to six posts per page, but there is no way to navigate between the pages.
We also have to add a section at the end that tells the user the page number and allows them to navigate back and forth.
The page_obj
variable has some handy attributes you can use to generate this navigation tool.
Let’s see what it looks like in the template.
{% extends 'base.html' %}
{% load blog_extras %}
{% block content %}
<h2>Your Feed</h2>
{% for instance in page_obj %}
{% if instance|model_type == 'Blog' %}
{% include 'blog/partials/blog_snippet.html' with blog=instance %}
{% elif instance|model_type == 'Photo' %}
{% include 'blog/partials/photo_snippet.html' with blog=instance %}
{% endif %}
{% endfor %}
<span>
{% if page_obj.has_previous %}
<a href="?page=1">« first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span>
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
{% endblock content %}
What does the page look like now? Fingers crossed! 🤞
Nice! We can now access all of the posts via the navigation tool.
Now that you’ve added this to the main feed, it’s time to do the same with the photo feed.
Exercise: Paginate the Photo Feed
Go ahead and paginate the photo feed.
You will need to:
Update the
photo_feed
view to create an instance of thePaginator
and include thepage_obj
object in the context.Update the
photo_feed.html
to iterate throughpage_obj
instead of photos.Add the navigation section to the bottom of the page in
photo_feed.html
- hint - this is a reusable element you have shared between multiple templates. Do you know how to include this in both without repeating yourself?
And your site is complete! Congratulations! Make sure to check the solution of your final exercise against the solution.
Let's Recap!
Pagination improves the performance of a site by splitting large iterables into smaller chunks.
To paginate a view, provide the
Paginator
class with an iterable such as a QuerySet or list and the number of instances you want to display per page.Access the page number through a query string and use it to generate a
page_obj
that you pass to the template context.Use a partial template to include the navigation section to paginate multiple pages.
Now that you can paginate pages of your site, it’s time for a quiz.