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  |