diff options
author | 2022-03-20 17:25:06 -0400 | |
---|---|---|
committer | 2022-03-20 17:25:06 -0400 | |
commit | 25fce5e0161c2d84d4a6b710aa5c83a863766f98 (patch) | |
tree | e3c15dad453f8d518bbf5335a14eddedf2c2d054 /backend/models | |
parent | Merge pull request #151 from python-discord/dependabot/pip/sentry-sdk-1.5.7 (diff) | |
parent | Merge branch 'main' into roles (diff) |
Merge pull request #135 from python-discord/roles
Overhaul Access System
Diffstat (limited to 'backend/models')
-rw-r--r-- | backend/models/__init__.py | 5 | ||||
-rw-r--r-- | backend/models/discord_role.py | 40 | ||||
-rw-r--r-- | backend/models/discord_user.py | 34 | ||||
-rw-r--r-- | backend/models/form.py | 13 |
4 files changed, 87 insertions, 5 deletions
diff --git a/backend/models/__init__.py b/backend/models/__init__.py index 8ad7f7f..a9f76e0 100644 --- a/backend/models/__init__.py +++ b/backend/models/__init__.py @@ -1,12 +1,15 @@ from .antispam import AntiSpam -from .discord_user import DiscordUser +from .discord_role import DiscordRole +from .discord_user import DiscordMember, DiscordUser from .form import Form, FormList from .form_response import FormResponse, ResponseList from .question import CodeQuestion, Question __all__ = [ "AntiSpam", + "DiscordRole", "DiscordUser", + "DiscordMember", "Form", "FormResponse", "CodeQuestion", diff --git a/backend/models/discord_role.py b/backend/models/discord_role.py new file mode 100644 index 0000000..c05c9de --- /dev/null +++ b/backend/models/discord_role.py @@ -0,0 +1,40 @@ +import typing + +from pydantic import BaseModel + + +class RoleTags(BaseModel): + """Meta information about a discord role.""" + + bot_id: typing.Optional[str] + integration_id: typing.Optional[str] + premium_subscriber: bool + + def __init__(self, **data: typing.Any) -> None: + """ + Handle the terrible discord API. + + Discord only returns the premium_subscriber field if it's true, + meaning the typical validation process wouldn't work. + + We manually parse the raw data to determine if the field exists, and give it a useful + bool value. + """ + data["premium_subscriber"] = "premium_subscriber" in data.keys() + super().__init__(**data) + + +class DiscordRole(BaseModel): + """Schema model of Discord guild roles.""" + + id: str + name: str + color: int + hoist: bool + icon: typing.Optional[str] + unicode_emoji: typing.Optional[str] + position: int + permissions: str + managed: bool + mentionable: bool + tags: typing.Optional[RoleTags] diff --git a/backend/models/discord_user.py b/backend/models/discord_user.py index 9f246ba..0eca15b 100644 --- a/backend/models/discord_user.py +++ b/backend/models/discord_user.py @@ -1,10 +1,11 @@ +import datetime import typing as t from pydantic import BaseModel -class DiscordUser(BaseModel): - """Schema model of Discord user for form response.""" +class _User(BaseModel): + """Base for discord users and members.""" # Discord default fields. username: str @@ -20,5 +21,34 @@ class DiscordUser(BaseModel): premium_type: t.Optional[int] public_flags: t.Optional[int] + +class DiscordUser(_User): + """Schema model of Discord user for form response.""" + # Custom fields admin: bool + + +class DiscordMember(BaseModel): + """A discord guild member.""" + + user: _User + nick: t.Optional[str] + avatar: t.Optional[str] + roles: list[str] + joined_at: datetime.datetime + premium_since: t.Optional[datetime.datetime] + deaf: bool + mute: bool + pending: t.Optional[bool] + permissions: t.Optional[str] + communication_disabled_until: t.Optional[datetime.datetime] + + def dict(self, *args, **kwargs) -> dict[str, t.Any]: + """Convert the model to a python dict, and encode timestamps in a serializable format.""" + data = super().dict(*args, **kwargs) + for field, value in data.items(): + if isinstance(value, datetime.datetime): + data[field] = value.isoformat() + + return data diff --git a/backend/models/form.py b/backend/models/form.py index f19ed85..f888d6e 100644 --- a/backend/models/form.py +++ b/backend/models/form.py @@ -1,10 +1,10 @@ import typing as t import httpx -from pydantic import constr, BaseModel, Field, root_validator, validator +from pydantic import BaseModel, Field, constr, root_validator, validator from pydantic.error_wrappers import ErrorWrapper, ValidationError -from backend.constants import FormFeatures, WebHook +from backend.constants import DISCORD_GUILD, FormFeatures, WebHook from .question import Question PUBLIC_FIELDS = [ @@ -43,6 +43,8 @@ class Form(BaseModel): submitted_text: t.Optional[str] = None webhook: _WebHook = None discord_role: t.Optional[str] + response_readers: t.Optional[list[str]] + editors: t.Optional[list[str]] class Config: allow_population_by_field_name = True @@ -67,6 +69,13 @@ class Form(BaseModel): return value + @validator("response_readers", "editors") + def validate_role_scoping(cls, value: t.Optional[list[str]]) -> t.Optional[list[str]]: + """Ensure special role based permissions aren't granted to the @everyone role.""" + if value and str(DISCORD_GUILD) in value: + raise ValueError("You can not add the everyone role as an access scope.") + return value + @root_validator def validate_role(cls, values: dict[str, t.Any]) -> t.Optional[dict[str, t.Any]]: """Validates does Discord role provided when flag provided.""" |