Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Main Menu #41

Draft
wants to merge 28 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
099f814
Front-end hacked together to complete the back-end
sanjeevz3009 Nov 23, 2024
8df17b5
Navigation app added, models created for navigation settings
sanjeevz3009 Nov 25, 2024
ffcd5b4
Navigation bar on the front-end test
sanjeevz3009 Nov 26, 2024
8d5188c
Front-end code reverted due to the front-end main menu being done sep…
sanjeevz3009 Nov 26, 2024
d955f36
Merge branch 'main' into main-menu-CMS-169
sanjeevz3009 Nov 26, 2024
42341b6
New line
sanjeevz3009 Nov 26, 2024
f89c20d
Code formatted
sanjeevz3009 Nov 26, 2024
3cfef6d
Type hinted and code formatted
sanjeevz3009 Dec 1, 2024
7cee546
Code refactored, abstract link class created, validation messages add…
sanjeevz3009 Dec 1, 2024
ea8359e
Migration file
sanjeevz3009 Dec 1, 2024
d3c3157
Main menu modelling complete, restricting user to be able to only cre…
sanjeevz3009 Dec 2, 2024
bf1542f
LinkBlock extracted out of core.blocks.related and moved to core.bloc…
sanjeevz3009 Dec 3, 2024
f5c0144
Code formatted, zombie code removed, unused imports removed and MainM…
sanjeevz3009 Dec 8, 2024
cd99772
Merge branch 'main' into main-menu-CMS-169
sanjeevz3009 Dec 8, 2024
31ea34e
Front-end integrated
sanjeevz3009 Dec 9, 2024
6b79ace
Type hints added
sanjeevz3009 Dec 9, 2024
56d0fd7
Nav template issues fixed
sanjeevz3009 Dec 9, 2024
45838ea
gettext_lazy added
sanjeevz3009 Dec 9, 2024
f3c43ce
Preview added
sanjeevz3009 Dec 9, 2024
30a9a39
Use the title provided by the user over the default title from the page
sanjeevz3009 Dec 9, 2024
f38f975
help text update
sanjeevz3009 Dec 9, 2024
f12078a
Type hints added
sanjeevz3009 Dec 9, 2024
3aa2f70
Migration file regenerated
sanjeevz3009 Dec 9, 2024
4a941f1
Tests added
sanjeevz3009 Dec 9, 2024
5652121
TODO removed and translatable added
sanjeevz3009 Dec 10, 2024
3a25142
Description help text updated
sanjeevz3009 Dec 10, 2024
40c28c1
Tests setup
sanjeevz3009 Dec 12, 2024
5345a9b
Example StreamField value setting in test_highlights_streamfield_limit
zerolab Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions cms/core/blocks/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from django.core.exceptions import ValidationError
from django.forms.utils import ErrorList
from django.utils.functional import cached_property
from django.utils.translation import gettext as _
from wagtail.blocks import (
CharBlock,
PageChooserBlock,
StreamBlockValidationError,
StructBlock,
StructValue,
URLBlock,
)


class LinkBlockStructValue(StructValue):
"""Custom StructValue for link blocks."""

@cached_property
def link(self) -> dict | None:
"""A convenience property that returns the block value in a consistent way,
regardless of the chosen values (be it a Wagtail page or external link).
"""
value = None
title = self.get("title")
desc = self.get("description")
has_description = "description" in self

if external_url := self.get("external_url"):
value = {"url": external_url, "text": title}
if has_description:
value["description"] = desc

if (page := self.get("page")) and page.live:
value = {"url": page.url, "text": title or page.title}
if has_description:
value["description"] = desc or getattr(page.specific_deferred, "summary", "")

return value


class LinkBlock(StructBlock):
"""Related link block with page or link validation."""

page = PageChooserBlock(required=False)
external_url = URLBlock(required=False, label="or External Link")
title = CharBlock(
help_text="Populate when adding an external link. "
"When choosing a page, you can leave it blank to use the page's own title",
required=False,
)

