Skip to content

Commit

Permalink
Topic page enhancements (#96)
Browse files Browse the repository at this point in the history
* Add featured series, explore more, related articles/methodologies
* Exclude local settings from mypy
* Add toc, featured article and processed articles/methodologies
* Translate missed string in template
* Add component for featured article
* Add a label/release date properties to relevant page models
* Update the topic template
* Wire in the Explore More block
* Handle highlighted articles/methodologies
* Add tests
* Tweak the featured and highlight choosers to relevant pages only
* Tweak so related articles display the full title (with series prefix)
* Add basic functional tests
  • Loading branch information
zerolab authored Feb 13, 2025
1 parent 39038e4 commit ec5da7c
Show file tree
Hide file tree
Showing 32 changed files with 1,472 additions and 54 deletions.
1 change: 1 addition & 0 deletions cms/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions cms/articles/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions cms/articles/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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."""
Expand Down
4 changes: 4 additions & 0 deletions cms/core/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
132 changes: 132 additions & 0 deletions cms/core/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -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)
45 changes: 45 additions & 0 deletions cms/core/utils.py
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions cms/jinja2/templates/components/featured/featured-article.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div class="featured-article">
<h3 class="ons-u-fs-m ons-u-mt-no ons-u-mb-2xs">
<a href="{{ pageurl(article) }}">{{ article.display_title }}</a>
</h3>
<ul class="featured-article__metadata">
<li class="featured-article__attribute ons-u-fs-s">
<span class="ons-u-fw-b">{{ _("Released") }}:</span>
<time datetime="{{ article.release_date|date("c") }}">{{ article.release_date|date("DATE_FORMAT") }}</time>
</li>
<li class="featured-article__attribute ons-u-fs-s">
<span class="ons-u-fw-b">{{ _("Article") }}</span>
</li>
</ul>

{% if article.listing_image %}
<div class="featured-article__embed-container">
{{ image(article.listing_image, "width-1000") }}
</div>
{% endif %}

{% if article.main_points_summary %}
<div class="featured-article__description">
{{ article.main_points_summary|richtext }}
</div>
{% endif %}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{% from "components/document-list/_macro.njk" import onsDocumentList %}
{{ onsDocumentList({"documents": formatted_items}) }}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ <h1 class="ons-u-fs-3xl common-header__heading">
</div>
<div class="ons-grid__col ons-col-8@m ons-col-12@s ons-u-p-no@2xs@m">
<section id="summary" class="spacing">
<h2>Summary</h2>
<h2>{{ _("Summary") }}</h2>
{{ page.summary|richtext() }}
</section>

Expand Down
55 changes: 52 additions & 3 deletions cms/jinja2/templates/pages/topic_page.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{% extends "templates/base_page.html" %}
{% from "components/table-of-contents/_macro.njk" import onsTableOfContents %}
{% from "components/document-list/_macro.njk" import onsDocumentList %}

{% block header_area %}
<div class="topic-header">
Expand All @@ -21,9 +23,56 @@ <h1 class="ons-u-fs-3xl common-header__heading">
</div>
</div>
</div>
{% endblock %}

{% block main %}
<div class="ons-grid ons-grid-flex-gap ons-grid-flex-gap--32 ons-js-toc-container">
<div class="ons-grid__col ons-grid__col--sticky@m ons-col-4@m">
{% with toc_title=_("Contents"), toc_aria_label=_("Sections in this page") %}
{# fmt:off #}
{{-
onsTableOfContents({
"title": toc_title,
"ariaLabel": toc_aria_label,
"itemsList": table_of_contents
})
}}
{# fmt:on #}
{% endwith %}
</div>

<div class="ons-grid__col ons-col-8@m">
{% if featured_item %}
<section id="featured" class="spacing">
<h2>{{ _("Featured") }}</h2>

{% with article=featured_item %}
{% include "templates/components/featured/featured-article.html" %}
{% endwith %}
</section>
{% endif %}

{% if page.headline_figures %}
{% include_block page.headline_figures %}
{% endif %}
{% if formatted_articles %}
<section id="related-articles" class="spacing">
<h2>{{ _("Related articles") }}</h2>
{{ onsDocumentList({"documents": formatted_articles}) }}
</section>
{% endif %}

{% if formatted_methodologies %}
<section id="related-methods" class="spacing">
<h2>{{ _("Methods and quality information") }}</h2>
{{ onsDocumentList({"documents": formatted_methodologies}) }}
</section>
{% endif %}

{% if page.explore_more %}
<section id="explore-more">
<h2>{{ _("Explore more") }}</h2>

{% include_block page.explore_more %}
</section>
{% endif %}
</div>
</div>
{% endblock %}
8 changes: 7 additions & 1 deletion cms/methodology/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from cms.core.query import order_by_pk_position

if TYPE_CHECKING:
import datetime

from wagtail.admin.panels import Panel
from wagtail.query import PageQuerySet

Expand All @@ -37,8 +39,8 @@ class MethodologyRelatedPage(Orderable):

class MethodologyPage(BasePage): # type: ignore[django-manager-missing]
parent_page_types: ClassVar[list[str]] = ["topics.TopicPage"]

template = "templates/pages/methodology_page.html"
label = _("Methodology")

summary = RichTextField(features=settings.RICH_TEXT_BASIC)
publication_date = models.DateField()
Expand Down Expand Up @@ -97,6 +99,10 @@ def get_context(self, request: HttpRequest, *args: Any, **kwargs: Any) -> dict:
context["related_publications"] = self.get_formatted_related_publications_list(request=request)
return context

@property
def release_date(self) -> "datetime.date":
return self.publication_date

@cached_property
def related_publications(self) -> "PageQuerySet":
"""Return a `PageQuerySet` of the StatisticalArticlePage page model via the
Expand Down
Loading

0 comments on commit ec5da7c

Please sign in to comment.