From 8c4ce7b92ef6a88b447cb95169f5fb3802bb91db Mon Sep 17 00:00:00 2001 From: Warren Chen Date: Thu, 4 Jun 2026 15:29:06 +0900 Subject: [PATCH] Add sitemap, RSS feed, OG image, and pagination size controls --- innovedus_cms/home/feeds.py | 30 ++++++++++ innovedus_cms/home/models.py | 23 ++++++-- innovedus_cms/home/pagination.py | 56 +++++++++++++++++++ .../home/static/css/article_list.css | 29 ++++++++++ innovedus_cms/home/static/css/category.css | 8 +++ .../home/templates/home/article_page.html | 7 +++ .../home/includes/page-article-list.html | 29 +++++++--- innovedus_cms/home/views.py | 5 +- innovedus_cms/mysite/settings/base.py | 2 + innovedus_cms/mysite/static/css/mysite.css | 8 +++ innovedus_cms/mysite/templates/base.html | 7 +++ innovedus_cms/mysite/urls.py | 4 ++ innovedus_cms/search/views.py | 4 +- 13 files changed, 195 insertions(+), 17 deletions(-) create mode 100644 innovedus_cms/home/feeds.py create mode 100644 innovedus_cms/home/pagination.py diff --git a/innovedus_cms/home/feeds.py b/innovedus_cms/home/feeds.py new file mode 100644 index 0000000..ff473c0 --- /dev/null +++ b/innovedus_cms/home/feeds.py @@ -0,0 +1,30 @@ +from django.contrib.syndication.views import Feed +from django.utils.feedgenerator import Rss201rev2Feed +from django.utils.html import strip_tags + +from .models import ArticlePage + + +class LatestArticlesFeed(Feed): + feed_type = Rss201rev2Feed + title = "DeBuT AI 最新文章" + link = "/" + description = "DeBuT AI 最新文章 RSS feed" + + def items(self): + return ArticlePage.objects.live().order_by("-date", "-id")[:20] + + def item_title(self, item): + return item.title + + def item_description(self, item): + return strip_tags(item.intro or item.search_description or "") + + def item_link(self, item): + return item.url + + def item_pubdate(self, item): + return item.date + + def item_categories(self, item): + return list(item.tags.values_list("name", flat=True)) diff --git a/innovedus_cms/home/models.py b/innovedus_cms/home/models.py index 96d7320..5dfda27 100644 --- a/innovedus_cms/home/models.py +++ b/innovedus_cms/home/models.py @@ -8,6 +8,7 @@ from modelcluster.contrib.taggit import ClusterTaggableManager from modelcluster.fields import ParentalKey from taggit.models import TaggedItemBase from wagtail.search import index +from .pagination import build_pagination_context, get_page_size def _get_env_int(name, default): value = os.environ.get(name) @@ -62,7 +63,7 @@ class CategoryMixin: ArticlePage.objects.child_of(self) .live() .order_by("-date", "-id"), - PAGE_SIZE, + get_page_size(request, PAGE_SIZE), ) page_number = request.GET.get("page") if request else None @@ -78,7 +79,7 @@ class CategoryMixin: "title": self.title, "items": page_obj, "url": self.url, - "page_range": paginator.get_elided_page_range(page_obj.number), + "pagination": build_pagination_context(request, page_obj, paginator), } ) return blocks @@ -98,7 +99,8 @@ class CategoryMixin: else: # Paginated view paginator = Paginator( - ArticlePage.objects.live().order_by("-date", "-id"), PAGE_SIZE + ArticlePage.objects.live().order_by("-date", "-id"), + get_page_size(request, PAGE_SIZE), ) page_number = request.GET.get("page") @@ -112,7 +114,7 @@ class CategoryMixin: "title": self.title, "items": page_obj, "url": self.url, - "page_range": paginator.get_elided_page_range(page_obj.number), + "pagination": build_pagination_context(request, page_obj, paginator), } def get_trending_articles(self, request=None, exclude_ids=None): @@ -134,7 +136,7 @@ class CategoryMixin: } else: # Paginated view - paginator = Paginator(articles_qs, PAGE_SIZE) + paginator = Paginator(articles_qs, get_page_size(request, PAGE_SIZE)) page_number = request.GET.get("page") try: @@ -147,7 +149,7 @@ class CategoryMixin: "title": self.title, "items": page_obj, "url": self.url, - "page_range": paginator.get_elided_page_range(page_obj.number), + "pagination": build_pagination_context(request, page_obj, paginator), } @@ -310,6 +312,15 @@ class ArticlePage(Page, BreadcrumbMixin): def get_context(self, request): context = super().get_context(request) + if self.cover_image: + cover = self.cover_image.get_rendition("original") + context["og_image"] = { + "url": request.build_absolute_uri(cover.url), + "width": cover.width, + "height": cover.height, + "alt": self.title, + } + breadcrumbs, site_root = self.build_breadcrumbs() # context["breadcrumbs"] = breadcrumbs # context["breadcrumb_root"] = site_root diff --git a/innovedus_cms/home/pagination.py b/innovedus_cms/home/pagination.py new file mode 100644 index 0000000..cd7a7d9 --- /dev/null +++ b/innovedus_cms/home/pagination.py @@ -0,0 +1,56 @@ +PAGE_SIZE_OPTIONS = (10, 20, 30) + + +def get_page_size(request, default): + try: + page_size = int(request.GET.get("page_size", default)) + except (TypeError, ValueError): + return default + + return page_size if page_size in PAGE_SIZE_OPTIONS else default + + +def build_query_string(request, **updates): + query = request.GET.copy() + + for key, value in updates.items(): + if value is None: + query.pop(key, None) + else: + query[key] = value + + return query.urlencode() + + +def build_pagination_context(request, page_obj, paginator): + page_range = paginator.get_elided_page_range(page_obj.number) + page_size = paginator.per_page + + return { + "page_size": page_size, + "page_size_options": [ + { + "value": option, + "url": f"?{build_query_string(request, page_size=option, page=None)}", + "is_current": option == page_size, + } + for option in PAGE_SIZE_OPTIONS + ], + "pages": [ + { + "number": page_num, + "url": f"?{build_query_string(request, page=page_num)}" + if page_num != "…" + else "", + "is_current": page_num == page_obj.number, + "is_ellipsis": page_num == "…", + } + for page_num in page_range + ], + "previous_url": f"?{build_query_string(request, page=page_obj.previous_page_number())}" + if page_obj.has_previous() + else "", + "next_url": f"?{build_query_string(request, page=page_obj.next_page_number())}" + if page_obj.has_next() + else "", + } diff --git a/innovedus_cms/home/static/css/article_list.css b/innovedus_cms/home/static/css/article_list.css index dae7827..4fb498d 100644 --- a/innovedus_cms/home/static/css/article_list.css +++ b/innovedus_cms/home/static/css/article_list.css @@ -136,6 +136,35 @@ color: #0e1b4266; } +.page-size-selector { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + margin: 40px 0 20px; + font-size: 14px; +} + +.page-size-label { + color: #0e1b4266; +} + +.page-size-option { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 40px; + height: 32px; + border: 1px solid #0e1b42; + border-radius: 4px; + color: #0e1b42; +} + +.page-size-option.is-current { + background: #0e1b42; + color: #ffffff; +} + .pagination { display: flex; justify-content: center; diff --git a/innovedus_cms/home/static/css/category.css b/innovedus_cms/home/static/css/category.css index a748c31..222243b 100644 --- a/innovedus_cms/home/static/css/category.css +++ b/innovedus_cms/home/static/css/category.css @@ -3,6 +3,14 @@ color: #ffffff; } +.template-darkbackground .category-title { + height: 60px; +} + +.template-darkbackground .category-title span { + line-height: 60px; +} + .subcategory-title { display: flex; align-items: center; diff --git a/innovedus_cms/home/templates/home/article_page.html b/innovedus_cms/home/templates/home/article_page.html index 6979947..ed853a5 100644 --- a/innovedus_cms/home/templates/home/article_page.html +++ b/innovedus_cms/home/templates/home/article_page.html @@ -63,6 +63,13 @@ + + + + + + + {% endwith %} {% with tags=page.tags.all %} diff --git a/innovedus_cms/home/templates/home/includes/page-article-list.html b/innovedus_cms/home/templates/home/includes/page-article-list.html index 4e2c221..0908e66 100644 --- a/innovedus_cms/home/templates/home/includes/page-article-list.html +++ b/innovedus_cms/home/templates/home/includes/page-article-list.html @@ -6,10 +6,23 @@
{% include "home/includes/article_list.html" with items=category.items show_hero=show_hero empty_message=empty_message %} + {% if category.pagination.page_size_options %} +
+ 每頁 + {% for option in category.pagination.page_size_options %} + {% if option.is_current %} + {{ option.value }} + {% else %} + {{ option.value }} + {% endif %} + {% endfor %} +
+ {% endif %} + {% if category.items.paginator.num_pages > 1 %}