Add pagination to blog post list

This commit is contained in:
Jake Howard 2022-08-26 23:13:06 +01:00
parent bf097b9bb7
commit 0fbd36ce3e
Signed by: jake
GPG key ID: 57AFB45680EDD477
6 changed files with 112 additions and 24 deletions

View file

@ -46,3 +46,27 @@
margin-bottom: 1rem;
}
}
nav.pagination {
@include dark-mode {
a.pagination-link,
.pagination-next,
.pagination-previous {
background-color: $grey-darker;
border-color: $black;
color: $dark-mode-text;
&:hover {
border-color: $grey-dark;
}
&.is-current {
background-color: $primary;
}
}
.pagination-ellipsis {
color: $dark-mode-text;
}
}
}

View file

@ -1,9 +1,12 @@
from typing import Any
from django.core.paginator import EmptyPage
from django.core.paginator import Page as PaginatorPage
from django.core.paginator import Paginator
from django.db import models
from django.db.models.functions import TruncMonth
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.http.response import Http404, HttpResponse, HttpResponseBadRequest
from django.utils import timezone
from django.utils.functional import cached_property
from modelcluster.fields import ParentalManyToManyField
@ -12,6 +15,7 @@ from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.query import PageQuerySet
from website.common.models import BaseContentPage
from website.common.serializers import PaginationSerializer
from website.common.utils import TocEntry
@ -30,31 +34,47 @@ class BlogPostListPage(RoutablePageMixin, BaseContentPage):
@cached_property
def table_of_contents(self) -> list[TocEntry]:
post_months = [
dt.strftime("%Y-%m")
for dt in self.get_blog_posts()
.annotate(post_month=TruncMonth("date", output_field=models.DateField()))
.order_by("-post_month")
.values_list("post_month", flat=True)
.distinct()
]
post_months = sorted(
{
dt.strftime("%Y-%m")
for dt in self.paginator_page.object_list.annotate(
post_month=TruncMonth("date", output_field=models.DateField())
).values_list("post_month", flat=True)
}
)
return [TocEntry(post_month, post_month, 0, []) for post_month in post_months]
def get_blog_posts(self) -> PageQuerySet:
return BlogPostPage.objects.descendant_of(self).live()
def get_context(self, request: HttpRequest) -> dict:
context = super().get_context(request)
context["child_pages"] = (
@cached_property
def paginator_page(self) -> PaginatorPage:
pages = (
self.get_blog_posts()
.select_related("hero_image")
.select_related("hero_unsplash_photo")
.prefetch_related("tags")
.order_by("-date")
.order_by("-date", "title")
)
paginator = Paginator(pages, per_page=1)
try:
return paginator.page(self.serializer.validated_data["page"])
except EmptyPage:
raise Http404
def get_context(self, request: HttpRequest) -> dict:
context = super().get_context(request)
context["pages"] = self.paginator_page
return context
@route(r"^$")
def index_route(self, request: HttpRequest) -> HttpResponse:
self.serializer = PaginationSerializer(data=request.GET)
if not self.serializer.is_valid():
return HttpResponseBadRequest()
return super().index_route(request)
@route(r"^feed/$")
def feed(self, request: HttpRequest) -> HttpResponse:
from .views import BlogPostPageFeed

View file

@ -13,16 +13,21 @@
{% block post_content %}
<section class="container">
{% for page in child_pages %}
{% if not filtering_by_tag %}
{% ifchanged %}
<time datetime="{{ page.date|date:'c' }}" title='{{ page.date|date:"F Y" }}'>
<h3 id="{{ page.date|date:'Y-m' }}" class="date-header">{{ page.date|date:"Y-m" }}</h3>
</time>
{% endifchanged %}
{% endif %}
{% for page in pages %}
{% ifchanged %}
<time datetime="{{ page.date|date:'Y-m' }} title='{{ page.date|date:"F Y" }}'>
<h3 id="{{ page.date|date:'Y-m' }}" class="date-header">{{ page.date|date:"Y-m" }}</h3>
</time>
{% endifchanged %}
{% include "common/listing-item.html" %}
{% endfor %}
</section>
</section>
{% if pages.has_other_pages %}
<section class="container">
<hr class="my-5" />
{% include "common/pagination.html" %}
</section>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,5 @@
from rest_framework import serializers
class PaginationSerializer(serializers.Serializer):
page = serializers.IntegerField(min_value=1, default=1)

View file

@ -0,0 +1,33 @@
{% load wagtailadmin_tags %}
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
{% if pages.has_previous %}
<a class="pagination-previous" href="{% querystring page=pages.previous_page_number %}">Previous</a>
{% endif %}
{% if pages.has_next %}
<a class="pagination-next" href="{% querystring page=pages.next_page_number %}">Next page</a>
{% endif %}
<ul class="pagination-list">
{% if pages.has_previous and pages.previous_page_number != 1 %}
<li><a class="pagination-link" aria-label="Goto page 1" href="{% querystring page=1 %}">1</a></li>
<li><span class="pagination-ellipsis">&hellip;</span></li>
{% endif %}
{% if pages.has_previous %}
<li><a class="pagination-link" aria-label="Goto page {{ pages.previous_page_number }}" href="{% querystring page=pages.previous_page_number %}">{{ pages.previous_page_number }}</a></li>
{% endif %}
<li><a class="pagination-link is-current" aria-label="Page {{ pages.number }}" aria-current="page">{{ pages.number }}</a></li>
{% if pages.has_next %}
<li><a class="pagination-link" aria-label="Goto page {{ pages.next_page_number }}" href="{% querystring page=pages.next_page_number %}">{{ pages.next_page_number }}</a></li>
{% endif %}
{% if pages.has_next and pages.next_page_number != pages.paginator.num_pages %}
<li><span class="pagination-ellipsis">&hellip;</span></li>
<li><a class="pagination-link" aria-label="Goto page {{ pages.paginator.num_pages }}" href="{% querystring page=pages.paginator.num_pages %}">{{ pages.paginator.num_pages }}</a></li>
{% endif %}
</ul>
</nav>

View file

@ -1,8 +1,9 @@
from rest_framework import serializers
from website.common.serializers import PaginationSerializer
MIN_SEARCH_LENGTH = 3
class SearchParamsSerializer(serializers.Serializer):
class SearchParamsSerializer(PaginationSerializer):
q = serializers.CharField(min_length=MIN_SEARCH_LENGTH)
page = serializers.IntegerField(min_value=1, default=1)