From d52a20962bbda8f94646ff12c38eb71e2a5241ab Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:38:25 +0300 Subject: (Tag Cleanup): Removed Tags viewset. --- pydis_site/apps/api/viewsets/bot/__init__.py | 1 - pydis_site/apps/api/viewsets/bot/tag.py | 105 --------------------------- 2 files changed, 106 deletions(-) delete mode 100644 pydis_site/apps/api/viewsets/bot/tag.py (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py index e64e3988..84b87eab 100644 --- a/pydis_site/apps/api/viewsets/bot/__init__.py +++ b/pydis_site/apps/api/viewsets/bot/__init__.py @@ -9,5 +9,4 @@ from .off_topic_channel_name import OffTopicChannelNameViewSet from .offensive_message import OffensiveMessageViewSet from .reminder import ReminderViewSet from .role import RoleViewSet -from .tag import TagViewSet from .user import UserViewSet diff --git a/pydis_site/apps/api/viewsets/bot/tag.py b/pydis_site/apps/api/viewsets/bot/tag.py deleted file mode 100644 index 7e9ba117..00000000 --- a/pydis_site/apps/api/viewsets/bot/tag.py +++ /dev/null @@ -1,105 +0,0 @@ -from rest_framework.viewsets import ModelViewSet - -from pydis_site.apps.api.models.bot.tag import Tag -from pydis_site.apps.api.serializers import TagSerializer - - -class TagViewSet(ModelViewSet): - """ - View providing CRUD operations on tags shown by our bot. - - ## Routes - ### GET /bot/tags - Returns all tags in the database. - - #### Response format - >>> [ - ... { - ... 'title': "resources", - ... 'embed': { - ... 'content': "Did you really think I'd put something useful here?" - ... } - ... } - ... ] - - #### Status codes - - 200: returned on success - - ### GET /bot/tags/ - Gets a single tag by its title. - - #### Response format - >>> { - ... 'title': "My awesome tag", - ... 'embed': { - ... 'content': "totally not filler words" - ... } - ... } - - #### Status codes - - 200: returned on success - - 404: if a tag with the given `title` could not be found - - ### POST /bot/tags - Adds a single tag to the database. - - #### Request body - >>> { - ... 'title': str, - ... 'embed': dict - ... } - - The embed structure is the same as the embed structure that the Discord API - expects. You can view the documentation for it here: - https://discordapp.com/developers/docs/resources/channel#embed-object - - #### Status codes - - 201: returned on success - - 400: if one of the given fields is invalid - - ### PUT /bot/tags/ - Update the tag with the given `title`. - - #### Request body - >>> { - ... 'title': str, - ... 'embed': dict - ... } - - The embed structure is the same as the embed structure that the Discord API - expects. You can view the documentation for it here: - https://discordapp.com/developers/docs/resources/channel#embed-object - - #### Status codes - - 200: returned on success - - 400: if the request body was invalid, see response body for details - - 404: if the tag with the given `title` could not be found - - ### PATCH /bot/tags/ - Update the tag with the given `title`. - - #### Request body - >>> { - ... 'title': str, - ... 'embed': dict - ... } - - The embed structure is the same as the embed structure that the Discord API - expects. You can view the documentation for it here: - https://discordapp.com/developers/docs/resources/channel#embed-object - - #### Status codes - - 200: returned on success - - 400: if the request body was invalid, see response body for details - - 404: if the tag with the given `title` could not be found - - ### DELETE /bot/tags/ - Deletes the tag with the given `title`. - - #### Status codes - - 204: returned on success - - 404: if a tag with the given `title` does not exist - """ - - serializer_class = TagSerializer - queryset = Tag.objects.all() -- cgit v1.2.3 From 86409cf41469bd2e5b8d69988a729b3b88bb0a26 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:39:37 +0300 Subject: (Tag Cleanup): Removed Tags from Administration --- pydis_site/apps/api/admin.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index 0333fefc..dd1291b8 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -14,7 +14,6 @@ from .models import ( OffTopicChannelName, OffensiveMessage, Role, - Tag, User ) @@ -64,5 +63,4 @@ admin.site.register(Nomination) admin.site.register(OffensiveMessage) admin.site.register(OffTopicChannelName) admin.site.register(Role) -admin.site.register(Tag) admin.site.register(User) -- cgit v1.2.3 From 14453c9607ed83e040c9b1ed000a31e47568fd69 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:41:16 +0300 Subject: (Tag Cleanup): Removed Tags serializer. --- pydis_site/apps/api/serializers.py | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 52e0d972..90bd6f91 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -16,7 +16,6 @@ from .models import ( OffensiveMessage, Reminder, Role, - Tag, User ) @@ -250,16 +249,6 @@ class RoleSerializer(ModelSerializer): fields = ('id', 'name', 'colour', 'permissions', 'position') -class TagSerializer(ModelSerializer): - """A class providing (de-)serialization of `Tag` instances.""" - - class Meta: - """Metadata defined for the Django REST Framework.""" - - model = Tag - fields = ('title', 'embed') - - class UserSerializer(BulkSerializerMixin, ModelSerializer): """A class providing (de-)serialization of `User` instances.""" -- cgit v1.2.3 From 05b0c725b45dd87c755a8475d08442a0603a515e Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:41:58 +0300 Subject: (Tag Cleanup): Removed Tags URL --- pydis_site/apps/api/urls.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index a4fd5b2e..4dbf93db 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -14,7 +14,6 @@ from .viewsets import ( OffensiveMessageViewSet, ReminderViewSet, RoleViewSet, - TagViewSet, UserViewSet ) @@ -61,10 +60,6 @@ bot_router.register( 'roles', RoleViewSet ) -bot_router.register( - 'tags', - TagViewSet -) bot_router.register( 'users', UserViewSet -- cgit v1.2.3 From bb55875dcdaf838c41b1e3b2c3dc4fdf368b0f09 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:43:33 +0300 Subject: (Tag Cleanup): Removed Tags Model test. --- pydis_site/apps/api/tests/test_models.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index e0e347bb..853e6621 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -14,7 +14,6 @@ from pydis_site.apps.api.models import ( OffensiveMessage, Reminder, Role, - Tag, User ) from pydis_site.apps.api.models.mixins import ModelReprMixin @@ -104,10 +103,6 @@ class StringDunderMethodTests(SimpleTestCase): ), creation=dt.utcnow() ), - Tag( - title='bob', - embed={'content': "the builder"} - ), User( id=5, name='bob', -- cgit v1.2.3 From 790c50cbc2d45134fc0b668bc21ca7e01e4af2a2 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:46:19 +0300 Subject: (Tag Cleanup): Removed Tags Model import from models __init__.py --- pydis_site/apps/api/models/bot/__init__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'pydis_site/apps/api') 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 -- cgit v1.2.3 From b16809620f444872f4d295ee55e4e8e9cdde12c8 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:49:49 +0300 Subject: (Tag Cleanup): Moved embed validators from Tag model to utils.py --- pydis_site/apps/api/models/mixins.py | 173 +++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/mixins.py b/pydis_site/apps/api/models/mixins.py index 5d75b78b..2d658cb9 100644 --- a/pydis_site/apps/api/models/mixins.py +++ b/pydis_site/apps/api/models/mixins.py @@ -1,4 +1,177 @@ +from collections.abc import Mapping from operator import itemgetter +from typing import Any, Dict + +from django.core.exceptions import ValidationError +from django.core.validators import MaxLengthValidator, MinLengthValidator + + +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) from django.db import models -- cgit v1.2.3 From 1f623b6629bdbc854323dc71d149a647355031c6 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:52:05 +0300 Subject: (Tag Cleanup): Removed `tag` from embed validator names due Tags will be removed. --- pydis_site/apps/api/models/mixins.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/mixins.py b/pydis_site/apps/api/models/mixins.py index 2d658cb9..e95888b7 100644 --- a/pydis_site/apps/api/models/mixins.py +++ b/pydis_site/apps/api/models/mixins.py @@ -12,7 +12,7 @@ def is_bool_validator(value: Any) -> None: raise ValidationError(f"This field must be of type bool, not {type(value)}.") -def validate_tag_embed_fields(fields: dict) -> None: +def validate_embed_fields(fields: dict) -> None: """Raises a ValidationError if any of the given embed fields is invalid.""" field_validators = { 'name': (MaxLengthValidator(limit_value=256),), @@ -39,7 +39,7 @@ def validate_tag_embed_fields(fields: dict) -> None: validator(value) -def validate_tag_embed_footer(footer: Dict[str, str]) -> None: +def validate_embed_footer(footer: Dict[str, str]) -> None: """Raises a ValidationError if the given footer is invalid.""" field_validators = { 'text': ( @@ -64,7 +64,7 @@ def validate_tag_embed_footer(footer: Dict[str, str]) -> None: validator(value) -def validate_tag_embed_author(author: Any) -> None: +def validate_embed_author(author: Any) -> None: """Raises a ValidationError if the given author is invalid.""" field_validators = { 'name': ( @@ -90,7 +90,7 @@ def validate_tag_embed_author(author: Any) -> None: validator(value) -def validate_tag_embed(embed: Any) -> None: +def validate_embed(embed: Any) -> None: """ Validate a JSON document containing an embed as possible to send on Discord. @@ -106,11 +106,11 @@ def validate_tag_embed(embed: Any) -> None: >>> 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 + >>> from pydis_site.apps.api.models.utils import validate_embed >>> class MyMessage(models.Model): ... embed = pgfields.JSONField( ... validators=( - ... validate_tag_embed, + ... validate_embed, ... ) ... ) ... # ... @@ -146,10 +146,10 @@ def validate_tag_embed(embed: Any) -> None: 'description': (MaxLengthValidator(limit_value=2048),), 'fields': ( MaxLengthValidator(limit_value=25), - validate_tag_embed_fields + validate_embed_fields ), - 'footer': (validate_tag_embed_footer,), - 'author': (validate_tag_embed_author,) + 'footer': (validate_embed_footer,), + 'author': (validate_embed_author,) } if not embed: -- cgit v1.2.3 From 9b6b9d9d01811bbe2d9a1964970e3b4a38180623 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:55:09 +0300 Subject: (Tag Cleanup): Replaced import from Tags model with utils.py validator import. --- pydis_site/apps/api/models/bot/message.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py index 78dcbf1d..e929ea25 100644 --- a/pydis_site/apps/api/models/bot/message.py +++ b/pydis_site/apps/api/models/bot/message.py @@ -5,9 +5,8 @@ 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 ModelReprMixin, validate_embed class Message(ModelReprMixin, models.Model): @@ -47,7 +46,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." -- cgit v1.2.3 From 1029c56eb7e1f986a540a58bc2e657db6925a93a Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 08:57:27 +0300 Subject: (Tag Cleanup): Replaced import from Tags model with utils.py validator import in validators test. --- pydis_site/apps/api/tests/test_validators.py | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/tests/test_validators.py b/pydis_site/apps/api/tests/test_validators.py index 241af08c..8bb7b917 100644 --- a/pydis_site/apps/api/tests/test_validators.py +++ b/pydis_site/apps/api/tests/test_validators.py @@ -5,7 +5,7 @@ from django.test import TestCase from ..models.bot.bot_setting import validate_bot_setting_name from ..models.bot.offensive_message import future_date_validator -from ..models.bot.tag import validate_tag_embed +from ..models.utils import validate_embed REQUIRED_KEYS = ( @@ -25,77 +25,77 @@ class BotSettingValidatorTests(TestCase): class TagEmbedValidatorTests(TestCase): def test_rejects_non_mapping(self): with self.assertRaises(ValidationError): - validate_tag_embed('non-empty non-mapping') + validate_embed('non-empty non-mapping') def test_rejects_missing_required_keys(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'unknown': "key" }) def test_rejects_one_correct_one_incorrect(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'provider': "??", 'title': "" }) def test_rejects_empty_required_key(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': '' }) def test_rejects_list_as_embed(self): with self.assertRaises(ValidationError): - validate_tag_embed([]) + validate_embed([]) def test_rejects_required_keys_and_unknown_keys(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': "the duck walked up to the lemonade stand", 'and': "he said to the man running the stand" }) def test_rejects_too_long_title(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': 'a' * 257 }) def test_rejects_too_many_fields(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'fields': [{} for _ in range(26)] }) def test_rejects_too_long_description(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'description': 'd' * 2049 }) def test_allows_valid_embed(self): - validate_tag_embed({ + validate_embed({ 'title': "My embed", 'description': "look at my embed, my embed is amazing" }) def test_allows_unvalidated_fields(self): - validate_tag_embed({ + validate_embed({ 'title': "My embed", 'provider': "what am I??" }) def test_rejects_fields_as_list_of_non_mappings(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'fields': ['abc'] }) def test_rejects_fields_with_unknown_fields(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'fields': [ { 'what': "is this field" @@ -105,7 +105,7 @@ class TagEmbedValidatorTests(TestCase): def test_rejects_fields_with_too_long_name(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'fields': [ { 'name': "a" * 257 @@ -115,7 +115,7 @@ class TagEmbedValidatorTests(TestCase): def test_rejects_one_correct_one_incorrect_field(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'fields': [ { 'name': "Totally valid", @@ -131,7 +131,7 @@ class TagEmbedValidatorTests(TestCase): def test_rejects_missing_required_field_field(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'fields': [ { 'name': "Totally valid", @@ -142,7 +142,7 @@ class TagEmbedValidatorTests(TestCase): def test_rejects_invalid_inline_field_field(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'fields': [ { 'name': "Totally valid", @@ -153,7 +153,7 @@ class TagEmbedValidatorTests(TestCase): }) def test_allows_valid_fields(self): - validate_tag_embed({ + validate_embed({ 'fields': [ { 'name': "valid", @@ -174,14 +174,14 @@ class TagEmbedValidatorTests(TestCase): def test_rejects_footer_as_non_mapping(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': "whatever", 'footer': [] }) def test_rejects_footer_with_unknown_fields(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': "whatever", 'footer': { 'duck': "quack" @@ -190,7 +190,7 @@ class TagEmbedValidatorTests(TestCase): def test_rejects_footer_with_empty_text(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': "whatever", 'footer': { 'text': "" @@ -198,7 +198,7 @@ class TagEmbedValidatorTests(TestCase): }) def test_allows_footer_with_proper_values(self): - validate_tag_embed({ + validate_embed({ 'title': "whatever", 'footer': { 'text': "django good" @@ -207,14 +207,14 @@ class TagEmbedValidatorTests(TestCase): def test_rejects_author_as_non_mapping(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': "whatever", 'author': [] }) def test_rejects_author_with_unknown_field(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': "whatever", 'author': { 'field': "that is unknown" @@ -223,7 +223,7 @@ class TagEmbedValidatorTests(TestCase): def test_rejects_author_with_empty_name(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': "whatever", 'author': { 'name': "" @@ -232,7 +232,7 @@ class TagEmbedValidatorTests(TestCase): def test_rejects_author_with_one_correct_one_incorrect(self): with self.assertRaises(ValidationError): - validate_tag_embed({ + validate_embed({ 'title': "whatever", 'author': { # Relies on "dictionary insertion order remembering" (D.I.O.R.) behaviour @@ -242,7 +242,7 @@ class TagEmbedValidatorTests(TestCase): }) def test_allows_author_with_proper_values(self): - validate_tag_embed({ + validate_embed({ 'title': "whatever", 'author': { 'name': "Bob" -- cgit v1.2.3 From 5df2d613bc72aad60fed73d2703dff55093def03 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 09:02:48 +0300 Subject: (Tag Cleanup): Removed tag import in models __init__.py --- pydis_site/apps/api/models/__init__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 1d0ab7ea..e3f928e1 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -12,7 +12,6 @@ from .bot import ( OffTopicChannelName, Reminder, Role, - Tag, User ) from .log_entry import LogEntry -- cgit v1.2.3 From 494b735f762bf1780448db4591a4be4850dcdd4a Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 09:17:43 +0300 Subject: (Tag Cleanup): Removed Tag model --- .../apps/api/migrations/0019_deletedmessage.py | 2 +- pydis_site/apps/api/models/bot/tag.py | 25 ---------------------- 2 files changed, 1 insertion(+), 26 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/migrations/0019_deletedmessage.py b/pydis_site/apps/api/migrations/0019_deletedmessage.py index 33746253..6b848d64 100644 --- a/pydis_site/apps/api/migrations/0019_deletedmessage.py +++ b/pydis_site/apps/api/migrations/0019_deletedmessage.py @@ -18,7 +18,7 @@ class Migration(migrations.Migration): ('id', models.BigIntegerField(help_text='The message ID as taken from Discord.', primary_key=True, serialize=False, validators=[django.core.validators.MinValueValidator(limit_value=0, message='Message IDs cannot be negative.')])), ('channel_id', models.BigIntegerField(help_text='The channel ID that this message was sent in, taken from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Channel IDs cannot be negative.')])), ('content', models.CharField(help_text='The content of this message, taken from Discord.', max_length=2000)), - ('embeds', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(validators=[pydis_site.apps.api.models.bot.tag.validate_tag_embed]), help_text='Embeds attached to this message.', size=None)), + ('embeds', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(validators=[pydis_site.apps.api.models.utils.validate_embed]), help_text='Embeds attached to this message.', size=None)), ('author', models.ForeignKey(help_text='The author of this message.', on_delete=django.db.models.deletion.CASCADE, to='api.User')), ('deletion_context', models.ForeignKey(help_text='The deletion context this message is part of.', on_delete=django.db.models.deletion.CASCADE, to='api.MessageDeletionContext')), ], diff --git a/pydis_site/apps/api/models/bot/tag.py b/pydis_site/apps/api/models/bot/tag.py index 5e53582f..790ad37a 100644 --- a/pydis_site/apps/api/models/bot/tag.py +++ b/pydis_site/apps/api/models/bot/tag.py @@ -1,12 +1,8 @@ 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: @@ -175,24 +171,3 @@ def validate_tag_embed(embed: Any) -> None: 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 -- cgit v1.2.3 From ad5183a3956321c8956defa3f32e399400662bf6 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 09:20:48 +0300 Subject: (Tag Cleanup): Removed unnecessary tag validation migration. --- pydis_site/apps/api/migrations/0008_tag_embed_validator.py | 7 ------- 1 file changed, 7 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/migrations/0008_tag_embed_validator.py b/pydis_site/apps/api/migrations/0008_tag_embed_validator.py index d53ddb90..d92042d2 100644 --- a/pydis_site/apps/api/migrations/0008_tag_embed_validator.py +++ b/pydis_site/apps/api/migrations/0008_tag_embed_validator.py @@ -1,7 +1,5 @@ # Generated by Django 2.1.1 on 2018-09-23 10:07 -import pydis_site.apps.api.models.bot.tag -import django.contrib.postgres.fields.jsonb from django.db import migrations @@ -12,9 +10,4 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( - model_name='tag', - name='embed', - field=django.contrib.postgres.fields.jsonb.JSONField(help_text='The actual embed shown by this tag.', validators=[pydis_site.apps.api.models.bot.tag.validate_tag_embed]), - ), ] -- cgit v1.2.3 From a11d3a38a03b63a2018785917a56fe54c1b1f027 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 09:21:17 +0300 Subject: (Tag Cleanup): Removed Tag model file. --- pydis_site/apps/api/models/bot/tag.py | 173 ---------------------------------- 1 file changed, 173 deletions(-) delete mode 100644 pydis_site/apps/api/models/bot/tag.py (limited to 'pydis_site/apps/api') 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 790ad37a..00000000 --- a/pydis_site/apps/api/models/bot/tag.py +++ /dev/null @@ -1,173 +0,0 @@ -from collections.abc import Mapping -from typing import Any, Dict - -from django.core.exceptions import ValidationError -from django.core.validators import MaxLengthValidator, MinLengthValidator - - -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) -- cgit v1.2.3 From 5357d5baf3234550c09f88fa38a9f436503f570c Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 09:21:48 +0300 Subject: (Tag Cleanup): Added Tag removal migration --- pydis_site/apps/api/migrations/0051_delete_tag.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0051_delete_tag.py (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/migrations/0051_delete_tag.py b/pydis_site/apps/api/migrations/0051_delete_tag.py new file mode 100644 index 00000000..bada5788 --- /dev/null +++ b/pydis_site/apps/api/migrations/0051_delete_tag.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.11 on 2020-04-01 06:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0050_remove_infractions_active_default_value'), + ] + + operations = [ + migrations.DeleteModel( + name='Tag', + ), + ] -- cgit v1.2.3 From 99485facba10d1f0d918e8c0ab152372db89c7b9 Mon Sep 17 00:00:00 2001 From: ks123 Date: Wed, 1 Apr 2020 09:25:15 +0300 Subject: (Tag Cleanup): Removed Tag viewset from viewsets __init__.py --- pydis_site/apps/api/viewsets/__init__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py index 8699517e..dfbb880d 100644 --- a/pydis_site/apps/api/viewsets/__init__.py +++ b/pydis_site/apps/api/viewsets/__init__.py @@ -10,7 +10,6 @@ from .bot import ( OffTopicChannelNameViewSet, ReminderViewSet, RoleViewSet, - TagViewSet, UserViewSet ) from .log_entry import LogEntryViewSet -- cgit v1.2.3 From 2b2f491e476c4564899c8716fde189da4a493287 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 27 Aug 2020 07:34:41 +0300 Subject: Move import to beginning of models mixins file --- pydis_site/apps/api/models/mixins.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/mixins.py b/pydis_site/apps/api/models/mixins.py index e95888b7..692d14f7 100644 --- a/pydis_site/apps/api/models/mixins.py +++ b/pydis_site/apps/api/models/mixins.py @@ -4,6 +4,7 @@ from typing import Any, Dict from django.core.exceptions import ValidationError from django.core.validators import MaxLengthValidator, MinLengthValidator +from django.db import models def is_bool_validator(value: Any) -> None: @@ -173,8 +174,6 @@ def validate_embed(embed: Any) -> None: for validator in field_validators[field_name]: validator(value) -from django.db import models - class ModelReprMixin: """Mixin providing a `__repr__()` to display model class name and initialisation parameters.""" -- cgit v1.2.3 From 2011d2f2c30dc67c1f34e82e5593f8e4ce1243dd Mon Sep 17 00:00:00 2001 From: Karlis S Date: Thu, 27 Aug 2020 04:44:08 +0000 Subject: Move some parts from Mixins file to utils --- pydis_site/apps/api/models/mixins.py | 172 ---------------------------------- pydis_site/apps/api/models/utils.py | 174 +++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 172 deletions(-) create mode 100644 pydis_site/apps/api/models/utils.py (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/mixins.py b/pydis_site/apps/api/models/mixins.py index 692d14f7..5d75b78b 100644 --- a/pydis_site/apps/api/models/mixins.py +++ b/pydis_site/apps/api/models/mixins.py @@ -1,180 +1,8 @@ -from collections.abc import Mapping from operator import itemgetter -from typing import Any, Dict -from django.core.exceptions import ValidationError -from django.core.validators import MaxLengthValidator, MinLengthValidator from django.db import models -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_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_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_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_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.utils import validate_embed - >>> class MyMessage(models.Model): - ... embed = pgfields.JSONField( - ... validators=( - ... validate_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_embed_fields - ), - 'footer': (validate_embed_footer,), - 'author': (validate_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 ModelReprMixin: """Mixin providing a `__repr__()` to display model class name and initialisation parameters.""" diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/utils.py new file mode 100644 index 00000000..97a79507 --- /dev/null +++ b/pydis_site/apps/api/models/utils.py @@ -0,0 +1,174 @@ +from collections.abc import Mapping +from operator import itemgetter +from typing import Any, Dict + +from django.core.exceptions import ValidationError +from django.core.validators import MaxLengthValidator, MinLengthValidator + + +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_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_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_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_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.utils import validate_embed + >>> class MyMessage(models.Model): + ... embed = pgfields.JSONField( + ... validators=( + ... validate_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_embed_fields + ), + 'footer': (validate_embed_footer,), + 'author': (validate_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) -- cgit v1.2.3 From 1ab4ccbbbcf38dddace5855ed9521219821b7698 Mon Sep 17 00:00:00 2001 From: Karlis S Date: Thu, 27 Aug 2020 04:48:09 +0000 Subject: Remove unused import from models utils --- pydis_site/apps/api/models/utils.py | 1 - 1 file changed, 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/utils.py index 97a79507..107231ba 100644 --- a/pydis_site/apps/api/models/utils.py +++ b/pydis_site/apps/api/models/utils.py @@ -1,5 +1,4 @@ from collections.abc import Mapping -from operator import itemgetter from typing import Any, Dict from django.core.exceptions import ValidationError -- cgit v1.2.3 From fd2909c936efb9ab2285896b297b405c4dd7a1fb Mon Sep 17 00:00:00 2001 From: Karlis S Date: Thu, 27 Aug 2020 05:35:01 +0000 Subject: Move last parts from mixins to utils and delete mixins --- pydis_site/apps/api/models/mixins.py | 31 ------------------------------- pydis_site/apps/api/models/utils.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 31 deletions(-) delete mode 100644 pydis_site/apps/api/models/mixins.py (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/mixins.py b/pydis_site/apps/api/models/mixins.py deleted file mode 100644 index 5d75b78b..00000000 --- a/pydis_site/apps/api/models/mixins.py +++ /dev/null @@ -1,31 +0,0 @@ -from operator import itemgetter - -from django.db import models - - -class ModelReprMixin: - """Mixin providing a `__repr__()` to display model class name and initialisation parameters.""" - - def __repr__(self): - """Returns the current model class name and initialisation parameters.""" - attributes = ' '.join( - f'{attribute}={value!r}' - for attribute, value in sorted( - self.__dict__.items(), - key=itemgetter(0) - ) - if not attribute.startswith('_') - ) - return f'<{self.__class__.__name__}({attributes})>' - - -class ModelTimestampMixin(models.Model): - """Mixin providing created_at and updated_at fields.""" - - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - class Meta: - """Metaconfig for the mixin.""" - - abstract = True diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/utils.py index 107231ba..692d14f7 100644 --- a/pydis_site/apps/api/models/utils.py +++ b/pydis_site/apps/api/models/utils.py @@ -1,8 +1,10 @@ from collections.abc import Mapping +from operator import itemgetter from typing import Any, Dict from django.core.exceptions import ValidationError from django.core.validators import MaxLengthValidator, MinLengthValidator +from django.db import models def is_bool_validator(value: Any) -> None: @@ -171,3 +173,31 @@ def validate_embed(embed: Any) -> None: if field_name in field_validators: for validator in field_validators[field_name]: validator(value) + + +class ModelReprMixin: + """Mixin providing a `__repr__()` to display model class name and initialisation parameters.""" + + def __repr__(self): + """Returns the current model class name and initialisation parameters.""" + attributes = ' '.join( + f'{attribute}={value!r}' + for attribute, value in sorted( + self.__dict__.items(), + key=itemgetter(0) + ) + if not attribute.startswith('_') + ) + return f'<{self.__class__.__name__}({attributes})>' + + +class ModelTimestampMixin(models.Model): + """Mixin providing created_at and updated_at fields.""" + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + """Metaconfig for the mixin.""" + + abstract = True -- cgit v1.2.3 From 4eeb841724e7603dffc5334a6e4aa7b7b7ced997 Mon Sep 17 00:00:00 2001 From: Karlis S Date: Thu, 27 Aug 2020 05:37:55 +0000 Subject: Fix FilterList model mixins import path --- pydis_site/apps/api/models/bot/filter_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py index d279e137..cb4acb68 100644 --- a/pydis_site/apps/api/models/bot/filter_list.py +++ b/pydis_site/apps/api/models/bot/filter_list.py @@ -1,6 +1,6 @@ from django.db import models -from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin +from pydis_site.apps.api.models.utils import ModelReprMixin, ModelTimestampMixin class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model): -- cgit v1.2.3 From feafa0950d9132350e36f58fbae57121363d3277 Mon Sep 17 00:00:00 2001 From: Karlis S Date: Thu, 27 Aug 2020 05:47:47 +0000 Subject: Still move mixins back to its own file --- pydis_site/apps/api/models/bot/filter_list.py | 2 +- pydis_site/apps/api/models/mixins.py | 31 +++++++++++++++++++++++++++ pydis_site/apps/api/models/utils.py | 30 -------------------------- 3 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 pydis_site/apps/api/models/mixins.py (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py index cb4acb68..d279e137 100644 --- a/pydis_site/apps/api/models/bot/filter_list.py +++ b/pydis_site/apps/api/models/bot/filter_list.py @@ -1,6 +1,6 @@ from django.db import models -from pydis_site.apps.api.models.utils import ModelReprMixin, ModelTimestampMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model): diff --git a/pydis_site/apps/api/models/mixins.py b/pydis_site/apps/api/models/mixins.py new file mode 100644 index 00000000..5d75b78b --- /dev/null +++ b/pydis_site/apps/api/models/mixins.py @@ -0,0 +1,31 @@ +from operator import itemgetter + +from django.db import models + + +class ModelReprMixin: + """Mixin providing a `__repr__()` to display model class name and initialisation parameters.""" + + def __repr__(self): + """Returns the current model class name and initialisation parameters.""" + attributes = ' '.join( + f'{attribute}={value!r}' + for attribute, value in sorted( + self.__dict__.items(), + key=itemgetter(0) + ) + if not attribute.startswith('_') + ) + return f'<{self.__class__.__name__}({attributes})>' + + +class ModelTimestampMixin(models.Model): + """Mixin providing created_at and updated_at fields.""" + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + """Metaconfig for the mixin.""" + + abstract = True diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/utils.py index 692d14f7..107231ba 100644 --- a/pydis_site/apps/api/models/utils.py +++ b/pydis_site/apps/api/models/utils.py @@ -1,10 +1,8 @@ from collections.abc import Mapping -from operator import itemgetter from typing import Any, Dict from django.core.exceptions import ValidationError from django.core.validators import MaxLengthValidator, MinLengthValidator -from django.db import models def is_bool_validator(value: Any) -> None: @@ -173,31 +171,3 @@ def validate_embed(embed: Any) -> None: if field_name in field_validators: for validator in field_validators[field_name]: validator(value) - - -class ModelReprMixin: - """Mixin providing a `__repr__()` to display model class name and initialisation parameters.""" - - def __repr__(self): - """Returns the current model class name and initialisation parameters.""" - attributes = ' '.join( - f'{attribute}={value!r}' - for attribute, value in sorted( - self.__dict__.items(), - key=itemgetter(0) - ) - if not attribute.startswith('_') - ) - return f'<{self.__class__.__name__}({attributes})>' - - -class ModelTimestampMixin(models.Model): - """Mixin providing created_at and updated_at fields.""" - - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - class Meta: - """Metaconfig for the mixin.""" - - abstract = True -- cgit v1.2.3 From e1f75feb74c24b262b276534070e4ffb3bf295b3 Mon Sep 17 00:00:00 2001 From: Karlis S Date: Thu, 27 Aug 2020 05:53:09 +0000 Subject: Fix import paths of mixins in message model --- pydis_site/apps/api/models/bot/message.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py index e929ea25..f6ae55a5 100644 --- a/pydis_site/apps/api/models/bot/message.py +++ b/pydis_site/apps/api/models/bot/message.py @@ -6,7 +6,8 @@ from django.db import models from django.utils import timezone from pydis_site.apps.api.models.bot.user import User -from pydis_site.apps.api.models.utils import ModelReprMixin, validate_embed +from pydis_site.apps.api.models.mixins import ModelReprMixin +from pydis_site.apps.api.models.utils import validate_embed class Message(ModelReprMixin, models.Model): -- cgit v1.2.3 From ec689ad42d28de55d47f9d3730389ae7e179d565 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 27 Aug 2020 08:57:28 +0300 Subject: Fix embed validator location in migration --- pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py b/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py index e617e1c9..b03c8a18 100644 --- a/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py +++ b/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py @@ -16,6 +16,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='deletedmessage', name='embeds', - field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(validators=[pydis_site.apps.api.models.bot.tag.validate_tag_embed]), blank=True, help_text='Embeds attached to this message.', size=None), + field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(validators=[pydis_site.apps.api.models.utils.validate_embed]), blank=True, help_text='Embeds attached to this message.', size=None), ), ] -- cgit v1.2.3 From b43477faa59970adffc93c58743a4abce53b1547 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 27 Aug 2020 09:01:28 +0300 Subject: Replace bad import on migration --- pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py b/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py index b03c8a18..124c6a57 100644 --- a/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py +++ b/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py @@ -3,7 +3,7 @@ import django.contrib.postgres.fields import django.contrib.postgres.fields.jsonb from django.db import migrations -import pydis_site.apps.api.models.bot.tag +import pydis_site.apps.api.models.utils class Migration(migrations.Migration): -- cgit v1.2.3 From a9e1f6f398ca4cac42c01a211513c02881e7541d Mon Sep 17 00:00:00 2001 From: Karlis S Date: Tue, 1 Sep 2020 14:59:23 +0000 Subject: Create new merge migration to fix conflicts between migrations --- pydis_site/apps/api/migrations/0062_merge_20200901_1459.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0062_merge_20200901_1459.py (limited to 'pydis_site/apps/api') diff --git a/pydis_site/apps/api/migrations/0062_merge_20200901_1459.py b/pydis_site/apps/api/migrations/0062_merge_20200901_1459.py new file mode 100644 index 00000000..d162acf1 --- /dev/null +++ b/pydis_site/apps/api/migrations/0062_merge_20200901_1459.py @@ -0,0 +1,14 @@ +# Generated by Django 3.0.8 on 2020-09-01 14:59 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0051_delete_tag'), + ('api', '0061_merge_20200830_0526'), + ] + + operations = [ + ] -- cgit v1.2.3