Skip to content

Commit 6532305

Browse files
Add CMS-managed HomePage and Donate block (#16980)
Introduce Wagtail CMS support for the homepage, starting with the donate section. This creates the foundation for migrating homepage content to the CMS. Changes: - Add HomePage model StreamField that has DonateBlock in it - Add DonateBlock with configurable background color and anchor ID - Add 2:1 aspect ratio fill specs for image renditions - Duplicate the rest of the m24 homepage content as static content for now Note: This page exists alongside the current homepage. URL routing migration will be handled separately.
1 parent a96ae66 commit 6532305

File tree

15 files changed

+920
-41
lines changed

15 files changed

+920
-41
lines changed

bedrock/base/templates/macros-m24.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,38 @@
104104
</a>
105105
</li>
106106
{%- endmacro %}
107+
108+
{#
109+
Donate Section
110+
HTML Import: {% from "macros-m24.html" import m24_donate with context %}
111+
CSS Bundle: Requires m24-home CSS bundle
112+
Usage: Use with {% call %} block to pass body content.
113+
Macro Parameters:
114+
heading (Required): String for the section heading.
115+
heading_level: Number indicating heading level for title text. Should be based on semantic meaning, not presentational styling. Defaults to 2.
116+
image (Required): Pre-rendered image HTML (from resp_img, srcset_image, or raw img tag).
117+
link_new_window: Boolean to open link in new window. Defaults to False.
118+
link_text (Required): String for the call-to-action button text.
119+
link_url (Required): String URL for the call-to-action button.
120+
#}
121+
{% macro m24_donate(
122+
heading,
123+
image,
124+
link_text,
125+
link_url,
126+
link_new_window=False,
127+
heading_level=2
128+
) -%}
129+
<div class="m24-c-donate">
130+
<h{{ heading_level }} class="m24-c-donate-heading">{{ heading }}</h{{ heading_level }}>
131+
<div class="m24-c-donate-body">
132+
{{ caller() }}
133+
</div>
134+
<div class="m24-c-donate-media">
135+
{{ image }}
136+
</div>
137+
<p class="m24-c-donate-cta">
138+
<a href="{{ link_url }}" class="m24-c-cta" data-cta-text="{{ link_text|slugify }}"{% if link_new_window %} target="_blank" rel="noopener"{% endif %}>{{ link_text }}</a>
139+
</p>
140+
</div>
141+
{%- endmacro %}

bedrock/cms/models/base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class AbstractBedrockCMSPage(WagtailBasePage):
3434
`serve_password_required_response` method, via the @method_decorator above
3535
"""
3636

37+
# Fluent (.ftl) files for localization. Override in subclasses to specify
38+
# page-specific FTL files. If None, only FLUENT_DEFAULT_FILES will be used.
39+
ftl_files = None
40+
3741
# Make the `slug` field 'synchronised', so it automatically gets copied over to
3842
# every localized variant of the page and shouldn't get sent for translation.
3943
# See https://wagtail-localize.org/stable/how-to/field-configuration/
@@ -67,9 +71,8 @@ def _render_with_fluent_string_support(self, request, *args, **kwargs):
6771
# can swap that for our Fluent-compatible rendering method
6872
template = self.get_template(request, *args, **kwargs)
6973
context = self.get_context(request, *args, **kwargs)
70-
# We shouldn't need to spec any special ftl_files param for render()
71-
# here because the global spec is in settings.FLUENT_DEFAULT_FILES
72-
return l10n_utils.render(request, template, context)
74+
# Pass page-specific ftl_files if defined, otherwise only FLUENT_DEFAULT_FILES will be used
75+
return l10n_utils.render(request, template, context, ftl_files=self.ftl_files)
7376

7477
def serve(self, request, *args, **kwargs):
7578
# Need to replicate behaviour in https://github.com/wagtail/wagtail/blob/stable/5.2.x/wagtail/models/__init__.py#L1928

bedrock/cms/models/images.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
# regenerate it, if needed, so that the image library is updated locally after
2020
# downloading images from Dev, Stage or Prod
2121
"max-165x165",
22+
# 2:1 aspect ratio renditions for homepage donate block (and similar uses)
23+
"fill-400x200",
24+
"fill-600x300",
25+
"fill-800x400",
26+
"fill-1000x500",
27+
"fill-1200x600",
28+
"fill-1400x700",
2229
]
2330

2431

bedrock/mozorg/blocks/home.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
from wagtail import blocks
6+
from wagtail.images.blocks import ImageChooserBlock
7+
from wagtail_link_block.blocks import LinkBlock
8+
9+
10+
class DonateBlockSettings(blocks.StructBlock):
11+
"""Settings for the donate block."""
12+
13+
background_color = blocks.ChoiceBlock(
14+
choices=[
15+
("pink", "Pink"),
16+
("green", "Green"),
17+
("orange", "Orange"),
18+
("gray", "Gray"),
19+
],
20+
required=False,
21+
help_text="What color should the background be?",
22+
)
23+
24+
anchor_id = blocks.CharBlock(
25+
required=False,
26+
max_length=100,
27+
help_text=(
28+
"Optional: Add an ID to make this section linkable. "
29+
"Will be converted to URL-safe format (e.g., 'Donate Section' becomes 'donate-section')."
30+
),
31+
)
32+
33+
class Meta:
34+
icon = "cog"
35+
collapsed = True
36+
label = "Settings"
37+
label_format = "Background: {background_color} - ID: {anchor_id}"
38+
form_classname = "compact-form struct-block"
39+
40+
41+
class DonateBlock(blocks.StructBlock):
42+
"""Block for the donate section on the homepage."""
43+
44+
settings = DonateBlockSettings()
45+
46+
heading = blocks.CharBlock(
47+
max_length=255,
48+
)
49+
50+
body = blocks.RichTextBlock(
51+
features=["bold", "link"],
52+
help_text="Keep this to 2 paragraphs or fewer.",
53+
)
54+
55+
image = ImageChooserBlock(
56+
help_text="Ideal image size is 1400 x 700. Image will be cropped to a 2:1 aspect ratio.",
57+
)
58+
59+
image_alt = blocks.CharBlock(
60+
max_length=255,
61+
required=False,
62+
help_text="A concise description of the image for someone who can't see it.",
63+
)
64+
65+
cta_text = blocks.CharBlock(
66+
max_length=50,
67+
label="Link text",
68+
)
69+
70+
cta_link = LinkBlock(
71+
label="Link destination",
72+
)
73+
74+
class Meta:
75+
template = "mozorg/cms/home/blocks/donate_block.html"
76+
icon = "heart"
77+
label = "Donate Section"
78+
label_format = "{heading}"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
from io import BytesIO
6+
7+
from django.core.files.base import ContentFile
8+
9+
from PIL import Image
10+
from wagtail.models import Site
11+
12+
from bedrock.cms.models import BedrockImage, StructuralPage
13+
14+
15+
def get_placeholder_image(width=1400, height=700, color=(117, 79, 224)):
16+
"""Create a placeholder image for testing.
17+
18+
Args:
19+
width: Image width in pixels (default 1400 for 2:1 ratio)
20+
height: Image height in pixels (default 700 for 2:1 ratio)
21+
color: RGB tuple for fill color
22+
23+
Returns:
24+
BedrockImage instance
25+
"""
26+
image = Image.new("RGB", (width, height), color)
27+
image_buffer = BytesIO()
28+
image.save(image_buffer, format="PNG")
29+
image_buffer.seek(0)
30+
31+
bedrock_image, _ = BedrockImage.objects.get_or_create(
32+
title="Placeholder Image for Testing",
33+
defaults={
34+
"file": ContentFile(image_buffer.read(), "placeholder_image.png"),
35+
},
36+
)
37+
return bedrock_image
38+
39+
40+
def get_test_index_page():
41+
"""Get or create a test index page for block tests.
42+
43+
Returns:
44+
StructuralPage instance to use as parent for test pages
45+
"""
46+
site = Site.objects.get(is_default_site=True)
47+
root_page = site.root_page
48+
49+
index_page = StructuralPage.objects.filter(slug="block-tests-index").first()
50+
if not index_page:
51+
index_page = StructuralPage(
52+
slug="block-tests-index",
53+
title="Block Tests Index Page",
54+
)
55+
root_page.add_child(instance=index_page)
56+
index_page.save_revision().publish()
57+
58+
return index_page
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
6+
from bedrock.mozorg.fixtures.base_fixtures import get_placeholder_image, get_test_index_page
7+
from bedrock.mozorg.models import HomePage
8+
9+
10+
def get_donate_variants(image_id: int) -> list[dict]:
11+
"""Return list of DonateBlock data variants for testing.
12+
13+
Args:
14+
image_id: ID of the placeholder image to use
15+
16+
Returns:
17+
List of block data dictionaries representing different configurations
18+
"""
19+
return [
20+
# Variant 1: Default gray background, no anchor
21+
{
22+
"type": "donate_block",
23+
"value": {
24+
"settings": {
25+
"background_color": "gray",
26+
"anchor_id": "",
27+
},
28+
"heading": "Support Mozilla",
29+
"body": "<p>Help us build a better internet for everyone.</p>",
30+
"image": image_id,
31+
"image_alt": "Mozilla community event",
32+
"cta_text": "Donate Now",
33+
"cta_link": {
34+
"link_to": "custom_url",
35+
"custom_url": "https://foundation.mozilla.org/donate",
36+
"new_window": False,
37+
},
38+
},
39+
"id": "donate-variant-1",
40+
},
41+
# Variant 2: Pink background
42+
{
43+
"type": "donate_block",
44+
"value": {
45+
"settings": {
46+
"background_color": "pink",
47+
"anchor_id": "",
48+
},
49+
"heading": "Join the Movement",
50+
"body": "<p>Your donation powers our mission.</p><p>Every contribution counts.</p>",
51+
"image": image_id,
52+
"image_alt": "",
53+
"cta_text": "Give Today",
54+
"cta_link": {
55+
"link_to": "custom_url",
56+
"custom_url": "https://foundation.mozilla.org/give",
57+
"new_window": False,
58+
},
59+
},
60+
"id": "donate-variant-2",
61+
},
62+
# Variant 3: With anchor_id
63+
{
64+
"type": "donate_block",
65+
"value": {
66+
"settings": {
67+
"background_color": "green",
68+
"anchor_id": "donate-section",
69+
},
70+
"heading": "Make a Difference",
71+
"body": "<p>Support open source.</p>",
72+
"image": image_id,
73+
"image_alt": "Open source illustration",
74+
"cta_text": "Contribute",
75+
"cta_link": {
76+
"link_to": "custom_url",
77+
"custom_url": "https://foundation.mozilla.org/contribute",
78+
"new_window": False,
79+
},
80+
},
81+
"id": "donate-variant-3",
82+
},
83+
# Variant 4: With new_window=True
84+
{
85+
"type": "donate_block",
86+
"value": {
87+
"settings": {
88+
"background_color": "orange",
89+
"anchor_id": "",
90+
},
91+
"heading": "Support Our Work",
92+
"body": "<p>Opens in a <strong>new window</strong>.</p>",
93+
"image": image_id,
94+
"image_alt": "",
95+
"cta_text": "Donate (New Window)",
96+
"cta_link": {
97+
"link_to": "custom_url",
98+
"custom_url": "https://foundation.mozilla.org/external",
99+
"new_window": True,
100+
},
101+
},
102+
"id": "donate-variant-4",
103+
},
104+
]
105+
106+
107+
def get_donate_test_page() -> HomePage:
108+
"""Create a HomePage with all donate block variants for testing.
109+
110+
Returns:
111+
HomePage instance with donate blocks populated
112+
"""
113+
# Ensure we have a placeholder image
114+
placeholder_image = get_placeholder_image()
115+
116+
# Get variants with the image ID
117+
variants = get_donate_variants(placeholder_image.id)
118+
119+
# Get the index page as parent
120+
index_page = get_test_index_page()
121+
122+
# Check if test page already exists
123+
test_page = HomePage.objects.filter(slug="donate-block-test").first()
124+
if test_page:
125+
return test_page
126+
127+
# Create the test page with all variants
128+
test_page = HomePage(
129+
title="Donate Block Test Page",
130+
slug="donate-block-test",
131+
donate=variants,
132+
)
133+
134+
index_page.add_child(instance=test_page)
135+
test_page.save_revision().publish()
136+
137+
return test_page

0 commit comments

Comments
 (0)