Skip to content

Commit e5f8adc

Browse files
committed
Calculate form ancillary data when fetching form data
1 parent c29a08e commit e5f8adc

File tree

1 file changed

+66
-4
lines changed

1 file changed

+66
-4
lines changed

backend/routes/forms/form.py

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Returns, updates or deletes a single form given an ID."""
22

3+
import enum
34
import json.decoder
45

56
import deepmerge
6-
from pydantic import ValidationError
7+
from pydantic import BaseModel, ValidationError
78
from spectree.response import Response
89
from starlette.authentication import requires
910
from starlette.requests import Request
@@ -18,6 +19,33 @@
1819
PUBLIC_FORM_FEATURES = (constants.FormFeatures.OPEN, constants.FormFeatures.DISCOVERABLE)
1920

2021

22+
class SubmissionPrecheckSeverity(enum.StrEnum):
23+
SECONDARY = "secondary"
24+
WARNING = "warning"
25+
DANGER = "danger"
26+
27+
28+
class SubmissionProblem(BaseModel):
29+
severity: SubmissionPrecheckSeverity
30+
message: str
31+
32+
33+
class SubmissionPrecheck(BaseModel):
34+
problems: list[SubmissionProblem] = []
35+
can_submit: bool = True
36+
37+
38+
class FormWithAncillaryData(Form):
39+
"""
40+
Form model with ancillary data for the form.
41+
42+
This is used to return the form with additional information such as
43+
whether the user has edit access or not.
44+
"""
45+
46+
submission_precheck: SubmissionPrecheck = SubmissionPrecheck()
47+
48+
2149
class SingleForm(Route):
2250
"""
2351
Returns, updates or deletes a single form given an ID.
@@ -28,14 +56,17 @@ class SingleForm(Route):
2856
name = "form"
2957
path = "/{form_id:str}"
3058

31-
@api.validate(resp=Response(HTTP_200=Form, HTTP_404=ErrorMessage), tags=["forms"])
59+
@api.validate(resp=Response(HTTP_200=FormWithAncillaryData, HTTP_404=ErrorMessage), tags=["forms"])
3260
async def get(self, request: Request) -> JSONResponse:
3361
"""Returns single form information by ID."""
3462
form_id = request.path_params["form_id"].lower()
3563

3664
if form_id == AUTH_FORM.id:
3765
# Empty form for login purposes
38-
return JSONResponse(AUTH_FORM.dict(admin=False))
66+
data = AUTH_FORM.dict(admin=False)
67+
# Add in empty ancillary data
68+
data["submission_precheck"] = SubmissionPrecheck().dict()
69+
return JSONResponse(data)
3970

4071
try:
4172
await discord.verify_edit_access(form_id, request)
@@ -53,10 +84,41 @@ async def get(self, request: Request) -> JSONResponse:
5384
filters["features"] = {"$in": ["OPEN", "DISCOVERABLE"]}
5485

5586
form = await request.state.db.forms.find_one(filters)
87+
form = FormWithAncillaryData(**form)
5688
if not form:
5789
return JSONResponse({"error": "not_found"}, status_code=404)
5890

59-
return JSONResponse(Form(**form).dict(admin=admin))
91+
if constants.FormFeatures.OPEN.value not in form.features:
92+
form.submission_precheck.problems.append(
93+
SubmissionProblem(
94+
severity=SubmissionPrecheckSeverity.DANGER,
95+
message="This form is not open for submissions at the moment."
96+
)
97+
)
98+
form.submission_precheck.can_submit = False
99+
elif constants.FormFeatures.UNIQUE_RESPONDER.value in form.features:
100+
user_id = request.user.payload["id"] if request.user.is_authenticated else None
101+
if user_id:
102+
existing_response = await request.state.db.responses.find_one(
103+
{"form_id": form_id, "user.id": user_id}
104+
)
105+
if existing_response:
106+
form.submission_precheck.problems.append(
107+
SubmissionProblem(
108+
severity=SubmissionPrecheckSeverity.DANGER,
109+
message="You have already submitted a response to this form."
110+
)
111+
)
112+
form.submission_precheck.can_submit = False
113+
else:
114+
form.submission_precheck.problems.append(
115+
SubmissionProblem(
116+
severity=SubmissionPrecheckSeverity.SECONDARY,
117+
message="You must login at the bottom of the page before submitting this form."
118+
)
119+
)
120+
121+
return JSONResponse(form.dict(admin=admin))
60122

61123
@requires(["authenticated"])
62124
@api.validate(

0 commit comments

Comments
 (0)