For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Reject empty and non-numeric honeypot timestamps without raising an exception or processing the form submission.
Architecture: Keep timestamp validation inside HoneypotFormSubmissionMixin.time_diff(), which remains a boolean predicate. Conversion failures return False, allowing the existing process_form_submission() condition to ignore malformed submissions without changing template output or valid submission behavior.
Tech Stack: Python, Django, Wagtail, Django TestCase, Ruff, uv
- Preserve all existing
HONEYPOT_*setting names and valid-input behavior. - Preserve the existing
time_diff(value, interval)interface and interval boundary semantics. - Treat
TypeErrorandValueErrorfrom timestamp conversion as invalid honeypot input. - Do not suppress invalid project configuration such as a non-numeric interval.
- Do not change templates, migrations, dependencies, or README usage documentation.
- Add a short user-facing entry under
CHANGELOG→Unreleased.
Files:
- Modify:
tests/test_methods.py:19-31 - Modify:
tests/test_form.py:93-103 - Modify:
wagtail_honeypot/models.py:44-48 - Modify:
CHANGELOG:5-8
Interfaces:
-
Consumes:
HoneypotFormSubmissionMixin.time_diff(value, interval)and the existingHoneypotFormPageTestCase.post_form(**overrides)helper. -
Produces: unchanged
time_diff(value, interval) -> bool; values that cannot be converted withint(value)produceFalse. -
Step 1: Add focused method tests for invalid values
Add this method after test_time_diff_thresholds in tests/test_methods.py:
def test_time_diff_rejects_invalid_values(self):
cases = [
("empty string", ""),
("non-numeric string", "not-a-timestamp"),
("none", None),
]
for label, submitted_time in cases:
with self.subTest(label=label):
self.assertFalse(self.form.time_diff(submitted_time, self.interval))- Step 2: Add form-flow regression coverage
Add this method after test_form_submission_is_ignored_when_honeypot_time_is_too_short in TestHoneypotFormEnabled in tests/test_form.py:
def test_form_submission_is_ignored_when_honeypot_time_is_invalid(self):
for value in ("", "not-a-timestamp"):
with self.subTest(value=value):
resp = self.post_form(whf_time=value)
self.assert_submission_count(0)
self.assertContains(resp, "Thank you for your message")The response assertion protects the existing indistinguishable thank-you response, while the submission count proves the malformed request did not reach Wagtail's normal submission persistence path.
- Step 3: Run the new tests and verify the regression
Run:
uv run manage.py test \
tests.test_methods.TestHoneypotMethods.test_time_diff_rejects_invalid_values \
tests.test_form.TestHoneypotFormEnabled.test_form_submission_is_ignored_when_honeypot_time_is_invalidExpected: FAILED (errors=...); each malformed value reaches wagtail_honeypot/models.py and raises TypeError or ValueError from int(value).
- Step 4: Implement the minimal conversion guard
Replace time_diff() in wagtail_honeypot/models.py with:
@staticmethod
def time_diff(value, interval):
now_time = str(time.time()).split(".")[0]
try:
submitted_time = int(value)
except (TypeError, ValueError):
return False
diff = abs(int(now_time) - submitted_time)
return True if diff > interval else FalseKeep int(now_time) and the interval comparison outside the exception handler so project configuration and internal errors remain visible.
- Step 5: Run the focused regression tests
Run:
uv run manage.py test \
tests.test_methods.TestHoneypotMethods.test_time_diff_rejects_invalid_values \
tests.test_form.TestHoneypotFormEnabled.test_form_submission_is_ignored_when_honeypot_time_is_invalidExpected: Ran 2 tests followed by OK.
- Step 6: Add the changelog entry
Add this flat entry beneath ## Unreleased in CHANGELOG:
- Ignore malformed honeypot timestamps instead of raising an exception.- Step 7: Run focused form and method coverage
Run:
uv run manage.py test tests.test_methods tests.test_formExpected: all method and form tests pass, including valid timestamp thresholds, accepted submissions, and every ignored-submission case.
- Step 8: Run the complete test suite
Run:
make testExpected: the Django suite reports OK, and the coverage command exits successfully.
- Step 9: Run lint and inspect the final diff
Run:
make lint
git diff --check
git diff -- wagtail_honeypot/models.py tests/test_methods.py tests/test_form.py CHANGELOGExpected: Ruff reports no errors, git diff --check prints nothing, and the diff contains only the conversion guard, regression tests, and changelog entry.
- Step 10: Commit the fix
git add wagtail_honeypot/models.py tests/test_methods.py tests/test_form.py CHANGELOG
git commit -m "fix: reject malformed honeypot timestamps"Expected: one implementation commit containing the production fix, regression coverage, and changelog entry.