aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Joe Banks <[email protected]>2025-08-08 23:24:54 +0100
committerGravatar Joe Banks <[email protected]>2025-08-08 23:24:54 +0100
commite5f8adc7e5b7e04197b479be589172abdfaeef5c (patch)
tree891115d738ecb89481cbdb7059764209d1024ea6
parentAdd UNIQUE_RESPONDER form feature (diff)
Calculate form ancillary data when fetching form data
-rw-r--r--backend/routes/forms/form.py70
1 files changed, 66 insertions, 4 deletions
diff --git a/backend/routes/forms/form.py b/backend/routes/forms/form.py
index 86bbf49..9414e30 100644
--- a/backend/routes/forms/form.py
+++ b/backend/routes/forms/form.py
@@ -1,9 +1,10 @@
"""Returns, updates or deletes a single form given an ID."""
+import enum
import json.decoder
import deepmerge
-from pydantic import ValidationError
+from pydantic import BaseModel, ValidationError
from spectree.response import Response
from starlette.authentication import requires
from starlette.requests import Request
@@ -18,6 +19,33 @@ from backend.validation import ErrorMessage, OkayResponse, api
PUBLIC_FORM_FEATURES = (constants.FormFeatures.OPEN, constants.FormFeatures.DISCOVERABLE)
+class SubmissionPrecheckSeverity(enum.StrEnum):
+ SECONDARY = "secondary"
+ WARNING = "warning"
+ DANGER = "danger"
+
+
+class SubmissionProblem(BaseModel):
+ severity: SubmissionPrecheckSeverity
+ message: str
+
+
+class SubmissionPrecheck(BaseModel):
+ problems: list[SubmissionProblem] = []
+ can_submit: bool = True
+
+
+class FormWithAncillaryData(Form):
+ """
+ Form model with ancillary data for the form.
+
+ This is used to return the form with additional information such as
+ whether the user has edit access or not.
+ """
+
+ submission_precheck: SubmissionPrecheck = SubmissionPrecheck()
+
+
class SingleForm(Route):
"""
Returns, updates or deletes a single form given an ID.
@@ -28,14 +56,17 @@ class SingleForm(Route):
name = "form"
path = "/{form_id:str}"
- @api.validate(resp=Response(HTTP_200=Form, HTTP_404=ErrorMessage), tags=["forms"])
+ @api.validate(resp=Response(HTTP_200=FormWithAncillaryData, HTTP_404=ErrorMessage), tags=["forms"])
async def get(self, request: Request) -> JSONResponse:
"""Returns single form information by ID."""
form_id = request.path_params["form_id"].lower()
if form_id == AUTH_FORM.id:
# Empty form for login purposes
- return JSONResponse(AUTH_FORM.dict(admin=False))
+ data = AUTH_FORM.dict(admin=False)
+ # Add in empty ancillary data
+ data["submission_precheck"] = SubmissionPrecheck().dict()
+ return JSONResponse(data)
try:
await discord.verify_edit_access(form_id, request)
@@ -53,10 +84,41 @@ class SingleForm(Route):
filters["features"] = {"$in": ["OPEN", "DISCOVERABLE"]}
form = await request.state.db.forms.find_one(filters)
+ form = FormWithAncillaryData(**form)
if not form:
return JSONResponse({"error": "not_found"}, status_code=404)
- return JSONResponse(Form(**form).dict(admin=admin))
+ if constants.FormFeatures.OPEN.value not in form.features:
+ form.submission_precheck.problems.append(
+ SubmissionProblem(
+ severity=SubmissionPrecheckSeverity.DANGER,
+ message="This form is not open for submissions at the moment."
+ )
+ )
+ form.submission_precheck.can_submit = False
+ elif constants.FormFeatures.UNIQUE_RESPONDER.value in form.features:
+ user_id = request.user.payload["id"] if request.user.is_authenticated else None
+ if user_id:
+ existing_response = await request.state.db.responses.find_one(
+ {"form_id": form_id, "user.id": user_id}
+ )
+ if existing_response:
+ form.submission_precheck.problems.append(
+ SubmissionProblem(
+ severity=SubmissionPrecheckSeverity.DANGER,
+ message="You have already submitted a response to this form."
+ )
+ )
+ form.submission_precheck.can_submit = False
+ else:
+ form.submission_precheck.problems.append(
+ SubmissionProblem(
+ severity=SubmissionPrecheckSeverity.SECONDARY,
+ message="You must login at the bottom of the page before submitting this form."
+ )
+ )
+
+ return JSONResponse(form.dict(admin=admin))
@requires(["authenticated"])
@api.validate(