Skip to content

Commit 536a16e

Browse files
committed
feat(forms): implement anonymity setting for new surveys and cloning existing surveys
1 parent 10b47a8 commit 536a16e

File tree

37 files changed

+663
-294
lines changed

37 files changed

+663
-294
lines changed

backend/events/kotaeexpo2025/management/commands/setup_kotaeexpo2025.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def setup_forms(self):
283283
slug="expense-claim",
284284
key_fields=["title", "amount"],
285285
login_required=True,
286-
anonymity="name_and_email",
286+
anonymity="NAME_AND_EMAIL",
287287
active_from=datetime(2025, 1, 1, 0, 0, tzinfo=self.tz),
288288
active_until=datetime(2025, 12, 31, 23, 59, tzinfo=self.tz),
289289
),

backend/events/tracon2025/management/commands/setup_tracon2025.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -482,21 +482,21 @@ def setup_forms(self):
482482
for survey in [
483483
SurveyDTO(
484484
slug="kickoff-signup",
485-
anonymity="name_and_email",
485+
anonymity="NAME_AND_EMAIL",
486486
max_responses_per_user=1,
487487
login_required=True,
488488
),
489489
SurveyDTO(
490490
slug="expense-claim",
491491
key_fields=["title", "amount"],
492492
login_required=True,
493-
anonymity="name_and_email",
493+
anonymity="NAME_AND_EMAIL",
494494
),
495495
SurveyDTO(
496496
slug="car-usage",
497497
key_fields=["title", "kilometers"],
498498
login_required=True,
499-
anonymity="name_and_email",
499+
anonymity="NAME_AND_EMAIL",
500500
),
501501
]:
502502
survey.save(self.event)

backend/forms/graphql/meta.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from enum import Enum
2+
13
import graphene
24
from django.conf import settings
35
from django.core.exceptions import SuspiciousOperation
@@ -10,17 +12,22 @@
1012
from ..models.response import Response
1113
from ..models.survey import Survey
1214
from .response import ProfileResponseType
13-
from .survey_full import SurveyType
15+
from .survey_full import FullSurveyType
1416

1517
DEFAULT_LANGUAGE: str = settings.LANGUAGE_CODE
1618

1719

1820
SurveyAppType = graphene.Enum.from_enum(SurveyApp)
1921

2022

23+
class SurveyRelation(Enum):
24+
SUBSCRIBED = "SUBSCRIBED"
25+
ACCESSIBLE = "ACCESSIBLE"
26+
27+
2128
class FormsEventMetaType(graphene.ObjectType):
2229
surveys = graphene.List(
23-
graphene.NonNull(SurveyType),
30+
graphene.NonNull(FullSurveyType),
2431
include_inactive=graphene.Boolean(),
2532
app=graphene.NonNull(SurveyAppType),
2633
)
@@ -53,7 +60,7 @@ def resolve_surveys(
5360
return qs
5461

5562
survey = graphene.Field(
56-
SurveyType,
63+
FullSurveyType,
5764
slug=graphene.String(required=True),
5865
app=graphene.Argument(SurveyAppType),
5966
)
@@ -114,15 +121,23 @@ def resolve_response(meta: FormsProfileMeta, info, id: str):
114121
)
115122

116123
@staticmethod
117-
def resolve_surveys(meta: FormsProfileMeta, info, event_slug: str | None = None):
124+
def resolve_surveys(
125+
meta: FormsProfileMeta,
126+
info,
127+
event_slug: str | None = None,
128+
relation: SurveyRelation = SurveyRelation.ACCESSIBLE,
129+
):
118130
"""
119-
Returns all surveys subscribed to by the current user.
131+
Returns all surveys accessible by the current user.
132+
To limit to surveys subscribed to, specify `relation: SUBSCRIBED`.
133+
To limit by event, specify `eventSlug: $eventSlug`.
120134
"""
121135
if info.context.user != meta.person.user:
122136
raise SuspiciousOperation("User mismatch")
123137

