feat: improve Wagtail admin editor experience for articles
- 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>
This commit is contained in:
@@ -11,7 +11,7 @@ from django.shortcuts import get_object_or_404
|
||||
from modelcluster.contrib.taggit import ClusterTaggableManager
|
||||
from modelcluster.fields import ParentalKey
|
||||
from taggit.models import Tag, TaggedItemBase
|
||||
from wagtail.admin.panels import FieldPanel, PageChooserPanel
|
||||
from wagtail.admin.panels import FieldPanel, ObjectList, PageChooserPanel, TabbedInterface
|
||||
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
|
||||
from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.models import Page
|
||||
@@ -38,7 +38,7 @@ class HomePage(Page):
|
||||
.public()
|
||||
.select_related("author", "category")
|
||||
.prefetch_related("tags__metadata")
|
||||
.order_by("-first_published_at")
|
||||
.order_by("-published_date")
|
||||
)
|
||||
articles = list(articles_qs[:5])
|
||||
ctx["featured_article"] = self.featured_article
|
||||
@@ -64,7 +64,7 @@ class ArticleIndexPage(RoutablePageMixin, Page):
|
||||
.live()
|
||||
.select_related("author", "category")
|
||||
.prefetch_related("tags__metadata")
|
||||
.order_by("-first_published_at")
|
||||
.order_by("-published_date")
|
||||
)
|
||||
|
||||
def get_category_url(self, category):
|
||||
@@ -191,21 +191,46 @@ class ArticlePage(SeoMixin, Page):
|
||||
tags = ClusterTaggableManager(through="blog.ArticleTag", blank=True)
|
||||
read_time_mins = models.PositiveIntegerField(editable=False, default=1)
|
||||
comments_enabled = models.BooleanField(default=True)
|
||||
published_date = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Display date for this article. Auto-set on first publish if left blank.",
|
||||
)
|
||||
|
||||
parent_page_types = ["blog.ArticleIndexPage"]
|
||||
subpage_types: list[str] = []
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("category"),
|
||||
FieldPanel("author"),
|
||||
FieldPanel("hero_image"),
|
||||
content_panels = [
|
||||
FieldPanel("title"),
|
||||
FieldPanel("summary"),
|
||||
FieldPanel("body"),
|
||||
]
|
||||
|
||||
metadata_panels = [
|
||||
FieldPanel("category"),
|
||||
FieldPanel("author"),
|
||||
FieldPanel("tags"),
|
||||
FieldPanel("hero_image"),
|
||||
FieldPanel("comments_enabled"),
|
||||
]
|
||||
|
||||
promote_panels = Page.promote_panels + SeoMixin.seo_panels
|
||||
publishing_panels = [
|
||||
FieldPanel("published_date"),
|
||||
FieldPanel("go_live_at"),
|
||||
FieldPanel("expire_at"),
|
||||
]
|
||||
|
||||
edit_handler = TabbedInterface(
|
||||
[
|
||||
ObjectList(content_panels, heading="Content"),
|
||||
ObjectList(metadata_panels, heading="Metadata"),
|
||||
ObjectList(publishing_panels, heading="Publishing"),
|
||||
ObjectList(
|
||||
Page.promote_panels + SeoMixin.seo_panels,
|
||||
heading="SEO",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
search_fields = Page.search_fields
|
||||
|
||||
@@ -215,6 +240,8 @@ class ArticlePage(SeoMixin, Page):
|
||||
slug="general",
|
||||
defaults={"name": "General", "description": "General articles", "colour": "neutral"},
|
||||
)
|
||||
if not self.published_date and self.first_published_at:
|
||||
self.published_date = self.first_published_at
|
||||
self.read_time_mins = self._compute_read_time()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@@ -239,14 +266,14 @@ class ArticlePage(SeoMixin, Page):
|
||||
.filter(tags__in=tag_ids)
|
||||
.exclude(pk=self.pk)
|
||||
.distinct()
|
||||
.order_by("-first_published_at")[:count]
|
||||
.order_by("-published_date")[:count]
|
||||
)
|
||||
if len(related) < count:
|
||||
exclude_ids = [a.pk for a in related] + [self.pk]
|
||||
fallback = list(
|
||||
ArticlePage.objects.live()
|
||||
.exclude(pk__in=exclude_ids)
|
||||
.order_by("-first_published_at")[: count - len(related)]
|
||||
.order_by("-published_date")[: count - len(related)]
|
||||
)
|
||||
return related + fallback
|
||||
return related
|
||||
|
||||
Reference in New Issue
Block a user