I have a Django app that displays posts that are created using Django's built-in admin interface. The posts have tags incorporated using django-taggit (https://django-taggit.readthedocs.io/en/latest/)
The main page (home.html
) is set up to display posts and tags and when a tag is clicked, it takes you to a page (tag_posts.html
) with all tagged posts e.g. If i click on a post with the tag 'apple', I am presented with a page that shows all posts tagged with 'apple'. The main page works as intended, as does the pagination for home.html
.
THE ISSUE: When viewing the list of tagged posts, it is showing the number of posts by the number specifed with paginate_by
(in the code it's 2) but not showing me the option to click next/previous or page numbers.
What I Have Attempted:
I thought it may be the Bootstrap navigation links in the html file but I am using the same as in my home.html, which works.
re-factored my class-based view to include the tag as part of the context and supplying to the html as a context variable
Used basic html for navigation links
The issue lies with TagPostsView
Here is my view.py:
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponse
from django.views.generic import ListView, TemplateView
from .models import Session, PostsInSession
from django.core.paginator import Paginator
from taggit.models import Tag
class PostView(ListView):
queryset = Session.objects.prefetch_related('postsinsession_set').all()
context_object_name = 'sessions'
template_name = 'home.html'
paginate_by = 2 # Number of items per page
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tags = Tag.objects.order_by('name')
paginator = Paginator(self.queryset, self.paginate_by)
page = self.request.GET.get('page')
sessions = paginator.get_page(page)
context['sessions'] = sessions
context['tags'] = tags
return context
class TagPostsView(ListView):
template_name = 'tag_posts.html'
context_object_name = 'posts'
paginate_by = 2
def get_queryset(self):
tag_slug = self.get_tag_slug()
posts = PostsInSession.objects.filter(post__tags__slug=tag_slug)
return posts
def get_tag_slug(self):
return self.kwargs['tag_slug']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tag_slug = self.get_tag_slug()
tag = get_object_or_404(Tag, slug=tag_slug)
paginator = Paginator(context['posts'], self.paginate_by)
page = self.request.GET.get('page')
paginated_posts = paginator.get_page(page)
context['tag'] = tag
context['posts'] = paginated_posts
return context
tag_posts.html:
{% extends "base.html" %}
{% load markdownify %}
{% block content %}
<div class="row">
<div class="col">
<br>
{% if tag %}
<h2>Tag: {{ tag.name }}</h2>
{% endif %}
<hr>
{% for post in posts %}
<div class="card m-3 text-center">
<div class="card-header">
{{ post.post.title }}
</div>
<div class="card-body">
{{ post.post.body|markdownify }}
</div>
</div>
{% endfor %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if posts.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ posts.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% endif %}
{% for num in posts.paginator.page_range %}
{% if posts.number == num %}
<li class="page-item active" aria-current="page">
<span class="page-link">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if posts.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ posts.next_page_number }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
{% endblock %}
I found the solution in the end.
The paginator instance in get_context_data()
of the TagPostsView
should take the queryset as opposed to the context['posts']
.
This is because the Paginator class takes the queryset and splits into page objects (see: https://docs.djangoproject.com/en/4.2/topics/pagination/).
My original code was paginating the already paginated posts. Here is the fixed code:
def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) tag_slug = self.get_tag_slug() tag = get_object_or_404(Tag, slug=tag_slug) paginator = Paginator(self.get_queryset(), self.paginate_by) page = self.request.GET.get('page') paginated_posts = paginator.get_page(page) context['tag'] = tag context['posts'] = paginated_posts return context