From 0fbd36ce3ef42ae1432ef37d4e2bf8712998d55b Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Fri, 26 Aug 2022 23:13:06 +0100 Subject: [PATCH] Add pagination to blog post list --- static/src/scss/_listing.scss | 24 ++++++++++ website/blog/models.py | 46 +++++++++++++------ .../templates/blog/blog_post_list_page.html | 23 ++++++---- website/common/serializers.py | 5 ++ .../common/templates/common/pagination.html | 33 +++++++++++++ website/search/serializers.py | 5 +- 6 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 website/common/serializers.py create mode 100644 website/common/templates/common/pagination.html diff --git a/static/src/scss/_listing.scss b/static/src/scss/_listing.scss index acc4471..278e73f 100644 --- a/static/src/scss/_listing.scss +++ b/static/src/scss/_listing.scss @@ -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; + } + } +} diff --git a/website/blog/models.py b/website/blog/models.py index 05419fa..44ef1ce 100644 --- a/website/blog/models.py +++ b/website/blog/models.py @@ -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 diff --git a/website/blog/templates/blog/blog_post_list_page.html b/website/blog/templates/blog/blog_post_list_page.html index 194b7c0..770bc99 100644 --- a/website/blog/templates/blog/blog_post_list_page.html +++ b/website/blog/templates/blog/blog_post_list_page.html @@ -13,16 +13,21 @@ {% block post_content %}
- {% for page in child_pages %} - {% if not filtering_by_tag %} - {% ifchanged %} - - {% endifchanged %} - {% endif %} + {% for page in pages %} + {% ifchanged %} + + {% endifchanged %} {% include "common/listing-item.html" %} {% endfor %} -
+ + + {% if pages.has_other_pages %} +
+
+ {% include "common/pagination.html" %} +
+ {% endif %} {% endblock %} diff --git a/website/common/serializers.py b/website/common/serializers.py new file mode 100644 index 0000000..b576791 --- /dev/null +++ b/website/common/serializers.py @@ -0,0 +1,5 @@ +from rest_framework import serializers + + +class PaginationSerializer(serializers.Serializer): + page = serializers.IntegerField(min_value=1, default=1) diff --git a/website/common/templates/common/pagination.html b/website/common/templates/common/pagination.html new file mode 100644 index 0000000..4384c7f --- /dev/null +++ b/website/common/templates/common/pagination.html @@ -0,0 +1,33 @@ +{% load wagtailadmin_tags %} + + diff --git a/website/search/serializers.py b/website/search/serializers.py index 95664a4..4935125 100644 --- a/website/search/serializers.py +++ b/website/search/serializers.py @@ -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)