Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 28 additions & 22 deletions db/_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@
from pathlib import Path
from typing import TYPE_CHECKING

import django_stubs_ext

if TYPE_CHECKING:
from collections.abc import Sequence
from collections.abc import Mapping, Sequence
from typing import Final

__all__: "Sequence[str]" = ()

# Build paths inside the project like this: BASE_DIR / "subdir".
BASE_DIR = Path(__file__).resolve().parent

# Monkeypatching Django, so stubs will work for all generics,
# SOURCE: https://github.com/typeddjango/django-stubs
django_stubs_ext.monkeypatch()


# NOTE: Build paths inside the project like this: BASE_DIR / "subdir".
BASE_DIR: "Final[Path]" = Path(__file__).resolve().parent

# NOTE: settings.py is called when setting up the mypy_django_plugin & when running Pytest. When mypy/Pytest runs no config settings variables are set, so they should not be accessed
IMPORTED_BY_MYPY_OR_PYTEST: "Final[bool]" = any(
Expand All @@ -32,31 +40,29 @@
SECRET_KEY = settings.DISCORD_BOT_TOKEN


# Application definition

INSTALLED_APPS = ["django.contrib.contenttypes", "db.core.app_config.CoreConfig"]

MIDDLEWARE = ["django.middleware.common.CommonMiddleware"]


# Database
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
# Application Definition

DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "core.db"}}
INSTALLED_APPS: "Final[Sequence[str]]" = [
"django.contrib.contenttypes",
"db.core.app_config.CoreConfig",
]

MIDDLEWARE: "Final[Sequence[str]]" = ["django.middleware.common.CommonMiddleware"]

# Internationalization
# https://docs.djangoproject.com/en/stable/topics/i18n/

LANGUAGE_CODE = "en-gb"
# Database Settings

TIME_ZONE = "Europe/London"
DATABASES: "Final[Mapping[str, object]]" = { # SOURCE: https://docs.djangoproject.com/en/stable/ref/settings#databases
"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "core.db"}
}

USE_I18N = True
DEFAULT_AUTO_FIELD: "Final[str]" = "django.db.models.BigAutoField" # SOURCE: https://docs.djangoproject.com/en/stable/ref/settings#default-auto-field

USE_TZ = True

# Default primary key field type
# https://docs.djangoproject.com/en/stable/ref/settings/#default-auto-field
# Internationalisation, Language & Time Settings
# SOURCE: https://docs.djangoproject.com/en/stable/topics/i18n

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
LANGUAGE_CODE: "Final[str]" = "en-gb"
TIME_ZONE: "Final[str]" = "Europe/London"
USE_I18N: "Final[bool]" = True
USE_TZ: "Final[bool]" = True
126 changes: 71 additions & 55 deletions db/core/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@
from django.core.validators import MinValueValidator, RegexValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_stubs_ext.db.models import TypedModelMeta

from .utils import AsyncBaseModel, DiscordMember

if TYPE_CHECKING:
from collections.abc import Sequence
from typing import Final
from collections.abc import Set as AbstractSet
from typing import ClassVar, Final

from django.db.models.constraints import BaseConstraint
from django_stubs_ext import StrOrPromise


__all__: "Sequence[str]" = (
"AssignedCommitteeAction",
Expand Down Expand Up @@ -47,23 +53,23 @@ class Status(models.TextChoices):
DiscordMember,
on_delete=models.CASCADE,
related_name="assigned_committee_actions",
verbose_name="Discord Member",
verbose_name=_("Discord Member"),
blank=False,
null=False,
unique=False,
)
description = models.TextField("Description", max_length=200, null=False, blank=False)
description = models.TextField(_("Description"), max_length=200, null=False, blank=False)
status = models.CharField(
max_length=3, choices=Status, default=Status.NOT_STARTED, null=False, blank=False
)

class Meta: # noqa: D106
verbose_name = "Assigned Committee Action"
constraints = [ # noqa: RUF012
class Meta(TypedModelMeta): # noqa: D106
verbose_name: "ClassVar[StrOrPromise]" = _("Assigned Committee Action")
constraints: "ClassVar[list[BaseConstraint] | tuple[BaseConstraint, ...]]" = (
models.UniqueConstraint(
fields=["discord_member", "description"], name="unique_user_action"
)
]
),
)

@override
def __repr__(self) -> str:
Expand All @@ -88,15 +94,19 @@ class IntroductionReminderOptOutMember(AsyncBaseModel):
DiscordMember,
on_delete=models.CASCADE,
related_name="opted_out_of_introduction_reminders",
verbose_name="Discord Member",
verbose_name=_("Discord Member"),
blank=False,
null=False,
primary_key=True,
)

