Compare commits
24 Commits
d275acde9f
...
9d28be16ba
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d28be16ba | |||
| f308f96616 | |||
| 249e6851b4 | |||
| 5c4aac62cb | |||
| d94e1bda81 | |||
| 0cc1bf4861 | |||
| 5e4787665e | |||
| 22859b679f | |||
| 9f513271ac | |||
| 71853ac32d | |||
| 74a4c947a1 | |||
| 36dadd7ac1 | |||
| 5e7f72ea2f | |||
| 0c1683c74b | |||
| c0e592e973 | |||
| f8c576263c | |||
| 6d8b06dfc7 | |||
| 0252f2aeb6 | |||
| 0fd92120d9 | |||
| d855b862ca | |||
| 9729b89477 | |||
| 4c62bc4de8 | |||
| 337d27425e | |||
| 8c9d6bc6ef |
@ -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) )
|
||||
|
||||
75
src/fiber_package/event/activity.py
Normal file
75
src/fiber_package/event/activity.py
Normal file
@ -0,0 +1,75 @@
|
||||
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.signup import SignupModel
|
||||
from fiber_package.signup.util import validate_answer
|
||||
from fiber_package.signup.enums import is_question_category
|
||||
|
||||
from fiber_package.signup.element.form_base_question import FormBaseQuestion
|
||||
|
||||
from fiber_package.signup.element.types import FormElementUnion, FormTextUnion
|
||||
|
||||
class ActivityModel(CommunicationModel):
|
||||
category: Literal[CommunicationCategory.activity]
|
||||
|
||||
slug: str
|
||||
# unique short name used in the public URL
|
||||
|
||||
start_time: datetime
|
||||
end_time: datetime
|
||||
|
||||
payment_info: ActivityPaymentInfo | None = None
|
||||
|
||||
form: list[FormElementUnion]
|
||||
|
||||
signups: SignupRefList = SignupRefList([], "signup")
|
||||
|
||||
# TODO: see Felt 'activiteiten' DB schema. Things like:
|
||||
# email, target group, maximal attendees ....
|
||||
|
||||
@model_validator(mode='after')
|
||||
def check_end_after_start(self) -> Self:
|
||||
if self.end_time < self.start_time:
|
||||
raise ValueError("End time cannot be earlier than start time")
|
||||
return self
|
||||
|
||||
def validate_signup_form(self, signup: SignupModel) -> None:
|
||||
required = set()
|
||||
answerable = set()
|
||||
|
||||
question_dict: dict[str, FormBaseQuestion] = {}
|
||||
|
||||
for element in self.form:
|
||||
if not is_question_category(element.category):
|
||||
continue
|
||||
# so we know it is istance of FormBaseQuestion
|
||||
answerable.add(element.name)
|
||||
if element.required:
|
||||
required.add(element.name)
|
||||
question_dict[element.name] = element
|
||||
|
||||
answers = signup.answers
|
||||
for answer in answers:
|
||||
q_name = answer.question_name
|
||||
if q_name not in answerable:
|
||||
raise ValueError(
|
||||
f"Form contains answer to "
|
||||
f"non-existing question {q_name}."
|
||||
)
|
||||
answerable.remove(q_name)
|
||||
if answer.question_name in required:
|
||||
required.remove(q_name)
|
||||
|
||||
question = question_dict[q_name]
|
||||
validate_answer(question, answer)
|
||||
|
||||
|
||||
if len(required) > 0:
|
||||
raise ValueError("Form does not answer all required questions.")
|
||||
44
src/fiber_package/event/communication.py
Normal file
44
src/fiber_package/event/communication.py
Normal file
@ -0,0 +1,44 @@
|
||||
from pydantic import Field, HttpUrl
|
||||
|
||||
from fiber_package.base import FiberBaseModel
|
||||
|
||||
from .enums import CommunicationCategory
|
||||
|
||||
'''
|
||||
A "communication" is a piece of communication/promotion to be shared with
|
||||
members, which may appear on the website and in the weekly mailing.
|
||||
'''
|
||||
class CommunicationModel(FiberBaseModel):
|
||||
category: CommunicationCategory
|
||||
|
||||
name_en: str
|
||||
name_nl: str
|
||||
|
||||
association_tags: list[str] = Field(default_factory=list)
|
||||
# list of associations this communication belongs to
|
||||
|
||||
subtitle_en: str
|
||||
subtitle_nl: str
|
||||
# subtitles currently (2026) appear only in the table of contents in the
|
||||
# weekly mailing; they are meant to be a short catchy flavour text
|
||||
|
||||
description_en: str
|
||||
description_nl: str
|
||||
|
||||
image: HttpUrl | None = None
|
||||
# often, a poster comes associated with a communication (in particular if
|
||||
# it's an activity). In that case, the poster is uploaded to the cloud and
|
||||
# the share link is attached to the communication
|
||||
# TODO: potentially rethink the way images are attached to communications
|
||||
|
||||
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
|
||||
10
src/fiber_package/event/enums.py
Normal file
10
src/fiber_package/event/enums.py
Normal file
@ -0,0 +1,10 @@
|
||||
from enum import StrEnum
|
||||
|
||||
'''
|
||||
The category is only used to place the communication correctly in the weekly
|
||||
mailing and the board-website, it is not visible (or relevant) to readers
|
||||
'''
|
||||
class CommunicationCategory(StrEnum):
|
||||
announcement = "announcement"
|
||||
news = "news"
|
||||
activity = "activity"
|
||||
48
src/fiber_package/event/notes.txt
Normal file
48
src/fiber_package/event/notes.txt
Normal file
@ -0,0 +1,48 @@
|
||||
Okay some ideas:
|
||||
|
||||
We want a modular system.
|
||||
A signup form = list of signup questions
|
||||
A signup = list of signup answers
|
||||
|
||||
|
||||
Examples of questions:
|
||||
* Dietary restrictions
|
||||
* Insurance info
|
||||
* Pick option from list
|
||||
* Team (interesting one, as people can also make new teams)
|
||||
|
||||
|
||||
class SignupQuestion
|
||||
--> SignupQuestionDiet, SignupQuestionInsurance, SignupQuestionOption ....
|
||||
Questions differentiate through enum question_type
|
||||
Questions can have parameters like Option
|
||||
|
||||
--------------
|
||||
|
||||
Every SignupQuestionXYZ has a SignupAnswerXYZ
|
||||
|
||||
|
||||
An activity consists of:
|
||||
* usual event stuff, email, max_attendees, ....
|
||||
* a slug!
|
||||
* dict of SignupQuestions
|
||||
* list of QuestionOrder
|
||||
|
||||
A signup then consists of:
|
||||
* ActivityRef (what activity)
|
||||
* EntityRef (who signed up)
|
||||
* optional payment_id
|
||||
* optional is_reserve
|
||||
* optional confirmed (true if paid)
|
||||
* dict of SignupAnswers
|
||||
|
||||
--------------
|
||||
|
||||
When saving a signup, we check:
|
||||
* get activity
|
||||
* is person allowed to sign up?
|
||||
* do we have enough signup answers
|
||||
* for every answer: does answer validate type of question?
|
||||
|
||||
|
||||
|
||||
14
src/fiber_package/event/submodels.py
Normal file
14
src/fiber_package/event/submodels.py
Normal 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
|
||||
|
||||
|
||||
@ -11,7 +11,9 @@ CollectionName = Literal[
|
||||
"entity",
|
||||
"committee",
|
||||
"committee_edition",
|
||||
"guild"
|
||||
"guild",
|
||||
"signup",
|
||||
"event"
|
||||
]
|
||||
|
||||
C = TypeVar("C", bound=str)
|
||||
@ -47,3 +49,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["event"]]
|
||||
CommunicationRefList = Ref[Literal["event"]]
|
||||
|
||||
# Activities are stored in the collection of communications
|
||||
ActivityRef = CommunicationRef
|
||||
ActivityRefList = CommunicationRefList
|
||||
|
||||
8
src/fiber_package/signup/answer/answer_base.py
Normal file
8
src/fiber_package/signup/answer/answer_base.py
Normal 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
|
||||
9
src/fiber_package/signup/answer/answer_checkmarks.py
Normal file
9
src/fiber_package/signup/answer/answer_checkmarks.py
Normal file
@ -0,0 +1,9 @@
|
||||
from typing import Literal
|
||||
|
||||
from .answer_base import AnswerBase
|
||||
from fiber_package.signup.enums import FormElementCategory
|
||||
|
||||
class AnswerCheckmarks(AnswerBase):
|
||||
category: Literal[FormElementCategory.checkmarks]
|
||||
|
||||
answer_list: list[bool]
|
||||
9
src/fiber_package/signup/answer/answer_open_questions.py
Normal file
9
src/fiber_package/signup/answer/answer_open_questions.py
Normal 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]
|
||||
|
||||
answer_list: list[str]
|
||||
11
src/fiber_package/signup/answer/types.py
Normal file
11
src/fiber_package/signup/answer/types.py
Normal file
@ -0,0 +1,11 @@
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .answer_open_questions import AnswerOpenQuestions
|
||||
from .answer_checkmarks import AnswerCheckmarks
|
||||
|
||||
AnswerUnion = Annotated[
|
||||
AnswerOpenQuestions | AnswerCheckmarks,
|
||||
Field(discriminator="category")
|
||||
]
|
||||
11
src/fiber_package/signup/element/form_base_question.py
Normal file
11
src/fiber_package/signup/element/form_base_question.py
Normal file
@ -0,0 +1,11 @@
|
||||
from .form_element import FormElement
|
||||
from fiber_package.signup.enums import FormQuestionCategory
|
||||
|
||||
class FormBaseQuestion(FormElement):
|
||||
category: FormQuestionCategory
|
||||
|
||||
required: bool
|
||||
|
||||
title: str | None = None
|
||||
pretext: str | None = None
|
||||
posttext: str | None = None
|
||||
5
src/fiber_package/signup/element/form_base_text.py
Normal file
5
src/fiber_package/signup/element/form_base_text.py
Normal file
@ -0,0 +1,5 @@
|
||||
from .form_element import FormElement
|
||||
from fiber_package.signup.enums import FormTextCategory
|
||||
|
||||
class FormBaseText(FormElement):
|
||||
category: FormTextCategory
|
||||
29
src/fiber_package/signup/element/form_checkmarks.py
Normal file
29
src/fiber_package/signup/element/form_checkmarks.py
Normal file
@ -0,0 +1,29 @@
|
||||
from typing import Literal
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .form_base_question import FormBaseQuestion
|
||||
from fiber_package.signup.enums import FormElementCategory
|
||||
|
||||
from fiber_package.signup.answer.answer_checkmarks import AnswerCheckmarks
|
||||
|
||||
class CheckmarkQuestion(BaseModel):
|
||||
question_en: str
|
||||
question_nl: str
|
||||
default: bool = False
|
||||
|
||||
class FormCheckmarks(FormBaseQuestion):
|
||||
category: Literal[FormElementCategory.checkmarks]
|
||||
|
||||
questions: list[CheckmarkQuestion]
|
||||
|
||||
def validate_answer(self, answer: AnswerCheckmarks) -> None:
|
||||
if answer.question_name != self.name:
|
||||
raise ValueError("Answer question name does not match.")
|
||||
|
||||
answer_list = answer.answer_list
|
||||
if len(answer_list) != len(self.questions):
|
||||
raise ValueError("Length of answer question list does not match.")
|
||||
|
||||
|
||||
|
||||
|
||||
8
src/fiber_package/signup/element/form_element.py
Normal file
8
src/fiber_package/signup/element/form_element.py
Normal 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
|
||||
34
src/fiber_package/signup/element/form_open_questions.py
Normal file
34
src/fiber_package/signup/element/form_open_questions.py
Normal file
@ -0,0 +1,34 @@
|
||||
from typing import Literal
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .form_base_question import FormBaseQuestion
|
||||
from fiber_package.signup.enums import FormElementCategory
|
||||
|
||||
from fiber_package.signup.answer.answer_open_questions import AnswerOpenQuestions
|
||||
|
||||
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]
|
||||
|
||||
def validate_answer(self, answer: AnswerOpenQuestions) -> None:
|
||||
if answer.question_name != self.name:
|
||||
raise ValueError("Answer question name does not match.")
|
||||
|
||||
answer_list = answer.answer_list
|
||||
if len(answer_list) != len(self.questions):
|
||||
raise ValueError("Length of answer question list does not match.")
|
||||
|
||||
|
||||
|
||||
|
||||
9
src/fiber_package/signup/element/form_text.py
Normal file
9
src/fiber_package/signup/element/form_text.py
Normal 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
|
||||
9
src/fiber_package/signup/element/form_title.py
Normal file
9
src/fiber_package/signup/element/form_title.py
Normal file
@ -0,0 +1,9 @@
|
||||
from typing import Literal
|
||||
|
||||
from .form_element import FormElement
|
||||
from fiber_package.signup.enums import FormElementCategory
|
||||
|
||||
class FormTitle(FormElement):
|
||||
category: Literal[FormElementCategory.title_text]
|
||||
|
||||
title: str
|
||||
23
src/fiber_package/signup/element/types.py
Normal file
23
src/fiber_package/signup/element/types.py
Normal file
@ -0,0 +1,23 @@
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .form_open_questions import FormOpenQuestions
|
||||
from .form_checkmarks import FormCheckmarks
|
||||
from .form_text import FormText
|
||||
from .form_title import FormTitle
|
||||
|
||||
FormTextUnion = Annotated[
|
||||
FormText | FormTitle,
|
||||
Field(discriminator="category")
|
||||
]
|
||||
|
||||
FormQuestionUnion = Annotated[
|
||||
FormOpenQuestions | FormCheckmarks,
|
||||
Field(discriminator="category")
|
||||
]
|
||||
|
||||
FormElementUnion = Annotated[
|
||||
FormTextUnion | FormQuestionUnion,
|
||||
Field(discriminator="category")
|
||||
]
|
||||
24
src/fiber_package/signup/enums.py
Normal file
24
src/fiber_package/signup/enums.py
Normal file
@ -0,0 +1,24 @@
|
||||
from enum import StrEnum
|
||||
from typing import Literal, get_args
|
||||
|
||||
'''
|
||||
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"
|
||||
checkmarks = "checkmarks"
|
||||
|
||||
FormQuestionCategory = Literal[
|
||||
FormElementCategory.open_questions,
|
||||
FormElementCategory.checkmarks
|
||||
]
|
||||
|
||||
FormTextCategory = Literal[
|
||||
FormElementCategory.text_piece,
|
||||
FormElementCategory.title_text
|
||||
]
|
||||
|
||||
def is_question_category(category: FormElementCategory) -> bool:
|
||||
return category in get_args(FormQuestionCategory)
|
||||
13
src/fiber_package/signup/howto-add-new-question.txt
Normal file
13
src/fiber_package/signup/howto-add-new-question.txt
Normal file
@ -0,0 +1,13 @@
|
||||
In order to add a new question type you need to:
|
||||
|
||||
1) Make a new tag in enums.py
|
||||
2) add it to the list of question tags
|
||||
3) Create the FormXXX in element
|
||||
4) Create the AnswerXXX in answer
|
||||
5) Add it to the answer union type in answer.types
|
||||
6) Add it to the question union type in element.types
|
||||
Tip: run ./pre-commit to run the typechecker to see if you got your imports right!
|
||||
7) Add it to fiber-django
|
||||
6a) Add it to the process_question function event/util.py
|
||||
6b) Add it to core/templates/form
|
||||
6c) Add it to the form page (currently dlfsite/templates/testing/form.html)
|
||||
65
src/fiber_package/signup/notes.txt
Normal file
65
src/fiber_package/signup/notes.txt
Normal file
@ -0,0 +1,65 @@
|
||||
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)
|
||||
|
||||
|
||||
|
||||
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)
|
||||
16
src/fiber_package/signup/signup.py
Normal file
16
src/fiber_package/signup/signup.py
Normal 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.types import AnswerUnion
|
||||
|
||||
class SignupModel(FiberBaseModel):
|
||||
collection_name: Literal["signup"] = "signup"
|
||||
|
||||
attendee: EntityRef
|
||||
activity: ActivityRef
|
||||
|
||||
answers: list[AnswerUnion]
|
||||
|
||||
# TODO: signed_up, is_reserve, payment, confirmed, ....
|
||||
18
src/fiber_package/signup/util.py
Normal file
18
src/fiber_package/signup/util.py
Normal file
@ -0,0 +1,18 @@
|
||||
from fiber_package.signup.element.form_base_question import FormBaseQuestion
|
||||
from fiber_package.signup.element.form_open_questions import FormOpenQuestions
|
||||
|
||||
from fiber_package.signup.answer.answer_base import AnswerBase
|
||||
from fiber_package.signup.enums import FormElementCategory
|
||||
|
||||
from fiber_package.signup.answer.answer_open_questions import AnswerOpenQuestions
|
||||
|
||||
def validate_answer(question: FormBaseQuestion, answer: AnswerBase) -> None:
|
||||
if question.category != answer.category:
|
||||
raise ValueError("Question and answer category do not match!")
|
||||
|
||||
if question.category == FormElementCategory.open_questions:
|
||||
question_val = FormOpenQuestions.model_validate(question.model_dump())
|
||||
answer_val = AnswerOpenQuestions.model_validate(answer.model_dump())
|
||||
question_val.validate_answer(answer_val)
|
||||
|
||||
# TODO: implement
|
||||
Loading…
x
Reference in New Issue
Block a user