diff options
Diffstat (limited to 'pydis_site/apps/api/models/bot')
-rw-r--r-- | pydis_site/apps/api/models/bot/__init__.py | 1 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/deleted_message.py | 4 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/documentation_link.py | 5 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/message.py | 10 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/message_deletion_context.py | 11 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/nomination.py | 12 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/off_topic_channel_name.py | 5 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/offensive_message.py | 9 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/role.py | 8 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/tag.py | 198 | ||||
-rw-r--r-- | pydis_site/apps/api/models/bot/user.py | 17 |
11 files changed, 66 insertions, 214 deletions
diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index efd98184..1673b434 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -11,5 +11,4 @@ from .off_topic_channel_name import OffTopicChannelName from .offensive_message import OffensiveMessage from .reminder import Reminder from .role import Role -from .tag import Tag from .user import User diff --git a/pydis_site/apps/api/models/bot/deleted_message.py b/pydis_site/apps/api/models/bot/deleted_message.py index 1eb4516e..50b70d8c 100644 --- a/pydis_site/apps/api/models/bot/deleted_message.py +++ b/pydis_site/apps/api/models/bot/deleted_message.py @@ -14,6 +14,6 @@ class DeletedMessage(Message): ) class Meta: - """Sets the default ordering for list views to oldest first.""" + """Sets the default ordering for list views to newest first.""" - ordering = ["id"] + ordering = ("-id",) diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py index 5a46460b..2a0ce751 100644 --- a/pydis_site/apps/api/models/bot/documentation_link.py +++ b/pydis_site/apps/api/models/bot/documentation_link.py @@ -24,3 +24,8 @@ class DocumentationLink(ModelReprMixin, models.Model): def __str__(self): """Returns the package and URL for the current documentation link, for display purposes.""" return f"{self.package} - {self.base_url}" + + class Meta: + """Defines the meta options for the documentation link model.""" + + ordering = ['package'] diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py index 78dcbf1d..ff06de21 100644 --- a/pydis_site/apps/api/models/bot/message.py +++ b/pydis_site/apps/api/models/bot/message.py @@ -5,9 +5,9 @@ from django.core.validators import MinValueValidator from django.db import models from django.utils import timezone -from pydis_site.apps.api.models.bot.tag import validate_tag_embed from pydis_site.apps.api.models.bot.user import User from pydis_site.apps.api.models.mixins import ModelReprMixin +from pydis_site.apps.api.models.utils import validate_embed class Message(ModelReprMixin, models.Model): @@ -21,7 +21,8 @@ class Message(ModelReprMixin, models.Model): limit_value=0, message="Message IDs cannot be negative." ), - ) + ), + verbose_name="ID" ) author = models.ForeignKey( User, @@ -38,7 +39,8 @@ class Message(ModelReprMixin, models.Model): limit_value=0, message="Channel IDs cannot be negative." ), - ) + ), + verbose_name="Channel ID" ) content = models.CharField( max_length=2_000, @@ -47,7 +49,7 @@ class Message(ModelReprMixin, models.Model): ) embeds = pgfields.ArrayField( pgfields.JSONField( - validators=(validate_tag_embed,) + validators=(validate_embed,) ), blank=True, help_text="Embeds attached to this message." diff --git a/pydis_site/apps/api/models/bot/message_deletion_context.py b/pydis_site/apps/api/models/bot/message_deletion_context.py index 04ae8d34..1410250a 100644 --- a/pydis_site/apps/api/models/bot/message_deletion_context.py +++ b/pydis_site/apps/api/models/bot/message_deletion_context.py @@ -1,4 +1,5 @@ from django.db import models +from django_hosts.resolvers import reverse from pydis_site.apps.api.models.bot.user import User from pydis_site.apps.api.models.mixins import ModelReprMixin @@ -28,3 +29,13 @@ class MessageDeletionContext(ModelReprMixin, models.Model): # the deletion context does not take place in the future. help_text="When this deletion took place." ) + + @property + def log_url(self) -> str: + """Create the url for the deleted message logs.""" + return reverse('logs', host="staff", args=(self.id,)) + + class Meta: + """Set the ordering for list views to newest first.""" + + ordering = ("-creation",) diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index 21e34e87..11b9e36e 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -18,7 +18,9 @@ class Nomination(ModelReprMixin, models.Model): related_name='nomination_set' ) reason = models.TextField( - help_text="Why this user was nominated." + help_text="Why this user was nominated.", + null=True, + blank=True ) user = models.ForeignKey( User, @@ -32,7 +34,8 @@ class Nomination(ModelReprMixin, models.Model): ) end_reason = models.TextField( help_text="Why the nomination was ended.", - default="" + default="", + blank=True ) ended_at = models.DateTimeField( auto_now_add=False, @@ -44,3 +47,8 @@ class Nomination(ModelReprMixin, models.Model): """Representation that makes the target and state of the nomination immediately evident.""" status = "active" if self.active else "ended" return f"Nomination of {self.user} ({status})" + + class Meta: + """Set the ordering of nominations to most recent first.""" + + ordering = ("-inserted_at",) diff --git a/pydis_site/apps/api/models/bot/off_topic_channel_name.py b/pydis_site/apps/api/models/bot/off_topic_channel_name.py index 20e77b9f..403c7465 100644 --- a/pydis_site/apps/api/models/bot/off_topic_channel_name.py +++ b/pydis_site/apps/api/models/bot/off_topic_channel_name.py @@ -16,6 +16,11 @@ class OffTopicChannelName(ModelReprMixin, models.Model): help_text="The actual channel name that will be used on our Discord server." ) + used = models.BooleanField( + default=False, + help_text="Whether or not this name has already been used during this rotation", + ) + def __str__(self): """Returns the current off-topic name, for display purposes.""" return self.name diff --git a/pydis_site/apps/api/models/bot/offensive_message.py b/pydis_site/apps/api/models/bot/offensive_message.py index 6c0e5ffb..74dab59b 100644 --- a/pydis_site/apps/api/models/bot/offensive_message.py +++ b/pydis_site/apps/api/models/bot/offensive_message.py @@ -24,7 +24,8 @@ class OffensiveMessage(ModelReprMixin, models.Model): limit_value=0, message="Message IDs cannot be negative." ), - ) + ), + verbose_name="Message ID" ) channel_id = models.BigIntegerField( help_text=( @@ -36,11 +37,13 @@ class OffensiveMessage(ModelReprMixin, models.Model): limit_value=0, message="Channel IDs cannot be negative." ), - ) + ), + verbose_name="Channel ID" ) delete_date = models.DateTimeField( help_text="The date on which the message will be auto-deleted.", - validators=(future_date_validator,) + validators=(future_date_validator,), + verbose_name="To Be Deleted" ) def __str__(self): diff --git a/pydis_site/apps/api/models/bot/role.py b/pydis_site/apps/api/models/bot/role.py index 721e4815..cfadfec4 100644 --- a/pydis_site/apps/api/models/bot/role.py +++ b/pydis_site/apps/api/models/bot/role.py @@ -22,7 +22,8 @@ class Role(ModelReprMixin, models.Model): message="Role IDs cannot be negative." ), ), - help_text="The role ID, taken from Discord." + help_text="The role ID, taken from Discord.", + verbose_name="ID" ) name = models.CharField( max_length=100, @@ -65,3 +66,8 @@ class Role(ModelReprMixin, models.Model): def __le__(self, other: Role) -> bool: """Compares the roles based on their position in the role hierarchy of the guild.""" return self.position <= other.position + + class Meta: + """Set role ordering from highest to lowest position.""" + + ordering = ("-position",) diff --git a/pydis_site/apps/api/models/bot/tag.py b/pydis_site/apps/api/models/bot/tag.py deleted file mode 100644 index 5e53582f..00000000 --- a/pydis_site/apps/api/models/bot/tag.py +++ /dev/null @@ -1,198 +0,0 @@ -from collections.abc import Mapping -from typing import Any, Dict - -from django.contrib.postgres import fields as pgfields -from django.core.exceptions import ValidationError -from django.core.validators import MaxLengthValidator, MinLengthValidator -from django.db import models - -from pydis_site.apps.api.models.mixins import ModelReprMixin - - -def is_bool_validator(value: Any) -> None: - """Validates if a given value is of type bool.""" - if not isinstance(value, bool): - raise ValidationError(f"This field must be of type bool, not {type(value)}.") - - -def validate_tag_embed_fields(fields: dict) -> None: - """Raises a ValidationError if any of the given embed fields is invalid.""" - field_validators = { - 'name': (MaxLengthValidator(limit_value=256),), - 'value': (MaxLengthValidator(limit_value=1024),), - 'inline': (is_bool_validator,), - } - - required_fields = ('name', 'value') - - for field in fields: - if not isinstance(field, Mapping): - raise ValidationError("Embed fields must be a mapping.") - - if not all(required_field in field for required_field in required_fields): - raise ValidationError( - f"Embed fields must contain the following fields: {', '.join(required_fields)}." - ) - - for field_name, value in field.items(): - if field_name not in field_validators: - raise ValidationError(f"Unknown embed field field: {field_name!r}.") - - for validator in field_validators[field_name]: - validator(value) - - -def validate_tag_embed_footer(footer: Dict[str, str]) -> None: - """Raises a ValidationError if the given footer is invalid.""" - field_validators = { - 'text': ( - MinLengthValidator( - limit_value=1, - message="Footer text must not be empty." - ), - MaxLengthValidator(limit_value=2048) - ), - 'icon_url': (), - 'proxy_icon_url': () - } - - if not isinstance(footer, Mapping): - raise ValidationError("Embed footer must be a mapping.") - - for field_name, value in footer.items(): - if field_name not in field_validators: - raise ValidationError(f"Unknown embed footer field: {field_name!r}.") - - for validator in field_validators[field_name]: - validator(value) - - -def validate_tag_embed_author(author: Any) -> None: - """Raises a ValidationError if the given author is invalid.""" - field_validators = { - 'name': ( - MinLengthValidator( - limit_value=1, - message="Embed author name must not be empty." - ), - MaxLengthValidator(limit_value=256) - ), - 'url': (), - 'icon_url': (), - 'proxy_icon_url': () - } - - if not isinstance(author, Mapping): - raise ValidationError("Embed author must be a mapping.") - - for field_name, value in author.items(): - if field_name not in field_validators: - raise ValidationError(f"Unknown embed author field: {field_name!r}.") - - for validator in field_validators[field_name]: - validator(value) - - -def validate_tag_embed(embed: Any) -> None: - """ - Validate a JSON document containing an embed as possible to send on Discord. - - This attempts to rebuild the validation used by Discord - as well as possible by checking for various embed limits so we can - ensure that any embed we store here will also be accepted as a - valid embed by the Discord API. - - Using this directly is possible, although not intended - you usually - stick this onto the `validators` keyword argument of model fields. - - Example: - - >>> from django.contrib.postgres import fields as pgfields - >>> from django.db import models - >>> from pydis_site.apps.api.models.bot.tag import validate_tag_embed - >>> class MyMessage(models.Model): - ... embed = pgfields.JSONField( - ... validators=( - ... validate_tag_embed, - ... ) - ... ) - ... # ... - ... - - Args: - embed (Any): - A dictionary describing the contents of this embed. - See the official documentation for a full reference - of accepted keys by this dictionary: - https://discordapp.com/developers/docs/resources/channel#embed-object - - Raises: - ValidationError: - In case the given embed is deemed invalid, a `ValidationError` - is raised which in turn will allow Django to display errors - as appropriate. - """ - all_keys = { - 'title', 'type', 'description', 'url', 'timestamp', - 'color', 'footer', 'image', 'thumbnail', 'video', - 'provider', 'author', 'fields' - } - one_required_of = {'description', 'fields', 'image', 'title', 'video'} - field_validators = { - 'title': ( - MinLengthValidator( - limit_value=1, - message="Embed title must not be empty." - ), - MaxLengthValidator(limit_value=256) - ), - 'description': (MaxLengthValidator(limit_value=2048),), - 'fields': ( - MaxLengthValidator(limit_value=25), - validate_tag_embed_fields - ), - 'footer': (validate_tag_embed_footer,), - 'author': (validate_tag_embed_author,) - } - - if not embed: - raise ValidationError("Tag embed must not be empty.") - - elif not isinstance(embed, Mapping): - raise ValidationError("Tag embed must be a mapping.") - - elif not any(field in embed for field in one_required_of): - raise ValidationError(f"Tag embed must contain one of the fields {one_required_of}.") - - for required_key in one_required_of: - if required_key in embed and not embed[required_key]: - raise ValidationError(f"Key {required_key!r} must not be empty.") - - for field_name, value in embed.items(): - if field_name not in all_keys: - raise ValidationError(f"Unknown field name: {field_name!r}") - - if field_name in field_validators: - for validator in field_validators[field_name]: - validator(value) - - -class Tag(ModelReprMixin, models.Model): - """A tag providing (hopefully) useful information.""" - - title = models.CharField( - max_length=100, - help_text=( - "The title of this tag, shown in searches and providing " - "a quick overview over what this embed contains." - ), - primary_key=True - ) - embed = pgfields.JSONField( - help_text="The actual embed shown by this tag.", - validators=(validate_tag_embed,) - ) - - def __str__(self): - """Returns the title of this tag, for display purposes.""" - return self.title diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index cd2d58b9..afc5ba1e 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -26,11 +26,12 @@ class User(ModelReprMixin, models.Model): message="User IDs cannot be negative." ), ), + verbose_name="ID", help_text="The ID of this user, taken from Discord." ) name = models.CharField( max_length=32, - help_text="The username, taken from Discord." + help_text="The username, taken from Discord.", ) discriminator = models.PositiveSmallIntegerField( validators=( @@ -57,12 +58,13 @@ class User(ModelReprMixin, models.Model): ) in_guild = models.BooleanField( default=True, - help_text="Whether this user is in our server." + help_text="Whether this user is in our server.", + verbose_name="In Guild" ) def __str__(self): """Returns the name and discriminator for the current user, for display purposes.""" - return f"{self.name}#{self.discriminator:0>4}" + return f"{self.name}#{self.discriminator:04d}" @property def top_role(self) -> Role: @@ -75,3 +77,12 @@ class User(ModelReprMixin, models.Model): if not roles: return Role.objects.get(name="Developers") return max(roles) + + @property + def username(self) -> str: + """ + Returns the display version with name and discriminator as a standard attribute. + + For usability in read-only fields such as Django Admin. + """ + return str(self) |