class Meta:
icon = "link"
value_class = LinkBlockStructValue

def clean(self, value: LinkBlockStructValue) -> LinkBlockStructValue:
"""Validate that either a page or external link is provided, and that external links have a title."""
value = super().clean(value)
page = value["page"]
external_url = value["external_url"]
errors = {}
non_block_errors = ErrorList()

# Require exactly one link
if not page and not external_url:
error = ValidationError(_("Either Page or External Link is required."), code="invalid")
errors["page"] = ErrorList([error])
errors["external_url"] = ErrorList([error])
non_block_errors.append(ValidationError(_("Missing required fields")))
elif page and external_url:
error = ValidationError(_("Please select either a page or a URL, not both."), code="invalid")
errors["page"] = ErrorList([error])
errors["external_url"] = ErrorList([error])

# Require title for external links
if not page and external_url and not value["title"]:
errors["title"] = ErrorList([ValidationError(_("Title is required for external links."), code="invalid")])

if errors:
raise StreamBlockValidationError(block_errors=errors, non_block_errors=non_block_errors)

return value
85 changes: 3 additions & 82 deletions cms/core/blocks/related.py
Original file line number Diff line number Diff line change
@@ -1,94 +1,15 @@
from typing import TYPE_CHECKING, Any

from django.core.exceptions import ValidationError
from django.forms.utils import ErrorList
from django.utils.functional import cached_property
from django.utils.text import slugify
from django.utils.translation import gettext as _
from wagtail.blocks import (
CharBlock,
ListBlock,
PageChooserBlock,
StreamBlockValidationError,
StructBlock,
StructValue,
URLBlock,
)
from wagtail.blocks import CharBlock, ListBlock

from .base import LinkBlock

if TYPE_CHECKING:
from wagtail.blocks.list_block import ListValue


class LinkBlockStructValue(StructValue):
"""Custom StructValue for link blocks."""

@cached_property
def link(self) -> dict | None:
"""A convenience property that returns the block value in a consistent way,
regardless of the chosen values (be it a Wagtail page or external link).
"""
value = None
title = self.get("title")
desc = self.get("description")
has_description = "description" in self

if external_url := self.get("external_url"):
value = {"url": external_url, "text": title}
if has_description:
value["description"] = desc

if (page := self.get("page")) and page.live:
value = {"url": page.url, "text": title or page.title}
if has_description:
value["description"] = desc or getattr(page.specific_deferred, "summary", "")

return value


class LinkBlock(StructBlock):
"""Related link block with page or link validation."""

page = PageChooserBlock(required=False)
external_url = URLBlock(required=False, label="or External Link")
title = CharBlock(
help_text="Populate when adding an external link. "
"When choosing a page, you can leave it blank to use the page's own title",
required=False,
)

class Meta:
icon = "link"
value_class = LinkBlockStructValue

def clean(self, value: LinkBlockStructValue) -> LinkBlockStructValue:
"""Validate that either a page or external link is provided, and that external links have a title."""
value = super().clean(value)
page = value["page"]
external_url = value["external_url"]
errors = {}
non_block_errors = ErrorList()

# Require exactly one link
if not page and not external_url:
error = ValidationError(_("Either Page or External Link is required."), code="invalid")
errors["page"] = ErrorList([error])
errors["external_url"] = ErrorList([error])
non_block_errors.append(ValidationError(_("Missing required fields")))
elif page and external_url:
error = ValidationError(_("Please select either a page or a URL, not both."), code="invalid")
errors["page"] = ErrorList([error])
errors["external_url"] = ErrorList([error])

# Require title for external links
if not page and external_url and not value["title"]:
errors["title"] = ErrorList([ValidationError(_("Title is required for external links."), code="invalid")])

if errors:
raise StreamBlockValidationError(block_errors=errors, non_block_errors=non_block_errors)

return value


class RelatedContentBlock(LinkBlock):
"""Related content block with page or link validation."""

Expand Down
Loading
Loading