Skip to content

Commit e4c4905

Browse files
WT-514 - More Anonym Improvements (#17027)
* use correct model names in Wagtail allowed page models and db export * make sure Person snippet is also in db export * add fields to AnonymContactPage for form submissions * handle form submissions for AnonymContactPage * update tests for form submissions to AnonymContactPage * add a few more icons for thumbnail icons blocks * new icons added * fix borders * use a honeybot field to reject bot submissions * use JS to trick bots into submitting to wrong URL --------- Co-authored-by: Kasey Kelly <[email protected]>
1 parent ac2a557 commit e4c4905

File tree

17 files changed

+496
-4
lines changed

17 files changed

+496
-4
lines changed

bedrock/anonym/blocks.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
("command-noautohide", "Command Noautohide"),
8888
("common-voice", "Common Voice"),
8989
("copy", "Copy"),
90+
("current-view", "Current View"),
9091
("customize", "Customize"),
9192
("cut", "Cut"),
9293
("dashboard", "Dashboard"),
@@ -150,8 +151,9 @@
150151
("hashtag-narrow", "Hashtag Narrow"),
151152
("hashtag", "Hashtag"),
152153
("headphone", "Headphone"),
153-
("heart-white", "Heart White"),
154154
("heart", "Heart"),
155+
("heart-rate", "Heart Rate"),
156+
("heart-white", "Heart White"),
155157
("help", "Help"),
156158
("highlight", "Highlight"),
157159
("history", "History"),
@@ -166,6 +168,7 @@
166168
("labs", "Labs"),
167169
("language", "Language"),
168170
("library", "Library"),
171+
("layer", "Layer"),
169172
("link", "Link"),
170173
("listen", "Listen"),
171174
("lite", "Lite"),
@@ -295,6 +298,7 @@
295298
("undo", "Undo"),
296299
("update", "Update"),
297300
("user", "User"),
301+
("users", "Users"),
298302
("video-card", "Video Card"),
299303
("video-recoder-disabled", "Video Recoder Disabled"),
300304
("video-recorder", "Video Recorder"),
@@ -315,6 +319,14 @@
315319
"pricetag-white": "img/icons",
316320
"rhombus-layers": "img/icons",
317321
"rhombus-layers-white": "img/icons",
322+
"lock": "img/mozorg/anonym",
323+
"sparkles": "img/mozorg/anonym",
324+
"globe": "img/mozorg/anonym",
325+
"dashboard": "img/mozorg/anonym",
326+
"users": "img/mozorg/anonym",
327+
"current-view": "img/mozorg/anonym",
328+
"heart-rate": "img/mozorg/anonym",
329+
"layer": "img/mozorg/anonym",
318330
}
319331

320332

