- Add published_date field to ArticlePage with auto-populate from first_published_at on first publish, plus data migration backfill - Surface go_live_at/expire_at scheduling fields in editor panels - Reorganise ArticlePage editor with TabbedInterface (Content, Metadata, Publishing, SEO tabs) - Add Articles PageListingViewSet to admin menu with custom columns (author, category, published date, status) and category/author filters - Add Articles summary dashboard panel showing drafts, scheduled, and recently published articles - Update all front-end queries and RSS feeds to use published_date - Add 10 unit tests and 4 E2E tests for new admin features Closes #39 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
106 lines
3.3 KiB
Python
106 lines
3.3 KiB
Python
from django.utils.html import format_html
|
|
|
|
import django_filters
|
|
from wagtail import hooks
|
|
from wagtail.admin.filters import WagtailFilterSet
|
|
from wagtail.admin.ui.components import Component
|
|
from wagtail.admin.ui.tables import Column, DateColumn
|
|
from wagtail.admin.ui.tables.pages import BulkActionsColumn, PageStatusColumn, PageTitleColumn
|
|
from wagtail.admin.viewsets.pages import PageListingViewSet
|
|
from wagtail.snippets.models import register_snippet
|
|
from wagtail.snippets.views.snippets import SnippetViewSet
|
|
|
|
from apps.authors.models import Author
|
|
from apps.blog.models import ArticlePage, Category, TagMetadata
|
|
|
|
|
|
class TagMetadataViewSet(SnippetViewSet):
|
|
model = TagMetadata
|
|
icon = "tag"
|
|
list_display = ["tag", "colour"]
|
|
|
|
|
|
register_snippet(TagMetadataViewSet)
|
|
|
|
|
|
class CategoryViewSet(SnippetViewSet):
|
|
model = Category
|
|
icon = "folder-open-inverse"
|
|
list_display = ["name", "slug", "show_in_nav", "sort_order"]
|
|
list_filter = ["show_in_nav"]
|
|
ordering = ["sort_order", "name"]
|
|
|
|
|
|
register_snippet(CategoryViewSet)
|
|
|
|
|
|
# ── Articles page listing ────────────────────────────────────────────────────
|
|
|
|
|
|
class ArticleFilterSet(WagtailFilterSet):
|
|
category = django_filters.ModelChoiceFilter(
|
|
queryset=Category.objects.all(),
|
|
empty_label="All categories",
|
|
)
|
|
author = django_filters.ModelChoiceFilter(
|
|
queryset=Author.objects.all(),
|
|
empty_label="All authors",
|
|
)
|
|
|
|
class Meta:
|
|
model = ArticlePage
|
|
fields = []
|
|
|
|
|
|
class ArticlePageListingViewSet(PageListingViewSet):
|
|
model = ArticlePage
|
|
icon = "doc-full"
|
|
menu_label = "Articles"
|
|
menu_order = 200
|
|
add_to_admin_menu = True
|
|
name = "articles"
|
|
columns = [
|
|
BulkActionsColumn("bulk_actions"),
|
|
PageTitleColumn("title", classname="title"),
|
|
Column("author", label="Author", sort_key="author__name"),
|
|
Column("category", label="Category"),
|
|
DateColumn("published_date", label="Published", sort_key="published_date"),
|
|
PageStatusColumn("status", sort_key="live"),
|
|
]
|
|
filterset_class = ArticleFilterSet
|
|
|
|
|
|
@hooks.register("register_admin_viewset")
|
|
def register_article_listing():
|
|
return ArticlePageListingViewSet("articles")
|
|
|
|
|
|
# ── Dashboard panel ──────────────────────────────────────────────────────────
|
|
|
|
|
|
class ArticlesSummaryPanel(Component):
|
|
name = "articles_summary"
|
|
template_name = "blog/panels/articles_summary.html"
|
|
order = 110
|
|
|
|
def get_context_data(self, parent_context):
|
|
context = super().get_context_data(parent_context)
|
|
context["drafts"] = (
|
|
ArticlePage.objects.not_live()
|
|
.order_by("-latest_revision_created_at")[:5]
|
|
)
|
|
context["scheduled"] = (
|
|
ArticlePage.objects.filter(go_live_at__isnull=False, live=False)
|
|
.order_by("go_live_at")[:5]
|
|
)
|
|
context["recent"] = (
|
|
ArticlePage.objects.live()
|
|
.order_by("-published_date")[:5]
|
|
)
|
|
return context
|
|
|
|
|
|
@hooks.register("construct_homepage_panels")
|
|
def add_articles_summary_panel(request, panels):
|
|
panels.append(ArticlesSummaryPanel())
|