Django provides a great way to manage paginated data that is split across several pages with “Previous/Next” links.
How Pagination objects interact
We can open Python shell: python manage.py shell
.
# Import Paginator class
>>> from django.core.paginator import Paginator
# Create dummy posts that we'd like to paginate
>>> posts = ['1', '2', '3', '4', '5']
# Create paginator objects
>>> p = Paginator(objects, 2) # Create 2 posts per page
# Number of posts
>>> p.count
5
# Number of pages
>>> p.num_pages
3
# Check page range
>>> p.page_range
range(1, 3)
# Loop over the pages
>>> for page in p.page_range
... print(page)
# Look at each page
>>> page1 = p.page(1)
>>> page1
<Page 1 of 3>
# Check the page number
>>> page1.number
1
# Check posts on the page
>>> page1.object_list
['1', '2']
# Create variable for page 2
>>> page2 = p.page(2)
>>> page2.object_list
['3', '4']
# Check if page 2 has a previous page
>>> page2.has_previous()
True
# Check if page 2 has a next page
>>> page2.has_next()
True
# Check page 2's next page number
>>> page2.next_page_number()
3
# Check page 2's previous page number
>>> page2.previous_page_number()
1
# Check if page 2 has other pages
>>> page2.has_other_pages()
True
# The 1-based index of the first item on this page
>>> page2.start_index()
3
# The 1-based index of the last item on this page
>>> page2.end_index()
4
# Check page 0
>>> p.page(0)
Traceback (most recent call last):
...
EmptyPage: That page number is less than 1
# Check page 4
>>> p.page(4)
Traceback (most recent call last):
...
EmptyPage: That page contains no results
paginate_by = 3
, so we have 3 posts per page:class PostListView(ListView):
model = Post
template_name = 'blog/index.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date']
paginate_by = 3
{% for post in posts %}
part.<!-- If the page is paginated -->
{% if is_paginated %}
<!-- If page has a previous page, give an option to go to the first page and previous page -->
{% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?page=1">First</a>
<a
class="btn btn-outline-info mb-4"
href="?page={{ page_obj.previous_page_number }}"
>Previous</a
>
{% endif %}
<!-- Navigate pages -->
{% for num in page_obj.paginator.page_range %}
<!-- Display current page -->
{% if page_obj.number == num %}
<a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
<!-- Display 2 pages before and after the current page -->
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% endif %} {% endfor %}
<!-- If page has a next page, give an option to go to the next page and final page -->
{% if page_obj.has_next %}
<a
class="btn btn-outline-info mb-4"
href="?page={{ page_obj.next_page_number }}"
>Next</a
>
<a
class="btn btn-outline-info mb-4"
href="?page={{ page_obj.paginator.num_pages }}"
>Last</a
>
{% endif %} {% endif %}
Import
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
paginate_by = 3
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username')) # If user doesn't exist
return Post.objects.filter(author=user).order_by('-date')
from django.urls import path
from .views import (
PostListView,
PostDetailView,
PostCreateView,
PostUpdateView,
PostDeleteView,
UserPostListView # Import UserPostListView
)
from . import views
urlpatterns = [
path('', PostListView.as_view(), name='blog-home'),
path('user/<str:username>', UserPostListView.as_view(), name='user-posts'), # add path
path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
path('post/new/', PostCreateView.as_view(), name='post-create'),
path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),
path('about/', views.about, name='blog-about'),
]
<h1 class="mb-3">Posts by {{ view.kwargs.username }} ({{ page_obj.paginator.count }})</h1>
<a class="mr-2" href="{% url 'user-posts' post.author.username %}">{{ post.author }}</a>
{% extends "blog/base.html" %} {% block content %}
<h1 class="mb-3">
Posts by {{ view.kwargs.username }} ({{ page_obj.paginator.count }})
</h1>
{% for post in posts %}
<article class="media content-section">
<img
class="rounded-circle article-img"
src="{{ post.author.profile.image.url }}"
/>
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{% url 'user-posts' post.author.username %}"
>{{ post.author }}</a
>
<small class="text-muted">{{ post.date|date:"F d, Y" }}</small>
</div>
<h2>
<a class="article-title" href="{% url 'post-detail' post.id %}"
>{{ post.title }}</a
>
</h2>
<p class="article-content">{{ post.content }}</p>
</div>
</article>
{% endfor %} {% if is_paginated %} {% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?page=1">First</a>
<a
class="btn btn-outline-info mb-4"
href="?page={{ page_obj.previous_page_number }}"
>Previous</a
>
{% endif %} {% for num in page_obj.paginator.page_range %} {% if page_obj.number
== num %}
<a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% endif %} {% endfor %} {% if page_obj.has_next %}
<a
class="btn btn-outline-info mb-4"
href="?page={{ page_obj.next_page_number }}"
>Next</a
>
<a
class="btn btn-outline-info mb-4"
href="?page={{ page_obj.paginator.num_pages }}"
>Last</a
>
{% endif %} {% endif %} {% endblock content %}
<a class="mr-2" href="{% url 'user-posts' post.author.username %}"
>{{ post.author }}</a
>
<a class="mr-2" href="{% url 'user-posts' object.author.username %}"
>{{ object.author }}</a
>
The difference is post_detail.html
has {{ object.author }}