Basic signup form & answer system

This commit is contained in:
Tiago Scholten 2026-10-13 21:40:37 +02:00
parent 9729b89477
commit d855b862ca
18 changed files with 218 additions and 27 deletions

View File

@ -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 .communication import CommunicationModel
from .submodels import ActivityPaymentInfo 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): class ActivityModel(CommunicationModel):
category: Annotated[ category: Literal[CommunicationCategory.activity]
CommunicationCategory,
Field(pattern=CommunicationCategory.activity)
]
slug: str slug: str
# unique short name used in the public URL # unique short name used in the public URL
@ -18,9 +22,9 @@ class ActivityModel(CommunicationModel):
payment_info: ActivityPaymentInfo | None = None payment_info: ActivityPaymentInfo | None = None
form: list[FormElement]
#signuplist: GroupRef | None = None signups: SignupRefList = SignupRefList([], "signup")
# TODO: implement signup list (huge modular system)
# TODO: see Felt 'activiteiten' DB schema. Things like: # TODO: see Felt 'activiteiten' DB schema. Things like:
# email, target group, maximal attendees .... # email, target group, maximal attendees ....

View File

@ -1,6 +1,4 @@
from pydantic import BaseModel, model_validator, Field, HttpUrl from pydantic import Field, HttpUrl
from enum import StrEnum
from datetime import datetime
from fiber_package.base import FiberBaseModel from fiber_package.base import FiberBaseModel
@ -16,7 +14,7 @@ class CommunicationModel(FiberBaseModel):
name_en: str name_en: str
name_nl: str name_nl: str
association_tags = [] association_tags: list[str] = Field(default_factory=list)
# list of associations this communication belongs to # list of associations this communication belongs to
subtitle_en: str subtitle_en: str

View File

@ -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

View File

@ -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 typing import Self, Literal
from datetime import date from datetime import date
@ -31,7 +31,7 @@ class PersonModel(FiberBaseModel):
if self.date_of_birth is None: if self.date_of_birth is None:
return False return False
if ref_date is None: if ref_date is None:
ref_data = date.today() ref_date = date.today()
age = ( ref_date.year - self.date_of_birth.year age = ( ref_date.year - self.date_of_birth.year
- ( (ref_date.month, ref_date.day) - ( (ref_date.month, ref_date.day)
< (self.date_of_birth.month, self.date_of_birth.day) ) < (self.date_of_birth.month, self.date_of_birth.day) )

View File

@ -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

View File

@ -11,7 +11,8 @@ CollectionName = Literal[
"entity", "entity",
"committee", "committee",
"committee_edition", "committee_edition",
"guild" "guild",
"signup"
] ]
C = TypeVar("C", bound=str) C = TypeVar("C", bound=str)
@ -47,3 +48,13 @@ CommitteeEditionRefList = RefList[Literal["committee_edition"]]
GuildRef = Ref[Literal["guild"]] GuildRef = Ref[Literal["guild"]]
GuildRefList = RefList[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

View File

@ -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

View File

@ -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]

View File

@ -0,0 +1,4 @@
from .form_element import FormElement
class FormBaseQuestion(FormElement):
required: bool

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
]

View File

@ -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)

View File

@ -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, ....