diff --git a/backend/api/conferences/types.py b/backend/api/conferences/types.py index c75d451ae6..e5a7238c8f 100644 --- a/backend/api/conferences/types.py +++ b/backend/api/conferences/types.py @@ -12,6 +12,8 @@ from strawberry import ID from api.cms.types import FAQ, Menu from api.events.types import Event +from api.generic_forms.types import Form as GenericForm +from api.generic_forms.types import FormPurpose from api.languages.types import Language from api.pretix.query import get_conference_tickets, get_voucher from api.pretix.types import TicketItem, Voucher @@ -197,6 +199,10 @@ def is_voting_closed(self, info: Info) -> bool: def deadline(self, info: Info, type: str) -> Deadline | None: return self.deadlines.filter(type=type).first() + @strawberry.field + def form(self, info: Info, purpose: FormPurpose) -> GenericForm | None: + return self.forms.filter(purpose=purpose).first() + @strawberry.field def audience_levels(self, info: Info) -> list[AudienceLevel]: return self.audience_levels.all() diff --git a/backend/api/generic_forms/__init__.py b/backend/api/generic_forms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/api/generic_forms/tests/__init__.py b/backend/api/generic_forms/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/api/generic_forms/tests/test_form_query.py b/backend/api/generic_forms/tests/test_form_query.py new file mode 100644 index 0000000000..83a7099f7b --- /dev/null +++ b/backend/api/generic_forms/tests/test_form_query.py @@ -0,0 +1,131 @@ +import pytest + +from conferences.tests.factories import ConferenceFactory +from generic_forms.models import Form, FormQuestion +from generic_forms.tests.factories import FormFactory, FormQuestionFactory + +pytestmark = pytest.mark.django_db + + +def _query_form(graphql_client, conference, purpose="GRANT"): + query = """query($code: String!, $purpose: FormPurpose!) { + conference(code: $code) { + form(purpose: $purpose) { + id + name + questions { + id + label + description + questionType + required + maxLength + options { + id + label + } + } + } + } + }""" + + return graphql_client.query( + query, variables={"code": conference.code, "purpose": purpose} + ) + + +def test_form_is_none_when_not_configured(graphql_client): + conference = ConferenceFactory() + + result = _query_form(graphql_client, conference) + + assert result["data"]["conference"]["form"] is None + + +def test_form_is_none_when_only_another_purpose_is_configured(graphql_client): + form = FormFactory(purpose=Form.Purpose.GENERIC) + + result = _query_form(graphql_client, form.conference, purpose="GRANT") + + assert result["data"]["conference"]["form"] is None + + +def test_form_belongs_to_the_requested_conference(graphql_client): + FormFactory(purpose=Form.Purpose.GRANT, name="Other conference form") + conference = ConferenceFactory() + + result = _query_form(graphql_client, conference) + + assert result["data"]["conference"]["form"] is None + + +def test_form_with_questions(graphql_client): + form = FormFactory(purpose=Form.Purpose.GRANT, name="Grant form") + question = FormQuestionFactory( + form=form, + label="Why do you want to attend?", + description="Tell us more", + question_type=FormQuestion.QuestionType.TEXTAREA, + required=True, + max_length=500, + order=0, + ) + + result = _query_form(graphql_client, form.conference) + + data = result["data"]["conference"]["form"] + assert data["id"] == str(form.id) + assert data["name"] == "Grant form" + assert data["questions"] == [ + { + "id": str(question.id), + "label": "Why do you want to attend?", + "description": "Tell us more", + "questionType": "TEXTAREA", + "required": True, + "maxLength": 500, + "options": [], + } + ] + + +def test_select_question_options_are_exposed(graphql_client): + form = FormFactory(purpose=Form.Purpose.GRANT) + FormQuestionFactory( + form=form, + question_type=FormQuestion.QuestionType.SELECT, + options=[ + {"id": "vegan", "label": "Vegan"}, + {"id": "veggie", "label": "Veggie"}, + ], + ) + + result = _query_form(graphql_client, form.conference) + + assert result["data"]["conference"]["form"]["questions"][0]["options"] == [ + {"id": "vegan", "label": "Vegan"}, + {"id": "veggie", "label": "Veggie"}, + ] + + +def test_questions_follow_the_configured_order(graphql_client): + form = FormFactory(purpose=Form.Purpose.GRANT) + second = FormQuestionFactory(form=form, order=1) + first = FormQuestionFactory(form=form, order=0) + third = FormQuestionFactory(form=form, order=2) + + result = _query_form(graphql_client, form.conference) + + ids = [q["id"] for q in result["data"]["conference"]["form"]["questions"]] + assert ids == [str(first.id), str(second.id), str(third.id)] + + +def test_inactive_questions_are_hidden(graphql_client): + form = FormFactory(purpose=Form.Purpose.GRANT) + active = FormQuestionFactory(form=form, active=True) + FormQuestionFactory(form=form, active=False) + + result = _query_form(graphql_client, form.conference) + + ids = [q["id"] for q in result["data"]["conference"]["form"]["questions"]] + assert ids == [str(active.id)] diff --git a/backend/api/generic_forms/types.py b/backend/api/generic_forms/types.py new file mode 100644 index 0000000000..37c9af9430 --- /dev/null +++ b/backend/api/generic_forms/types.py @@ -0,0 +1,43 @@ +import strawberry + +from api.context import Info +from generic_forms.models import Form as FormModel +from generic_forms.models import FormQuestion as FormQuestionModel + +FormPurpose = strawberry.enum(FormModel.Purpose, name="FormPurpose") +FormQuestionType = strawberry.enum( + FormQuestionModel.QuestionType, name="FormQuestionType" +) + + +@strawberry.type +class FormQuestionOption: + id: str + label: str + + +@strawberry.type +class FormQuestion: + id: strawberry.ID + label: str + description: str + question_type: FormQuestionType + required: bool + max_length: int | None + + @strawberry.field + def options(self, info: Info) -> list[FormQuestionOption]: + return [ + FormQuestionOption(id=option["id"], label=option["label"]) + for option in self.options + ] + + +@strawberry.type +class Form: + id: strawberry.ID + name: str + + @strawberry.field + def questions(self, info: Info) -> list[FormQuestion]: + return self.questions.filter(active=True)