Fix multi-tenancy/integrity bugs in the models: Form.slug and Field.key were globally unique (unique=True), so two clubs couldn't reuse a form slug and two forms couldn't reuse a field key — make slug unique per club (constraint already present) and key unique per form. Add a (submission, field) uniqueness constraint on Answer. Register all four models in the admin (Field inline on Form, Answer inline on Submission) and add formbuilder to the admin registration smoke test. Add a service layer: - submit_form(form, member, data): enforces is_active / login_required / open window / max_submissions, validates required + choice fields, and writes a Submission with Answers atomically (FormSubmissionError carries per-field errors). - build_form(form): a live django.forms.Form built from a Form's active fields, mapping each FieldType to the matching form field. - form_report(form): a tabular overview of every submission's answers plus per-value tallies for choice-type fields. Full suite at 100% coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
36 lines
1.5 KiB
Python
36 lines
1.5 KiB
Python
"""Build a live ``django.forms.Form`` class from a Form instance's fields."""
|
|
|
|
from django import forms
|
|
|
|
from formbuilder.models import Field
|
|
|
|
from .options import field_choices
|
|
|
|
FIELD_BUILDERS = {
|
|
Field.FieldType.TEXT: lambda field, kw: forms.CharField(**kw),
|
|
Field.FieldType.TEXTAREA: lambda field, kw: forms.CharField(widget=forms.Textarea, **kw),
|
|
Field.FieldType.NUMBER: lambda field, kw: forms.DecimalField(**kw),
|
|
Field.FieldType.EMAIL: lambda field, kw: forms.EmailField(**kw),
|
|
Field.FieldType.DATE: lambda field, kw: forms.DateField(**kw),
|
|
Field.FieldType.CHOICE: lambda field, kw: forms.ChoiceField(choices=field_choices(field), **kw),
|
|
Field.FieldType.MULTICHOICE: lambda field, kw: forms.MultipleChoiceField(choices=field_choices(field), **kw),
|
|
Field.FieldType.CHECKBOX: lambda field, kw: forms.BooleanField(**kw),
|
|
Field.FieldType.FILE: lambda field, kw: forms.FileField(**kw),
|
|
}
|
|
|
|
|
|
def _build_field(field):
|
|
kwargs = {"required": field.required, "label": field.label, "help_text": field.help_text}
|
|
return FIELD_BUILDERS[field.field_type](field, kwargs)
|
|
|
|
|
|
def build_form_class(form):
|
|
"""Return a ``forms.Form`` subclass with a field per active Field, keyed by ``key``."""
|
|
fields = {field.key: _build_field(field) for field in form.fields.filter(is_active=True).order_by("order")}
|
|
return type("DynamicForm", (forms.Form,), fields)
|
|
|
|
|
|
def build_form(form, data=None, files=None):
|
|
"""Instantiate the dynamic form, bound to ``data``/``files`` when provided."""
|
|
return build_form_class(form)(data=data, files=files)
|