124-
surveys = Survey.objects.filter(subscribers=meta.person.user)
125-
138+
surveys = Survey.objects.all()
139+
if relation == SurveyRelation.SUBSCRIBED:
140+
surveys = surveys.filter(subscribers=meta.person.user)
126141
if event_slug:
127142
surveys = surveys.filter(event__slug=event_slug)
128143

@@ -141,8 +156,9 @@ def resolve_surveys(meta: FormsProfileMeta, info, event_slug: str | None = None)
141156

142157
surveys = graphene.NonNull(
143158
graphene.List(
144-
graphene.NonNull(SurveyType),
159+
graphene.NonNull(FullSurveyType),
145160
),
146161
event_slug=graphene.String(),
162+
relation=graphene.Argument(graphene.Enum.from_enum(SurveyRelation)),
147163
description=normalize_whitespace(resolve_surveys.__doc__ or ""),
148164
)
Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,72 @@
11
import graphene
2+
from django.http import HttpRequest
23

3-
from access.cbac import graphql_check_model
4+
from access.cbac import graphql_check_instance, graphql_check_model
45
from core.models import Event
56

6-
from ...models.survey import Survey
7-
from ..survey_full import SurveyType
7+
from ...models.survey import Anonymity, Survey, SurveyApp
8+
from ..survey_full import FullSurveyType
9+
from ..survey_limited import AnonymiType
810

911

1012
class CreateSurveyInput(graphene.InputObjectType):
1113
event_slug = graphene.String(required=True)
1214
survey_slug = graphene.String(required=True)
15+
copy_from = graphene.String()
16+
anonymity = graphene.InputField(AnonymiType, required=True)
1317

1418

1519
class CreateSurvey(graphene.Mutation):
1620
class Arguments:
1721
input = CreateSurveyInput(required=True)
1822

19-
survey = graphene.Field(SurveyType)
23+
survey = graphene.Field(FullSurveyType)
2024

2125
@staticmethod
2226
def mutate(
2327
root,
2428
info,
2529
input: CreateSurveyInput,
2630
):
31+
# TODO scope
2732
event = Event.objects.get(slug=input.event_slug)
33+
request: HttpRequest = info.context
2834

29-
# NOTE: default app=forms OK here as creating program forms goes through another mut
3035
graphql_check_model(
3136
Survey,
3237
event.scope,
3338
info,
3439
operation="create",
40+
app="forms", # program offer forms created via another mutation
3541
)
36-
survey = Survey(event=event, slug=input.survey_slug)
37-
survey.full_clean() # Validate fields
38-
survey.save()
42+
43+
anonymity = Anonymity(input.anonymity)
44+
if input.copy_from:
45+
source_event_slug, source_survey_slug = str(input.copy_from).split("/")
46+
source_survey = Survey.objects.get(
47+
event__slug=source_event_slug,
48+
slug=source_survey_slug,
49+
)
50+
51+
graphql_check_instance(
52+
source_survey,
53+
info,
54+
app=source_survey.app, # NOTE same check as in FormsProfileMeta.surveys
55+
)
56+
57+
survey = source_survey.clone(
58+
event=event,
59+
slug=str(input.survey_slug),
60+
anonymity=anonymity,
61+
app=SurveyApp.FORMS,
62+
created_by=request.user, # type: ignore
63+
)
64+
else:
65+
survey = Survey(
66+
event=event,
67+
slug=input.survey_slug,
68+
anonymity=anonymity.value,
69+
).with_mandatory_attributes_for_app(SurveyApp.FORMS)
70+
survey.full_clean() # Validate fields
71+
survey.save()
3972
return CreateSurvey(survey=survey) # type: ignore

