From d855b862cac160773284d806fb7a6670e2d73983 Mon Sep 17 00:00:00 2001 From: Tiago Scholten Date: Tue, 13 Oct 2026 21:40:37 +0200 Subject: [PATCH] Basic signup form & answer system --- .../{event => communication}/activity.py | 18 +++-- .../{event => communication}/communication.py | 12 ++-- .../{event => communication}/enums.py | 0 .../{event => communication}/notes.txt | 0 src/fiber_package/communication/submodels.py | 14 ++++ src/fiber_package/entity/person.py | 4 +- src/fiber_package/event/submodels.py | 10 --- src/fiber_package/refs.py | 13 +++- .../signup/answer/answer_base.py | 8 +++ .../signup/answer/answer_open_questions.py | 9 +++ .../signup/elements/form_base_question.py | 4 ++ .../signup/elements/form_element.py | 8 +++ .../signup/elements/form_open_questions.py | 27 +++++++ .../signup/elements/form_text.py | 9 +++ .../signup/elements/form_title.py | 9 +++ src/fiber_package/signup/enums.py | 14 ++++ src/fiber_package/signup/notes.txt | 70 +++++++++++++++++++ src/fiber_package/signup/signup.py | 16 +++++ 18 files changed, 218 insertions(+), 27 deletions(-) rename src/fiber_package/{event => communication}/activity.py (61%) rename src/fiber_package/{event => communication}/communication.py (89%) rename src/fiber_package/{event => communication}/enums.py (100%) rename src/fiber_package/{event => communication}/notes.txt (100%) create mode 100644 src/fiber_package/communication/submodels.py delete mode 100644 src/fiber_package/event/submodels.py create mode 100644 src/fiber_package/signup/answer/answer_base.py create mode 100644 src/fiber_package/signup/answer/answer_open_questions.py create mode 100644 src/fiber_package/signup/elements/form_base_question.py create mode 100644 src/fiber_package/signup/elements/form_element.py create mode 100644 src/fiber_package/signup/elements/form_open_questions.py create mode 100644 src/fiber_package/signup/elements/form_text.py create mode 100644 src/fiber_package/signup/elements/form_title.py create mode 100644 src/fiber_package/signup/enums.py create mode 100644 src/fiber_package/signup/notes.txt create mode 100644 src/fiber_package/signup/signup.py diff --git a/src/fiber_package/event/activity.py b/src/fiber_package/communication/activity.py similarity index 61% rename from src/fiber_package/event/activity.py rename to src/fiber_package/communication/activity.py index 969e745..e6f0786 100644 --- a/src/fiber_package/event/activity.py +++ b/src/fiber_package/communication/activity.py @@ -1,14 +1,18 @@ -from typing import Annotated, Self +from typing import Literal, Self +from pydantic import model_validator + +from datetime import datetime from .communication import CommunicationModel from .submodels import ActivityPaymentInfo +from .enums import CommunicationCategory +from fiber_package.refs import SignupRefList + +from fiber_package.signup.elements.form_element import FormElement class ActivityModel(CommunicationModel): - category: Annotated[ - CommunicationCategory, - Field(pattern=CommunicationCategory.activity) - ] + category: Literal[CommunicationCategory.activity] slug: str # unique short name used in the public URL @@ -18,9 +22,9 @@ class ActivityModel(CommunicationModel): payment_info: ActivityPaymentInfo | None = None + form: list[FormElement] - #signuplist: GroupRef | None = None - # TODO: implement signup list (huge modular system) + signups: SignupRefList = SignupRefList([], "signup") # TODO: see Felt 'activiteiten' DB schema. Things like: # email, target group, maximal attendees .... diff --git a/src/fiber_package/event/communication.py b/src/fiber_package/communication/communication.py similarity index 89% rename from src/fiber_package/event/communication.py rename to src/fiber_package/communication/communication.py index 257b632..d5b17ee 100644 --- a/src/fiber_package/event/communication.py +++ b/src/fiber_package/communication/communication.py @@ -1,6 +1,4 @@ -from pydantic import BaseModel, model_validator, Field, HttpUrl -from enum import StrEnum -from datetime import datetime +from pydantic import Field, HttpUrl from fiber_package.base import FiberBaseModel @@ -16,7 +14,7 @@ class CommunicationModel(FiberBaseModel): name_en: str name_nl: str - association_tags = [] + association_tags: list[str] = Field(default_factory=list) # list of associations this communication belongs to subtitle_en: str @@ -36,11 +34,11 @@ class CommunicationModel(FiberBaseModel): visibility: bool # should this communication be shown to members at all? Useful for # temporarily hiding a communication - + only_members: bool # is this communication only visible to members? This refers to visibility # on the website, i.e. is this only visible to visitors who are logged in? - + archived: bool # an archived communication cannot be edited, only viewed for future - # reference \ No newline at end of file + # reference diff --git a/src/fiber_package/event/enums.py b/src/fiber_package/communication/enums.py similarity index 100% rename from src/fiber_package/event/enums.py rename to src/fiber_package/communication/enums.py diff --git a/src/fiber_package/event/notes.txt b/src/fiber_package/communication/notes.txt similarity index 100% rename from src/fiber_package/event/notes.txt rename to src/fiber_package/communication/notes.txt diff --git a/src/fiber_package/communication/submodels.py b/src/fiber_package/communication/submodels.py new file mode 100644 index 0000000..ffc6f30 --- /dev/null +++ b/src/fiber_package/communication/submodels.py @@ -0,0 +1,14 @@ +from decimal import Decimal +from typing import Annotated + +from pydantic import BaseModel, Field + +class ActivityPaymentInfo(BaseModel): + contribution: Annotated[ + Decimal, Field(decimal_places=2, ge=0) + ] + nnv_contribution: Annotated[ + Decimal | None, Field(None, decimal_places=2, ge=0) + ] = None + + diff --git a/src/fiber_package/entity/person.py b/src/fiber_package/entity/person.py index 4d05c34..eeabbeb 100644 --- a/src/fiber_package/entity/person.py +++ b/src/fiber_package/entity/person.py @@ -1,4 +1,4 @@ -from pydantic import model_validator, EmailStr, computed_field, field_serializer +from pydantic import model_validator, EmailStr, field_serializer from typing import Self, Literal from datetime import date @@ -31,7 +31,7 @@ class PersonModel(FiberBaseModel): if self.date_of_birth is None: return False if ref_date is None: - ref_data = date.today() + ref_date = date.today() age = ( ref_date.year - self.date_of_birth.year - ( (ref_date.month, ref_date.day) < (self.date_of_birth.month, self.date_of_birth.day) ) diff --git a/src/fiber_package/event/submodels.py b/src/fiber_package/event/submodels.py deleted file mode 100644 index e933c02..0000000 --- a/src/fiber_package/event/submodels.py +++ /dev/null @@ -1,10 +0,0 @@ -from decimal import Decimal -from typing import Annotated - -from pydantic import BaseModel, Field - -class ActivityPaymentInfo(BaseModel): - contribution: Annotated[Decimal, Field(decimal_places=2, ge=0)] - nnv_contribution: Annotated[Decimal | None, Field(None, decimal_places=2, ge=0)] = None - - diff --git a/src/fiber_package/refs.py b/src/fiber_package/refs.py index 6032df2..70879b1 100644 --- a/src/fiber_package/refs.py +++ b/src/fiber_package/refs.py @@ -11,7 +11,8 @@ CollectionName = Literal[ "entity", "committee", "committee_edition", - "guild" + "guild", + "signup" ] C = TypeVar("C", bound=str) @@ -47,3 +48,13 @@ CommitteeEditionRefList = RefList[Literal["committee_edition"]] GuildRef = Ref[Literal["guild"]] GuildRefList = RefList[Literal["guild"]] + +SignupRef = Ref[Literal["signup"]] +SignupRefList = RefList[Literal["signup"]] + +CommunicationRef = Ref[Literal["communication"]] +CommunicationRefList = Ref[Literal["communication"]] + +# Activities are stored in the collection of communications +ActivityRef = CommunicationRef +ActivityRefList = CommunicationRefList diff --git a/src/fiber_package/signup/answer/answer_base.py b/src/fiber_package/signup/answer/answer_base.py new file mode 100644 index 0000000..533793a --- /dev/null +++ b/src/fiber_package/signup/answer/answer_base.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel, ConfigDict +from fiber_package.signup.enums import FormQuestionCategory + +class AnswerBase(BaseModel): + model_config = ConfigDict(extra='allow') + + category: FormQuestionCategory + question_name: str diff --git a/src/fiber_package/signup/answer/answer_open_questions.py b/src/fiber_package/signup/answer/answer_open_questions.py new file mode 100644 index 0000000..a42b09f --- /dev/null +++ b/src/fiber_package/signup/answer/answer_open_questions.py @@ -0,0 +1,9 @@ +from typing import Literal + +from .answer_base import AnswerBase +from fiber_package.signup.enums import FormElementCategory + +class AnswerOpenQuestions(AnswerBase): + category: Literal[FormElementCategory.open_questions] + + answers: list[str] diff --git a/src/fiber_package/signup/elements/form_base_question.py b/src/fiber_package/signup/elements/form_base_question.py new file mode 100644 index 0000000..483d10b --- /dev/null +++ b/src/fiber_package/signup/elements/form_base_question.py @@ -0,0 +1,4 @@ +from .form_element import FormElement + +class FormBaseQuestion(FormElement): + required: bool diff --git a/src/fiber_package/signup/elements/form_element.py b/src/fiber_package/signup/elements/form_element.py new file mode 100644 index 0000000..b68be56 --- /dev/null +++ b/src/fiber_package/signup/elements/form_element.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel, ConfigDict +from fiber_package.signup.enums import FormElementCategory + +class FormElement(BaseModel): + model_config = ConfigDict(extra='allow') + + category: FormElementCategory + name: str diff --git a/src/fiber_package/signup/elements/form_open_questions.py b/src/fiber_package/signup/elements/form_open_questions.py new file mode 100644 index 0000000..9f4d774 --- /dev/null +++ b/src/fiber_package/signup/elements/form_open_questions.py @@ -0,0 +1,27 @@ +from typing import Literal +from pydantic import BaseModel + +from .form_base_question import FormBaseQuestion +from fiber_package.signup.enums import FormElementCategory + +class OpenQuestion(BaseModel): + question_en: str + question_nl: str + + explanation_en: str + explanation_nl: str + + placeholder_en: str = "" + placeholder_nl: str = "" + +class FormOpenQuestions(FormBaseQuestion): + category: Literal[FormElementCategory.open_questions] + + questions: list[OpenQuestion] + + title: str | None = None + pretext: str | None = None + posttext: str | None = None + + + diff --git a/src/fiber_package/signup/elements/form_text.py b/src/fiber_package/signup/elements/form_text.py new file mode 100644 index 0000000..991e42b --- /dev/null +++ b/src/fiber_package/signup/elements/form_text.py @@ -0,0 +1,9 @@ +from typing import Literal + +from .form_element import FormElement +from fiber_package.signup.enums import FormElementCategory + +class FormText(FormElement): + category: Literal[FormElementCategory.text_piece] + + text: str diff --git a/src/fiber_package/signup/elements/form_title.py b/src/fiber_package/signup/elements/form_title.py new file mode 100644 index 0000000..991d532 --- /dev/null +++ b/src/fiber_package/signup/elements/form_title.py @@ -0,0 +1,9 @@ +from typing import Literal + +from .form_element import FormElement +from fiber_package.signup.enums import FormElementCategory + +class FormText(FormElement): + category: Literal[FormElementCategory.title_text] + + title: str diff --git a/src/fiber_package/signup/enums.py b/src/fiber_package/signup/enums.py new file mode 100644 index 0000000..5455ec8 --- /dev/null +++ b/src/fiber_package/signup/enums.py @@ -0,0 +1,14 @@ +from enum import StrEnum +from typing import Literal + +''' +This category is used to differentiate types of form elements +''' +class FormElementCategory(StrEnum): + text_piece = "text_piece" + title_text = "title_text" + open_questions = "open_questions" + +FormQuestionCategory = Literal[ + FormElementCategory.open_questions +] diff --git a/src/fiber_package/signup/notes.txt b/src/fiber_package/signup/notes.txt new file mode 100644 index 0000000..d9a6f03 --- /dev/null +++ b/src/fiber_package/signup/notes.txt @@ -0,0 +1,70 @@ +Felt has sjablonen, stored in config + +sjablonen.(name) has fields: +* naam_nl = default name +* naam_en = ... +* info = used in the sjabloon overview +* types (array_keys ???) +* parameters = dict of parameters & type +* parametes_beschrijving = description of parameters +* parameters_sample = default values of all parameters + +On /activiteiten/bewerk we have a list of if statements +It groups the answers by type +* choises, feedback, teams, dinner, shirtsizes, nnv, +* public transport, reserve list, signups list + +* activites store their "types" as bitmask (1 = medisch, 2 = thuisfront ...) +* stored in models\Parameter +* list of parameter datatypes, + +Why is everything so builtin ?! Crazy. + +List of question types: +* medical --> alergies, meds, extra info +* thuisfront --> name contact, email contact, phone #1, phone #2 +* shirtsize --> pick enum from XXS, XS, S, M, L, XL, XXL +* identification --> document type, document number, expiry date, ... +* nnv --> state (notmember, later, member) & nnv number +* food --> joins for the meal (true/false), diet (text), vegetarian (t/f) +* public transport --> enum (no, week-only, weekend-only) +* travel / zorgverzekering --> verzekeraar, polisnummer, noodnummer, type (zorg of reis) +* questions --> parameter_id, slot, answer +* choices --> parameter_id, slot, choice +* feedback --> feedabck (str) +* team --> teamname (str) + +Maybe a bug? +If nnvbijdrage is set but nnv is not part of activity, +then everybody pays the LOW price as if NNV pays ??!! +Not a bug! You cannot set NNV if NNV is not part of sjabloon! +... its handled by custom js.... + + +omg it is TERRIBLE. In Silk every sjabloon has its OWN template ?! +so much work yikes + +Example for the "Studiereis" sjabloon: +* Some text (incl deadline) --> we can refer to variables and use HTML! +* Header (your signup is coupled to your current account!) +* Health insurance (+ text) +* Medical information (+ text) +* Home stayers (+text & checkmark about International Office) +* Identification (+text) +* NNV (+text) +* T-shirt +* OV +* Feedback + +Pattern: +* (title) (prefix text) item (suffix text) +Then possibly also: +* title-items & text-items + +------------- + +Ideas: +* Modular system! +* Create template activities (by creating activity, store as template & then delete?) + +* Combine complicated questions into custom question (see all above) diff --git a/src/fiber_package/signup/signup.py b/src/fiber_package/signup/signup.py new file mode 100644 index 0000000..c8e07ba --- /dev/null +++ b/src/fiber_package/signup/signup.py @@ -0,0 +1,16 @@ +from typing import Literal + +from fiber_package.base import FiberBaseModel +from fiber_package.refs import EntityRef, ActivityRef + +from fiber_package.signup.answer.answer_base import AnswerBase + +class Signup(FiberBaseModel): + collection_name: Literal["signup"] = "signup" + + attendee: EntityRef + activity: ActivityRef + + answers: list[AnswerBase] + + # TODO: signed_up, is_reserve, payment, confirmed, ....