feat(formbuilder): answers must belong to the submission's form

An Answer's field could point at a field of a *different* form than its
submission — and since forms are club-scoped, across clubs too. Validate
field.form == submission.form in clean().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 14:42:17 +02:00
parent 22d971d48c
commit d43ca0cfa8
2 changed files with 23 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import UniqueConstraint
from django.utils.translation import gettext_lazy as _
@@ -100,3 +101,7 @@ class Answer(UUIDModel):
def __str__(self):
return f"{self.submission} - {self.field}"
def clean(self):
if self.field_id and self.submission_id and self.field.form_id != self.submission.form_id:
raise ValidationError({"field": _("Must belong to the same form as the submission.")})

View File

@@ -1,6 +1,7 @@
from datetime import timedelta
from django import forms
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.db.models import ProtectedError
from django.test import TestCase
@@ -329,3 +330,20 @@ class FormReportTests(FormbuilderTestBase):
self.assertEqual(report.count, 0)
self.assertEqual(report.rows, [])
class AnswerCleanTests(FormbuilderTestBase):
def test_rejects_field_from_another_form(self):
other_form = Form.objects.create(club=self.club, title="Other", slug="other")
other_field = Field.objects.create(form=other_form, key="x", label="X", order=1)
submission = Submission.objects.create(form=self.form, member=self.member)
answer = Answer(submission=submission, field=other_field, value="v")
with self.assertRaises(ValidationError) as ctx:
answer.full_clean()
self.assertIn("field", ctx.exception.error_dict)
def test_accepts_field_from_the_submissions_form(self):
submission = Submission.objects.create(form=self.form, member=self.member)
Answer(submission=submission, field=self.name, value="v").full_clean()