diff options
author | 2020-12-01 20:56:49 +0000 | |
---|---|---|
committer | 2020-12-01 20:56:49 +0000 | |
commit | 0e34c3bd42bba9a3274ea7f83cdce9b0b828db14 (patch) | |
tree | 1d687734f79effb728f27a0b2dd24edc7d7ae910 /backend | |
parent | Merge pull request #12 from python-discord/ks123/admin-authentication (diff) | |
parent | Ignore too long line for if statement (diff) |
Merge pull request #11 from python-discord/ks123/models
Diffstat (limited to 'backend')
-rw-r--r-- | backend/constants.py | 41 | ||||
-rw-r--r-- | backend/models/__init__.py | 4 | ||||
-rw-r--r-- | backend/models/form.py | 27 | ||||
-rw-r--r-- | backend/models/question.py | 54 |
4 files changed, 126 insertions, 0 deletions
diff --git a/backend/constants.py b/backend/constants.py index 746e277..3b8bec8 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -3,6 +3,8 @@ load_dotenv() import os # noqa import binascii # noqa +from enum import Enum # noqa + DATABASE_URL = os.getenv("DATABASE_URL") MONGO_DATABASE = os.getenv("MONGO_DATABASE", "pydis_forms") @@ -15,3 +17,42 @@ OAUTH2_REDIRECT_URI = os.getenv( ) SECRET_KEY = os.getenv("SECRET_KEY", binascii.hexlify(os.urandom(30)).decode()) + +QUESTION_TYPES = [ + "radio", + "checkbox", + "select", + "short_text", + "textarea", + "code", + "range", + "section", +] + +REQUIRED_QUESTION_TYPE_DATA = { + "radio": { + "options": list, + }, + "select": { + "options": list, + }, + "code": { + "language": str, + }, + "range": { + "options": list, + }, + "section": { + "text": str, + }, +} + + +class FormFeatures(Enum): + """Lists form features. Read more in SCHEMA.md.""" + + DISCOVERABLE = "DISCOVERABLE" + REQUIRES_LOGIN = "REQUIRES_LOGIN" + OPEN = "OPEN" + COLLECT_EMAIL = "COLLECT_EMAIL" + DISABLE_ANTISPAM = "DISABLE_ANTISPAM" diff --git a/backend/models/__init__.py b/backend/models/__init__.py new file mode 100644 index 0000000..80abf6f --- /dev/null +++ b/backend/models/__init__.py @@ -0,0 +1,4 @@ +from .form import Form +from .question import Question + +__all__ = ["Form", "Question"] diff --git a/backend/models/form.py b/backend/models/form.py new file mode 100644 index 0000000..d0f0a3c --- /dev/null +++ b/backend/models/form.py @@ -0,0 +1,27 @@ +import typing as t + +from pydantic import BaseModel, Field, validator + +from backend.constants import FormFeatures +from backend.models import Question + + +class Form(BaseModel): + """Schema model for form.""" + + id: str = Field(alias="_id") + features: t.List[str] + questions: t.List[Question] + + @validator("features") + def validate_features(self, value: t.List[str]) -> t.Optional[t.List[str]]: + """Validates is all features in allowed list.""" + # Uppercase everything to avoid mixed case in DB + value = [v.upper() for v in value] + if not all(v in FormFeatures.__members__.values() for v in value): + raise ValueError("Form features list contains one or more invalid values.") + + if FormFeatures.COLLECT_EMAIL in value and FormFeatures.REQUIRES_LOGIN not in value: # noqa + raise ValueError("COLLECT_EMAIL feature require REQUIRES_LOGIN feature.") + + return value diff --git a/backend/models/question.py b/backend/models/question.py new file mode 100644 index 0000000..2324a47 --- /dev/null +++ b/backend/models/question.py @@ -0,0 +1,54 @@ +import typing as t + +from pydantic import BaseModel, Field, validator + +from backend.constants import QUESTION_TYPES, REQUIRED_QUESTION_TYPE_DATA + + +class Question(BaseModel): + """Schema model for form question.""" + + id: str = Field(alias="_id") + name: str + type: str + data: t.Dict[str, t.Any] + + @validator("type", pre=True) + def validate_question_type(self, value: str) -> t.Optional[str]: + """Checks if question type in currently allowed types list.""" + value = value.lower() + if value not in QUESTION_TYPES: + raise ValueError( + f"{value} is not valid question type. " + f"Allowed question types: {QUESTION_TYPES}." + ) + + return value + + @validator("data") + def validate_question_data( + self, + value: t.Dict[str, t.Any] + ) -> t.Optional[t.Dict[str, t.Any]]: + """Check does required data exists for question type and remove other data.""" + # When question type don't need data, don't add anything to keep DB clean. + if self.type not in REQUIRED_QUESTION_TYPE_DATA: + return {} + + # Required keys (and values) will be stored to here + # to remove all unnecessary stuff + result = {} + + for key, data_type in REQUIRED_QUESTION_TYPE_DATA[self.type].items(): + if key not in value: + raise ValueError(f"Required question data key '{key}' not provided.") + + if not isinstance(value[key], data_type): + raise ValueError( + f"Question data key '{key}' expects {data_type.__name__}, " + f"got {type(value[key]).__name__} instead." + ) + + result[key] = value[key] + + return result |