Add pagination to blog post list
This commit is contained in:
parent
bf097b9bb7
commit
0fbd36ce3e
6 changed files with 112 additions and 24 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 %}
|
||||
|
|
5
website/common/serializers.py
Normal file
5
website/common/serializers.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
|
||||
class PaginationSerializer(serializers.Serializer):
|
||||
page = serializers.IntegerField(min_value=1, default=1)
|
33
website/common/templates/common/pagination.html
Normal file
33
website/common/templates/common/pagination.html
Normal 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">…</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">…</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>
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue