Skip to content

Commit

Permalink
Register new wagtail hook to provide full URL as href to <a> tag fo…
Browse files Browse the repository at this point in the history
…r rendered internal links
  • Loading branch information
A-Ashiq committed Sep 6, 2024
1 parent d451b13 commit 33ce7d5
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 0 deletions.
47 changes: 47 additions & 0 deletions cms/dashboard/wagtail_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@
from django.templatetags.static import static
from django.utils.html import format_html
from django.utils.safestring import SafeString
from draftjs_exporter.dom import DOM
from wagtail import hooks
from wagtail.admin.menu import MenuItem
from wagtail.admin.rich_text.converters.html_to_contentstate import (
ExternalLinkElementHandler,
PageLinkElementHandler,
)
from wagtail.admin.site_summary import PagesSummaryItem, SummaryItem
from wagtail.models import Page
from wagtail.whitelist import check_url


@hooks.register("insert_global_admin_css")
Expand Down Expand Up @@ -82,3 +89,43 @@ def register_icons(icons: list[str]) -> list[str]:
"""
return icons + ADDITIONAL_CUSTOM_ICONS


def link_entity_with_href(props: dict):
link_props = _build_link_props(props=props)
return DOM.create_element("a", link_props, props["children"])


def _build_link_props(props: dict) -> dict[str, str | int]:
link_props = {}
page_id = props.get("id")

if page_id is not None:
link_props["linktype"] = "page"
link_props["id"] = page_id

# This is the added functionality
# on top of the original Wagtail implementation
page = Page.objects.get(id=page_id).specific
link_props["href"] = page.full_url
else:
link_props["href"] = check_url(url_string=props.get("url"))

return link_props


@hooks.register("register_rich_text_features", order=1)
def register_link_props(features):
features.register_converter_rule(
"contentstate",
"link",
{
"from_database_format": {
"a[href]": ExternalLinkElementHandler("LINK"),
'a[linktype="page"]': PageLinkElementHandler("LINK"),
},
"to_database_format": {
"entity_decorators": {"LINK": link_entity_with_href}
},
},
)
108 changes: 108 additions & 0 deletions tests/unit/cms/dashboard/test_wagtail_hooks.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
from unittest import mock

import pytest
from draftjs_exporter.dom import DOM
from wagtail.admin.rich_text.converters.html_to_contentstate import (
ExternalLinkElementHandler,
PageLinkElementHandler,
)
from wagtail.admin.site_summary import SummaryItem
from wagtail.models import Page

from cms.dashboard import wagtail_hooks
from cms.dashboard.wagtail_hooks import _build_link_props, link_entity_with_href

MODULE_PATH = "cms.dashboard.wagtail_hooks"

Expand Down Expand Up @@ -100,3 +108,103 @@ def test_update_summary_items():
assert len(core_summary_items) == 1
assert core_summary_items[0].request == mock_request
assert isinstance(core_summary_items[0], SummaryItem)


@mock.patch(f"{MODULE_PATH}.link_entity_with_href")
def test_register_link_props(spy_link_entity_with_href: mock.MagicMock):
"""
Given no input
When the wagtail hook `register_link_props` is called
Then the `link_entity_with_href()` function
is set on the link entity decorators
via the `register_converter_rule()` call
"""
# Given
spy_features = mock.Mock()

# When
wagtail_hooks.register_link_props(features=spy_features)

# Then
assert (
spy_features.mock_calls[0][1][2]["to_database_format"]["entity_decorators"][
"LINK"
]
== spy_link_entity_with_href
)


class TestLinkEntityWithHref:
@mock.patch.object(DOM, "create_element")
@mock.patch(f"{MODULE_PATH}._build_link_props")
def test_delegates_calls(
self,
spy_build_link_props: mock.MagicMock,
spy_dom_create_element: mock.MagicMock,
):
"""
Given props containing a URL and children elements
When `link_entity_with_href()` is called
Then the call is delegated
to `_build_link_props()` to make the initial props
which are then passed to `DOM.create_element()`
"""
# Given
mocked_children = mock.Mock()
fake_props = {"url": "https://abc.com", "children": mocked_children}

# When
link_entity_with_href(props=fake_props)

# Then
spy_build_link_props.assert_called_once_with(props=fake_props)
spy_dom_create_element.assert_called_once_with(
"a", spy_build_link_props.return_value, mocked_children
)


class TestBuildLinkProps:
@mock.patch.object(Page, "objects")
def test_build_link_props_with_valid_page_id(
self, mocked_page_model_manager: mock.MagicMock
):
"""
Given a valid ID for a `Page` object
When `_build_link_props()` is called
Then the returned props
also contain the page full URL
"""
# Given
page_id = 1
expected_url = "https://test-ukhsa-dashboard.com/covid-19"
mocked_page = mock.Mock()
mocked_page.specific.full_url = expected_url
mocked_page_model_manager.get.return_value = mocked_page

# When
link_props = _build_link_props({"id": page_id})

# Then
mocked_page_model_manager.get.assert_called_once_with(id=page_id)
expected_props = {"linktype": "page", "id": page_id, "href": expected_url}
assert link_props == expected_props

@mock.patch(f"{MODULE_PATH}.check_url")
def test_build_link_props_with_url(self, spy_check_url: mock.MagicMock):
"""
Given a URL for a `Page` object
When `_build_link_props()` is called
Then the returned props
contain only the page URL
"""
# Given
url = "https://test-ukhsa-dashboard.com/covid-19"
spy_check_url.return_value = url

# When
link_props = _build_link_props(props={"url": url})

# Then the correct link properties are returned
expected_props = {"href": url}
assert link_props == expected_props
spy_check_url.assert_called_once_with(url_string=url)

0 comments on commit 33ce7d5

Please sign in to comment.