bedrock/anonym/fixtures/page_fixtures.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,23 @@ def get_anonym_contact_test_page() -> AnonymContactPage:
218218
"""
219219
index_page = get_test_anonym_index_page()
220220

221+
# Create a thank-you page as the redirect target
222+
thank_you_page = AnonymContentSubPage.objects.filter(slug="test-thank-you").first()
223+
if not thank_you_page:
224+
thank_you_page = AnonymContentSubPage(
225+
slug="test-thank-you",
226+
title="Thank You",
227+
)
228+
index_page.add_child(instance=thank_you_page)
229+
thank_you_page.save_revision().publish()
230+
221231
test_page = AnonymContactPage.objects.filter(slug="test-contact-page").first()
222232
if not test_page:
223233
test_page = AnonymContactPage(
224234
slug="test-contact-page",
225235
title="Test Contact Page",
236+
to_email_address="[email protected]",
237+
redirect_to=thank_you_page,
226238
)
227239
index_page.add_child(instance=test_page)
228240

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
# Generated by Django 5.2.9 on 2026-02-10 18:03
6+
7+
import django.db.models.deletion
8+
from django.db import migrations, models
9+
10+
11+
class Migration(migrations.Migration):
12+
dependencies = [
13+
("anonym", "0004_alter_anonymnewsitempage_content"),
14+
("wagtailcore", "0096_referenceindex_referenceindex_source_object_and_more"),
15+
]
16+
17+
operations = [
18+
migrations.AddField(
19+
model_name="anonymcontactpage",
20+
name="redirect_to",
21+
field=models.ForeignKey(
22+
default=1,
23+
help_text="Page to redirect to after a successful form submission (e.g. a thank-you page).",
24+
on_delete=django.db.models.deletion.PROTECT,
25+
related_name="+",
26+
to="wagtailcore.page",
27+
),
28+
preserve_default=False,
29+
),
30+
migrations.AddField(
31+
model_name="anonymcontactpage",
32+
name="to_email_address",
33+
field=models.EmailField(default="[email protected]", help_text="Email address where form submissions will be sent.", max_length=254),
34+
preserve_default=False,
35+
),
36+
]

bedrock/anonym/migrations/0006_alter_anonymcontentsubpage_content_and_more.py

Lines changed: 29 additions & 0 deletions
Large diffs are not rendered by default.

bedrock/anonym/models.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5+
from django.conf import settings
6+
from django.core.mail import EmailMessage
57
from django.db import models
8+
from django.shortcuts import redirect
9+
from django.template.loader import render_to_string
610

711
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
812
from wagtail.fields import RichTextField, StreamField
@@ -227,11 +231,107 @@ class AnonymContactPage(AbstractBedrockCMSPage):
227231
help_text="Define the form fields that will appear on the contact page.",
228232
)
229233

234+
to_email_address = models.EmailField(
235+
help_text="Email address where form submissions will be sent.",
236+
)
237+
238+
redirect_to = models.ForeignKey(
239+
"wagtailcore.Page",
240+
on_delete=models.PROTECT,
241+
related_name="+",
242+
help_text="Page to redirect to after a successful form submission (e.g. a thank-you page).",
243+
)
244+
230245
content_panels = AbstractBedrockCMSPage.content_panels + [
231246
FieldPanel("subheading"),
232247
FieldPanel("form_fields"),
233248
]
234249

250+
settings_panels = AbstractBedrockCMSPage.settings_panels + [
251+
MultiFieldPanel(
252+
[
253+
FieldPanel("to_email_address"),
254+
FieldPanel("redirect_to"),
255+
],
256+
heading="Form Submission Settings",
257+
),
258+
]
259+
260+
def get_context(self, request, *args, **kwargs):
261+
context = super().get_context(request, *args, **kwargs)
262+
form_errors = getattr(request, "form_errors", None)
263+
if form_errors:
264+
context["form_errors"] = form_errors
265+
return context
266+
267+
def serve(self, request, *args, **kwargs):
268+
if request.method == "POST":
269+
form_errors = self.validate_form_data(request.POST)
270+
if form_errors:
271+
request.form_errors = form_errors
272+
return super().serve(request, *args, **kwargs)
273+
274+
self.send_form_email(request)
275+
return redirect(self.redirect_to.url)
276+
277+
return super().serve(request, *args, **kwargs)
278+
279+
def validate_form_data(self, post_data):
280+
"""Validate submitted form data against the field configuration.
281+
282+
Returns a list of error messages. An empty list means the data is valid.
283+
"""
284+
# If the honeypot field has data, then form validation fails.
285+
if post_data.get("office_fax", ""):
286+
return ["Form submission failed."]
287+
288+
errors = []
289+
has_any_data = False
290+
291+
for field in self.form_fields:
292+
block_type = field.block_type
293+
value = field.value
294+
identifier = value["settings"]["internal_identifier"]
295+
label = value["label"]
296+
is_required = value.get("required", False)
297+
298+
if block_type == "checkbox_group_field":
299+
submitted = post_data.getlist(identifier)
300+
else:
301+
submitted = post_data.get(identifier, "").strip()
302+
303+
if submitted:
304+
has_any_data = True
305+
306+
if is_required and not submitted:
307+
errors.append(f"{label} is required.")
308+
309+
if not has_any_data:
310+
errors.append("Please fill in at least one field.")
311+
312+
return errors
313+
314+
def send_form_email(self, request):
315+
"""Collect form data and send it as an email."""
316+
fields = []
317+
for field in self.form_fields:
318+
block_type = field.block_type
319+
value = field.value
320+
identifier = value["settings"]["internal_identifier"]
321+
label = value["label"]
322+
323+
if block_type == "checkbox_group_field":
324+
submitted = ", ".join(request.POST.getlist(identifier))
325+
else:
326+
submitted = request.POST.get(identifier, "")
327+
328+
fields.append({"label": label, "value": submitted})
329+
330+
msg = render_to_string("anonym/emails/contact-form.txt", {"fields": fields})
331+
subject = f"Contact form submission: {self.title}"
332+
email = EmailMessage(subject, msg, settings.DEFAULT_FROM_EMAIL, [self.to_email_address])
333+
email.send()
334+
235335
class Meta:
236336
verbose_name = "Anonym Contact Page"
237337

bedrock/anonym/templates/anonym/anonym_contact.html

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,28 @@ <h1 class="mzan-heading">{{ page.title }}</h1>
4444

4545
<div class="mzan-contact-form-wrap add-full-width-borders">
4646
<div class="mzan-section">
47-
<form method="post" action="#" class="mzan-contact-form">
47+
<form method="post" action="/page-not-found/" data-actn="" class="mzan-contact-form">
4848
{% csrf_token %}
4949

50+
{% if form_errors %}
51+
<div class="mzan-form-errors">
52+
<ul>
53+
{% for error in form_errors %}
54+
<li>{{ error }}</li>
55+
{% endfor %}
56+
</ul>
57+
</div>
58+
{% endif %}
59+
5060
{% for field in page.form_fields %}
5161
{% include_block field %}
5262
{% endfor %}
5363

64+
<div class="mzan-form-field important-field">
65+
<label for="office-fax">Leave this field empty</label>
66+
<input type="text" name="office_fax" id="office-fax">
67+
</div>
68+
5469
<div class="mzan-form-submit">
5570
<button type="submit" class="mzp-c-button">Submit</button>
5671
</div>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
A new contact form submission has been received:
2+
3+
------------------------------------
4+
{% for field in fields %}
5+
+ {{ field.label }}
6+
{{ field.value }}
7+
8+
{% endfor %}------------------------------------

0 commit comments

Comments
 (0)