backend/forms/graphql/mutations/create_survey_response.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def mutate(
5151
if survey.responses.filter(created_by=created_by).count() >= survey.max_responses_per_user:
5252
raise Exception("Maximum number of responses reached")
5353

54-
if survey.anonymity == "hard":
54+
if survey.anonymity == "HARD":
5555
created_by = None
5656
ip_address = ""
5757

backend/forms/graphql/mutations/update_form.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from ...models.form import Form
1111
from ...models.survey import Survey
12-
from ..survey_full import SurveyType
12+
from ..survey_full import FullSurveyType
1313

1414

1515
class FormForm(django_forms.ModelForm):
@@ -40,7 +40,7 @@ class UpdateForm(graphene.Mutation):
4040
class Arguments:
4141
input = UpdateFormInput(required=True)
4242

43-
survey = graphene.Field(SurveyType)
43+
survey = graphene.Field(FullSurveyType)
4444

4545
@staticmethod
4646
def mutate(

backend/forms/graphql/mutations/update_form_fields.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from ...models.field import Field
88
from ...models.survey import Survey
9-
from ..survey_full import SurveyType
9+
from ..survey_full import FullSurveyType
1010

1111

1212
class UpdateFormFieldsInput(graphene.InputObjectType):
@@ -24,7 +24,7 @@ class UpdateFormFields(graphene.Mutation):
2424
class Arguments:
2525
input = UpdateFormFieldsInput(required=True)
2626

27-
survey = graphene.Field(SurveyType)
27+
survey = graphene.Field(FullSurveyType)
2828

2929
@staticmethod
3030
def mutate(

backend/forms/graphql/mutations/update_survey.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from access.cbac import graphql_check_instance
88
from core.utils.form_utils import camel_case_keys_to_snake_case
99

10-
from ...models.survey import Survey
11-
from ..survey_full import SurveyType
10+
from ...models.survey import Survey, SurveyApp
11+
from ..survey_full import FullSurveyType
1212

1313

1414
class SurveyForm(django_forms.ModelForm):
@@ -38,7 +38,7 @@ class UpdateSurvey(graphene.Mutation):
3838
class Arguments:
3939
input = UpdateSurveyInput(required=True)
4040

41-
survey = graphene.Field(SurveyType)
41+
survey = graphene.Field(FullSurveyType)
4242

4343
@staticmethod
4444
def mutate(
@@ -64,6 +64,8 @@ def mutate(
6464
if not form.is_valid():
6565
raise django_forms.ValidationError(form.errors) # type: ignore
6666

67-
form.save()
67+
survey: Survey = form.save(commit=False)
68+
survey = survey.with_mandatory_attributes_for_app(SurveyApp.FORMS)
69+
survey.save()
6870

6971
return UpdateSurvey(survey=survey) # type: ignore

backend/forms/graphql/response.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def resolve_created_by(response: Response, info) -> User | None:
5555
Returns the user who submitted the response. If response is to an anonymous survey,
5656
this information will not be available.
5757
"""
58-
if (survey := response.form.survey) and survey.anonymity in ("hard", "soft"):
58+
if (survey := response.form.survey) and survey.anonymity in ("HARD", "SOFT"):
5959
return None
6060

6161
return response.created_by

backend/forms/graphql/survey_full.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from graphene.types.generic import GenericScalar
55

66
from access.cbac import graphql_check_instance
7+
from core.graphql.event_limited import LimitedEventType
78
from core.utils import normalize_whitespace
89
from dimensions.graphql.dimension_filter_input import DimensionFilterInput
910

@@ -18,7 +19,7 @@
1819
DEFAULT_LANGUAGE: str = settings.LANGUAGE_CODE
1920

2021

21-
class SurveyType(LimitedSurveyType):
22+
class FullSurveyType(LimitedSurveyType):
2223
@staticmethod
2324
def resolve_form(
2425
parent: Survey,
@@ -212,6 +213,9 @@ def resolve_can_remove_responses(survey: Survey, info):
212213
description=normalize_whitespace(resolve_can_remove_responses.__doc__ or ""),
213214
)
214215

216+
# TODO change to Scope
217+
event = graphene.NonNull(LimitedEventType)
218+
215219
class Meta:
216220
model = Survey
217221
fields = (
@@ -224,4 +228,5 @@ class Meta:
224228
"anonymity",
225229
"max_responses_per_user",
226230
"protect_responses",
231+
"event",
227232
)

0 commit comments

Comments
 (0)