class Meta: # noqa: D106
verbose_name = "Discord Member that has Opted-Out of Introduction Reminders"
verbose_name_plural = "Discord Members that have Opted-Out of Introduction Reminders"
class Meta(TypedModelMeta): # noqa: D106
verbose_name: "ClassVar[StrOrPromise]" = _(
"Discord Member that has Opted-Out of Introduction Reminders"
)
verbose_name_plural: "ClassVar[StrOrPromise]" = _(
"Discord Members that have Opted-Out of Introduction Reminders"
)


class SentOneOffIntroductionReminderMember(AsyncBaseModel):
Expand All @@ -114,17 +124,17 @@ class SentOneOffIntroductionReminderMember(AsyncBaseModel):
DiscordMember,
on_delete=models.CASCADE,
related_name="sent_one_off_introduction_reminder",
verbose_name="Discord Member",
verbose_name=_("Discord Member"),
blank=False,
null=False,
primary_key=True,
)

class Meta: # noqa: D106
verbose_name = (
class Meta(TypedModelMeta): # noqa: D106
verbose_name: "ClassVar[StrOrPromise]" = _(
"Discord Member that has had a one-off Introduction reminder sent to their DMs"
)
verbose_name_plural = (
verbose_name_plural: "ClassVar[StrOrPromise]" = _(
"Discord Members that have had a one-off Introduction reminder sent to their DMs"
)

Expand All @@ -147,15 +157,17 @@ class SentGetRolesReminderMember(AsyncBaseModel):
DiscordMember,
on_delete=models.CASCADE,
related_name="sent_get_roles_reminder",
verbose_name="Discord Member",
verbose_name=_("Discord Member"),
blank=False,
null=False,
primary_key=True,
)

class Meta: # noqa: D106
verbose_name = 'Discord Member that has had a "Get Roles" reminder sent to their DMs'
verbose_name_plural = (
class Meta(TypedModelMeta): # noqa: D106
verbose_name: "ClassVar[StrOrPromise]" = _(
'Discord Member that has had a "Get Roles" reminder sent to their DMs'
)
verbose_name_plural: "ClassVar[StrOrPromise]" = _(
'Discord Members that have had a "Get Roles" reminder sent to their DMs'
)

Expand All @@ -175,22 +187,26 @@ class GroupMadeMember(AsyncBaseModel):
INSTANCES_NAME_PLURAL: str = "Group Made Members"

hashed_group_member_id = models.CharField(
"Hashed Group Member ID",
_("Hashed Group Member ID"),
unique=True,
null=False,
blank=False,
max_length=64,
validators=[
RegexValidator(
r"\A[A-Fa-f\d]{64}\Z",
"hashed_group_member_id must be a valid sha256 hex-digest.",
_("hashed_group_member_id must be a valid sha256 hex-digest."),
)
],
)

class Meta: # noqa: D106
verbose_name = "Hashed Group ID of User that has been made Member"
verbose_name_plural = "Hashed Group IDs of Users that have been made Member"
class Meta(TypedModelMeta): # noqa: D106
verbose_name: "ClassVar[StrOrPromise]" = _(
"Hashed Group ID of User that has been made Member"
)
verbose_name_plural: "ClassVar[StrOrPromise]" = _(
"Hashed Group IDs of Users that have been made Member"
)

@override
def __setattr__(self, name: str, value: object) -> None:
Expand Down Expand Up @@ -236,8 +252,8 @@ def hash_group_member_id(

@classmethod
@override
def get_proxy_field_names(cls) -> set[str]:
return super().get_proxy_field_names() | {"group_member_id"}
def _get_proxy_field_names(cls) -> "AbstractSet[str]":
return {*super()._get_proxy_field_names(), "group_member_id"}


class DiscordReminder(AsyncBaseModel):
Expand All @@ -249,51 +265,52 @@ class DiscordReminder(AsyncBaseModel):
DiscordMember,
on_delete=models.CASCADE,
related_name="reminders",
verbose_name="Discord Member",
verbose_name=_("Discord Member"),
blank=False,
null=False,
unique=False,
)
message = models.TextField(
"Message to remind User", max_length=1500, null=False, blank=True
_("Message to remind User"), max_length=1500, null=False, blank=True
)
_channel_id = models.CharField(
"Discord Channel ID of the channel that the reminder needs to be sent in",
_("Discord Channel ID of the channel that the reminder needs to be sent in"),
unique=False,
null=False,
blank=False,
max_length=30,
validators=[
RegexValidator(
r"\A\d{17,20}\Z",
"channel_id must be a valid Discord channel ID (see https://docs.pycord.dev/en/stable/api/abcs.html#discord.abc.Snowflake.id)",
_(
"channel_id must be a valid Discord channel ID (see https://docs.pycord.dev/en/stable/api/abcs.html#discord.abc.Snowflake.id)"
),
)
],
)
_channel_type = models.IntegerField(
"Discord Channel Type of the channel that the reminder needs to be sent in",
_("Discord Channel Type of the channel that the reminder needs to be sent in"),
choices=[
(channel_type.value, channel_type.name) for channel_type in discord.ChannelType
],
null=True,
blank=True,
)
send_datetime = models.DateTimeField(
"Date & time to send reminder", unique=False, null=False, blank=False
_("Date & time to send reminder"), unique=False, null=False, blank=False
)

@property
def channel_id(self) -> int:
"""The ID of the channel that the reminder needs to be sent in."""
def channel_id(self) -> int: # noqa: D102
return int(self._channel_id)

@channel_id.setter
def channel_id(self, channel_id: str | int) -> None:
self._channel_id = str(channel_id)

@property
def channel_type(self) -> discord.ChannelType:
"""The type of channel that the reminder needs to be sent in."""
def channel_type(self) -> discord.ChannelType: # noqa: D102
# NOTE: This finds the type of channel that the reminder needs to be sent in."""
return discord.ChannelType(self._channel_type)

@channel_type.setter
Expand All @@ -309,15 +326,15 @@ def channel_type(self, channel_type: discord.ChannelType | int) -> None:

self._channel_type = channel_type

class Meta: # noqa: D106
verbose_name = "A Reminder for a Discord Member"
verbose_name_plural = "Reminders for Discord Members"
constraints = [ # noqa: RUF012
class Meta(TypedModelMeta): # noqa: D106
verbose_name: "ClassVar[StrOrPromise]" = _("A Reminder for a Discord Member")
verbose_name_plural: "ClassVar[StrOrPromise]" = _("Reminders for Discord Members")
constraints: "ClassVar[list[BaseConstraint] | tuple[BaseConstraint, ...]]" = (
models.UniqueConstraint(
fields=["discord_member", "message", "_channel_id"],
name="unique_user_channel_message",
)
]
),
)

@override
def __str__(self) -> str:
Expand Down Expand Up @@ -358,8 +375,8 @@ def get_formatted_message(self, user_mention: str | None) -> str:

@classmethod
@override
def get_proxy_field_names(cls) -> set[str]:
return super().get_proxy_field_names() | {"channel_id", "channel_type"}
def _get_proxy_field_names(cls) -> "AbstractSet[str]":
return {*super()._get_proxy_field_names(), "channel_id", "channel_type"}


class LeftDiscordMember(AsyncBaseModel):
Expand All @@ -375,20 +392,19 @@ class LeftDiscordMember(AsyncBaseModel):
_roles = models.JSONField("List of roles a Discord Member had")

@property
def roles(self) -> set[str]:
"""Retrieve the set of roles the member had when they left your Discord guild."""
def roles(self) -> set[str]: # noqa: D102
return set(self._roles)

@roles.setter
def roles(self, roles: set[str]) -> None:
self._roles = list(roles)

class Meta: # noqa: D106
verbose_name = (
class Meta(TypedModelMeta): # noqa: D106
verbose_name: "ClassVar[StrOrPromise]" = _(
"A List of Roles that a Discord Member had "
"when they left your group's Discord guild"
)
verbose_name_plural = (
verbose_name_plural: "ClassVar[StrOrPromise]" = _(
"Lists of Roles that Discord Members had when they left your group's Discord guild"
)

Expand All @@ -412,8 +428,8 @@ def clean(self) -> None:

@classmethod
@override
def get_proxy_field_names(cls) -> set[str]:
return super().get_proxy_field_names() | {"roles"}
def _get_proxy_field_names(cls) -> "AbstractSet[str]":
return {*super()._get_proxy_field_names(), "roles"}


class DiscordMemberStrikes(AsyncBaseModel):
Expand Down Expand Up @@ -449,12 +465,12 @@ class DiscordMemberStrikes(AsyncBaseModel):
default=0,
)

class Meta: # noqa: D106
verbose_name = (
class Meta(TypedModelMeta): # noqa: D106
verbose_name: "ClassVar[StrOrPromise]" = _(
"Discord Member that has been previously given one or more strikes "
"because they broke one or more of your group's Discord guild rules"
)
verbose_name_plural = (
verbose_name_plural: "ClassVar[StrOrPromise]" = _(
"Discord Members that have been previously given one or more strikes "
"because they broke one or more of your group's Discord guild rules"
)
Expand Down
Loading