diff --git a/cms/articles/models.py b/cms/articles/models.py index 1a006e1b..f450334f 100644 --- a/cms/articles/models.py +++ b/cms/articles/models.py @@ -86,6 +86,7 @@ class StatisticalArticlePage(BundledPageMixin, BasePage): # type: ignore[django parent_page_types: ClassVar[list[str]] = ["ArticleSeriesPage"] subpage_types: ClassVar[list[str]] = [] template = "templates/pages/statistical_article_page.html" + label = _("Article") # Fields news_headline = models.CharField(max_length=255, blank=True) diff --git a/cms/articles/tests/factories.py b/cms/articles/tests/factories.py index c5fc788c..827e0761 100644 --- a/cms/articles/tests/factories.py +++ b/cms/articles/tests/factories.py @@ -18,7 +18,7 @@ class HeadlineFigureBlockFactory(wagtail_factories.StructBlockFactory): trend = wagtail_factories.CharBlockFactory() -class ArticleSeriesFactory(wagtail_factories.PageFactory): +class ArticleSeriesPageFactory(wagtail_factories.PageFactory): """Factory for ArticleSeriesPage.""" class Meta: @@ -36,7 +36,7 @@ class Meta: django_get_or_create: ClassVar[list[str]] = ["slug", "parent"] title = factory.Faker("sentence", nb_words=4) - parent = factory.SubFactory(ArticleSeriesFactory) + parent = factory.SubFactory(ArticleSeriesPageFactory) summary = factory.Faker("text", max_nb_chars=100) news_headline = factory.Faker("text", max_nb_chars=50) diff --git a/cms/articles/tests/test_models.py b/cms/articles/tests/test_models.py index f9f54403..105d773d 100644 --- a/cms/articles/tests/test_models.py +++ b/cms/articles/tests/test_models.py @@ -7,7 +7,7 @@ from django.utils.formats import date_format from wagtail.test.utils import WagtailTestUtils -from cms.articles.tests.factories import ArticleSeriesFactory, StatisticalArticlePageFactory +from cms.articles.tests.factories import ArticleSeriesPageFactory, StatisticalArticlePageFactory from cms.core.tests.factories import ContactDetailsFactory @@ -16,7 +16,7 @@ class ArticleSeriesTestCase(WagtailTestUtils, TestCase): @classmethod def setUpTestData(cls): - cls.series = ArticleSeriesFactory() + cls.series = ArticleSeriesPageFactory() def test_index_redirect_404_with_no_subpages(self): """Test index path redirects to latest.""" diff --git a/cms/core/models/base.py b/cms/core/models/base.py index dd3c361a..edda1384 100644 --- a/cms/core/models/base.py +++ b/cms/core/models/base.py @@ -3,6 +3,7 @@ from django.conf import settings from django.utils.decorators import method_decorator from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ from wagtail.models import Page from wagtail.query import PageQuerySet @@ -49,6 +50,9 @@ class BasePage(ListingFieldsMixin, SocialFieldsMixin, Page): # type: ignore[dja # Update in your specific Page class if the StreamField using them is different. content_field_name: str = "content" + # used a page type label in the front-end + label = _("Page") + class Meta: abstract = True diff --git a/cms/core/tests/test_utils.py b/cms/core/tests/test_utils.py new file mode 100644 index 00000000..cd9d5efa --- /dev/null +++ b/cms/core/tests/test_utils.py @@ -0,0 +1,132 @@ +from datetime import datetime + +from django.test import TestCase +from django.utils.formats import date_format + +from cms.core.models.base import BasePage +from cms.core.utils import get_formatted_pages_list + + +# DummyPage mimics the minimum attributes and methods of a Wagtail Page. +class DummyPage(BasePage): + def __init__(self, title, summary="", listing_summary="", url="https://ons.gov.uk", **kwargs): # pylint: disable=super-init-not-called + # this just set attributes manually. + self.title = title + self.summary = summary + self.listing_summary = listing_summary + self._url = url + self._release_date = kwargs.get("release_date") + + def get_url(self, request=None, current_site=None): + return self._url + + @property + def release_date(self): + return self._release_date + + class Meta: + abstract = True + + +class DummyPageWithNoReleaseDate(DummyPage): + label = "Dummy Page" + + class Meta: + abstract = True + + +class GetFormattedPagesListTests(TestCase): + def test_without_release_date_and_listing_summary(self): + # When no listing_summary and release_date, should use summary for description, + # and use the default label. + page = DummyPage(title="Test Page", summary="Test summary", listing_summary="") + result = get_formatted_pages_list([page]) + expected = { + "title": {"text": "Test Page", "url": "https://ons.gov.uk"}, + "metadata": {"object": {"text": "Page"}}, + "description": "Test summary", + } + self.assertEqual(len(result), 1) + self.assertDictEqual(result[0], expected) + + def test_with_listing_summary_overrides_summary(self): + # When listing_summary is provided, that should be used as description. + page = DummyPage(title="Test Page", summary="Test summary", listing_summary="Listing summary") + result = get_formatted_pages_list([page]) + expected = { + "title": {"text": "Test Page", "url": "https://ons.gov.uk"}, + "metadata": {"object": {"text": "Page"}}, + "description": "Listing summary", + } + self.assertEqual(len(result), 1) + self.assertDictEqual(result[0], expected) + + def test_with_custom_label(self): + # When a custom label is defined, it should be used in metadata. + page = DummyPageWithNoReleaseDate(title="Test Page", summary="Test summary", listing_summary="") + result = get_formatted_pages_list([page]) + expected = { + "title": {"text": "Test Page", "url": "https://ons.gov.uk"}, + "metadata": {"object": {"text": "Dummy Page"}}, + "description": "Test summary", + } + self.assertEqual(len(result), 1) + self.assertDictEqual(result[0], expected) + + def test_with_release_date(self): + # When release_date is provided, metadata should include date formatting. + test_date = datetime(2024, 1, 1, 12, 30) + page = DummyPage(title="Test Page", summary="Test summary", listing_summary="", release_date=test_date) + result = get_formatted_pages_list([page]) + + expected_iso = date_format(test_date, "c") + expected_short = date_format(test_date, "DATE_FORMAT") + + expected = { + "title": {"text": "Test Page", "url": "https://ons.gov.uk"}, + "metadata": { + "object": {"text": "Page"}, + "date": { + "prefix": "Released", + "showPrefix": True, + "iso": expected_iso, + "short": expected_short, + }, + }, + "description": "Test summary", + } + self.assertEqual(len(result), 1) + self.assertDictEqual(result[0], expected) + + def test_multiple_pages(self): + # Test processing multiple dummy pages + test_date = datetime(2024, 1, 1, 12, 30) + page1 = DummyPage(title="Page One", summary="Summary One", listing_summary="", release_date=test_date) + page2 = DummyPageWithNoReleaseDate(title="Page Two", summary="Summary Two", listing_summary="Listing Two") + pages = [page1, page2] + result = get_formatted_pages_list(pages) + + expected_iso = date_format(test_date, "c") + expected_short = date_format(test_date, "DATE_FORMAT") + + expected_page1 = { + "title": {"text": "Page One", "url": "https://ons.gov.uk"}, + "metadata": { + "object": {"text": "Page"}, + "date": { + "prefix": "Released", + "showPrefix": True, + "iso": expected_iso, + "short": expected_short, + }, + }, + "description": "Summary One", + } + expected_page2 = { + "title": {"text": "Page Two", "url": "https://ons.gov.uk"}, + "metadata": {"object": {"text": "Dummy Page"}}, + "description": "Listing Two", + } + self.assertEqual(len(result), 2) + self.assertDictEqual(result[0], expected_page1) + self.assertDictEqual(result[1], expected_page2) diff --git a/cms/core/utils.py b/cms/core/utils.py new file mode 100644 index 00000000..25f955c5 --- /dev/null +++ b/cms/core/utils.py @@ -0,0 +1,45 @@ +from typing import TYPE_CHECKING, Any, Optional, TypedDict + +from django.db.models import QuerySet +from django.utils.formats import date_format +from django.utils.translation import gettext_lazy as _ + +if TYPE_CHECKING: + from django.http import HttpRequest + from wagtail.models import Page + + +class DocumentListItem(TypedDict): + title: dict[str, str] + metadata: dict[str, Any] + description: str + + +def get_formatted_pages_list( + pages: list["Page"] | QuerySet["Page"], request: Optional["HttpRequest"] = None +) -> list[DocumentListItem]: + """Returns a formatted list of page data for the documentList DS macro. + + See the search results section in https://service-manual.ons.gov.uk/design-system/components/document-list. + """ + data = [] + for page in pages: + datum: DocumentListItem = { + "title": { + "text": getattr(page, "display_title", page.title), + "url": page.get_url(request=request), + }, + "metadata": { + "object": {"text": getattr(page, "label", _("Page"))}, + }, + "description": getattr(page, "listing_summary", "") or getattr(page, "summary", ""), + } + if release_date := page.release_date: + datum["metadata"]["date"] = { + "prefix": _("Released"), + "showPrefix": True, + "iso": date_format(release_date, "c"), + "short": date_format(release_date, "DATE_FORMAT"), + } + data.append(datum) + return data diff --git a/cms/jinja2/templates/components/featured/featured-article.html b/cms/jinja2/templates/components/featured/featured-article.html new file mode 100644 index 00000000..94cfe4ab --- /dev/null +++ b/cms/jinja2/templates/components/featured/featured-article.html @@ -0,0 +1,26 @@ +