• 12 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/2/22

Paginate the Feed

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.

Screenshot of feed
Screenshot of feed

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! 🤞

Screenshot of the feed page with navigation
Screenshot of the feed page with navigation

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 the  Paginator  and include the  page_obj  object in the context.

  • Update the  photo_feed.html  to iterate through  page_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.

Example of certificate of achievement
Example of certificate of achievement