feat(formbuilder): fix tenancy bugs, add admin + services
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>
This commit is contained in:
50
formbuilder/admin.py
Normal file
50
formbuilder/admin.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Answer, Field, Form, Submission
|
||||
|
||||
|
||||
class FieldInline(admin.TabularInline):
|
||||
model = Field
|
||||
extra = 0
|
||||
fields = ["order", "key", "label", "field_type", "required", "is_active"]
|
||||
ordering = ["order"]
|
||||
|
||||
|
||||
@admin.register(Form)
|
||||
class FormAdmin(admin.ModelAdmin):
|
||||
list_display = ["title", "slug", "club", "is_active", "login_required", "opens_at", "closes_at"]
|
||||
list_filter = ["club", "is_active", "login_required"]
|
||||
search_fields = ["title", "slug"]
|
||||
prepopulated_fields = {"slug": ["title"]}
|
||||
inlines = [FieldInline]
|
||||
|
||||
|
||||
@admin.register(Field)
|
||||
class FieldAdmin(admin.ModelAdmin):
|
||||
list_display = ["label", "key", "form", "field_type", "order", "required", "is_active"]
|
||||
list_filter = ["field_type", "required", "is_active"]
|
||||
search_fields = ["label", "key", "form__title"]
|
||||
raw_id_fields = ["form"]
|
||||
|
||||
|
||||
class AnswerInline(admin.TabularInline):
|
||||
model = Answer
|
||||
extra = 0
|
||||
raw_id_fields = ["field"]
|
||||
|
||||
|
||||
@admin.register(Submission)
|
||||
class SubmissionAdmin(admin.ModelAdmin):
|
||||
list_display = ["form", "member", "submitted_at"]
|
||||
list_filter = ["form"]
|
||||
search_fields = ["form__title", "member__first_name", "member__last_name"]
|
||||
raw_id_fields = ["form", "member"]
|
||||
readonly_fields = ["submitted_at"]
|
||||
inlines = [AnswerInline]
|
||||
|
||||
|
||||
@admin.register(Answer)
|
||||
class AnswerAdmin(admin.ModelAdmin):
|
||||
list_display = ["submission", "field", "value"]
|
||||
search_fields = ["field__label", "field__key"]
|
||||
raw_id_fields = ["submission", "field"]
|
||||
Reference in New Issue
Block a user