aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Chris Lovering <[email protected]>2022-06-30 10:48:33 +0100
committerGravatar Chris Lovering <[email protected]>2022-06-30 10:49:51 +0100
commitd4f717ec186ffeedf7bdeb4991868160f1540b83 (patch)
tree0acc1788d848566c49bb0d49a10c2e4dfa1fd80f
parentMerge pull request #735 from python-discord/cj9-qualifier (diff)
Remove embed validators for deleted messages
These caused more harm than they were worth, as every time Discord updated a behaviour of an embed we would get errors and need ot update the validation. Instead we should just accept whatever discord gives us as correct
-rw-r--r--pydis_site/apps/api/migrations/0083_remove_embed_validation.py19
-rw-r--r--pydis_site/apps/api/models/bot/message.py5
-rw-r--r--pydis_site/apps/api/models/utils.py172
-rw-r--r--pydis_site/apps/api/tests/test_validators.py229
4 files changed, 20 insertions, 405 deletions
diff --git a/pydis_site/apps/api/migrations/0083_remove_embed_validation.py b/pydis_site/apps/api/migrations/0083_remove_embed_validation.py
new file mode 100644
index 00000000..e835bb66
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0083_remove_embed_validation.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.1.14 on 2022-06-30 09:41
+
+import django.contrib.postgres.fields
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0082_otn_allow_big_solidus'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='deletedmessage',
+ name='embeds',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(), blank=True, help_text='Embeds attached to this message.', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py
index bab3368d..bfa54721 100644
--- a/pydis_site/apps/api/models/bot/message.py
+++ b/pydis_site/apps/api/models/bot/message.py
@@ -7,7 +7,6 @@ from django.utils import timezone
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):
@@ -48,9 +47,7 @@ class Message(ModelReprMixin, models.Model):
blank=True
)
embeds = pgfields.ArrayField(
- models.JSONField(
- validators=(validate_embed,)
- ),
+ models.JSONField(),
blank=True,
help_text="Embeds attached to this message."
)
diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/utils.py
deleted file mode 100644
index 859394d2..00000000
--- a/pydis_site/apps/api/models/utils.py
+++ /dev/null
@@ -1,172 +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_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.db import models
- >>> from pydis_site.apps.api.models.utils import validate_embed
- >>> class MyMessage(models.Model):
- ... embed = models.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=4096),),
- '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)
diff --git a/pydis_site/apps/api/tests/test_validators.py b/pydis_site/apps/api/tests/test_validators.py
index 551cc2aa..8c46fcbc 100644
--- a/pydis_site/apps/api/tests/test_validators.py
+++ b/pydis_site/apps/api/tests/test_validators.py
@@ -5,7 +5,6 @@ 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.utils import validate_embed
REQUIRED_KEYS = (
@@ -22,234 +21,6 @@ class BotSettingValidatorTests(TestCase):
validate_bot_setting_name('bad name')
-class TagEmbedValidatorTests(TestCase):
- def test_rejects_non_mapping(self):
- with self.assertRaises(ValidationError):
- validate_embed('non-empty non-mapping')
-
- def test_rejects_missing_required_keys(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'unknown': "key"
- })
-
- def test_rejects_one_correct_one_incorrect(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'provider': "??",
- 'title': ""
- })
-
- def test_rejects_empty_required_key(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'title': ''
- })
-
- def test_rejects_list_as_embed(self):
- with self.assertRaises(ValidationError):
- validate_embed([])
-
- def test_rejects_required_keys_and_unknown_keys(self):
- with self.assertRaises(ValidationError):
- 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_embed({
- 'title': 'a' * 257
- })
-
- def test_rejects_too_many_fields(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'fields': [{} for _ in range(26)]
- })
-
- def test_rejects_too_long_description(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'description': 'd' * 4097
- })
-
- def test_allows_valid_embed(self):
- validate_embed({
- 'title': "My embed",
- 'description': "look at my embed, my embed is amazing"
- })
-
- def test_allows_unvalidated_fields(self):
- validate_embed({
- 'title': "My embed",
- 'provider': "what am I??"
- })
-
- def test_rejects_fields_as_list_of_non_mappings(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'fields': ['abc']
- })
-
- def test_rejects_fields_with_unknown_fields(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'fields': [
- {
- 'what': "is this field"
- }
- ]
- })
-
- def test_rejects_fields_with_too_long_name(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'fields': [
- {
- 'name': "a" * 257
- }
- ]
- })
-
- def test_rejects_one_correct_one_incorrect_field(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'fields': [
- {
- 'name': "Totally valid",
- 'value': "LOOK AT ME"
- },
- {
- 'name': "Totally valid",
- 'value': "LOOK AT ME",
- 'oh': "what is this key?"
- }
- ]
- })
-
- def test_rejects_missing_required_field_field(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'fields': [
- {
- 'name': "Totally valid",
- 'inline': True,
- }
- ]
- })
-
- def test_rejects_invalid_inline_field_field(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'fields': [
- {
- 'name': "Totally valid",
- 'value': "LOOK AT ME",
- 'inline': "Totally not a boolean",
- }
- ]
- })
-
- def test_allows_valid_fields(self):
- validate_embed({
- 'fields': [
- {
- 'name': "valid",
- 'value': "field",
- },
- {
- 'name': "valid",
- 'value': "field",
- 'inline': False,
- },
- {
- 'name': "valid",
- 'value': "field",
- 'inline': True,
- },
- ]
- })
-
- def test_rejects_footer_as_non_mapping(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'title': "whatever",
- 'footer': []
- })
-
- def test_rejects_footer_with_unknown_fields(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'title': "whatever",
- 'footer': {
- 'duck': "quack"
- }
- })
-
- def test_rejects_footer_with_empty_text(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'title': "whatever",
- 'footer': {
- 'text': ""
- }
- })
-
- def test_allows_footer_with_proper_values(self):
- validate_embed({
- 'title': "whatever",
- 'footer': {
- 'text': "django good"
- }
- })
-
- def test_rejects_author_as_non_mapping(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'title': "whatever",
- 'author': []
- })
-
- def test_rejects_author_with_unknown_field(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'title': "whatever",
- 'author': {
- 'field': "that is unknown"
- }
- })
-
- def test_rejects_author_with_empty_name(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'title': "whatever",
- 'author': {
- 'name': ""
- }
- })
-
- def test_rejects_author_with_one_correct_one_incorrect(self):
- with self.assertRaises(ValidationError):
- validate_embed({
- 'title': "whatever",
- 'author': {
- # Relies on "dictionary insertion order remembering" (D.I.O.R.) behaviour
- 'url': "bobswebsite.com",
- 'name': ""
- }
- })
-
- def test_allows_author_with_proper_values(self):
- validate_embed({
- 'title': "whatever",
- 'author': {
- 'name': "Bob"
- }
- })
-
-
class OffensiveMessageValidatorsTests(TestCase):
def test_accepts_future_date(self):
future_date_validator(datetime(3000, 1, 1, tzinfo=timezone.utc))