From e77259351da7f18a375aa9ce4801755efca6c65b Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Fri, 20 Mar 2020 19:07:57 -0700 Subject: Use basename instead of base_name with DRF router base_name was deprecated in 3.9 and finally removed in 3.11. --- pydis_site/apps/api/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index 4a0281b4..3bb5198e 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -41,7 +41,7 @@ bot_router.register( bot_router.register( 'off-topic-channel-names', OffTopicChannelNameViewSet, - base_name='offtopicchannelname' + basename='offtopicchannelname' ) bot_router.register( 'reminders', -- cgit v1.2.3 From 145beb37fcb4fa2f487f18b234dd72bc4e10c279 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 21 Mar 2020 10:04:44 -0700 Subject: Allow empty list for message embeds By default, blank=False for ArrayFields but allow_empty=True for ListField. Before DRF 3.10 there was a bug that ListField didn't respect the default value of blank=False and thus created a ListField in the serialiser with the default of allow_empty=True. We were relying on the behaviour of that bug. See encode/django-rest-framework#6597. --- .../migrations/0051_allow_blank_message_embeds.py | 21 +++++++++++++++++++++ pydis_site/apps/api/models/bot/message.py | 1 + 2 files changed, 22 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py (limited to 'pydis_site') 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 new file mode 100644 index 00000000..e617e1c9 --- /dev/null +++ b/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py @@ -0,0 +1,21 @@ +# Generated by Django 3.0.4 on 2020-03-21 17:05 + +import django.contrib.postgres.fields +import django.contrib.postgres.fields.jsonb +from django.db import migrations +import pydis_site.apps.api.models.bot.tag + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0050_remove_infractions_active_default_value'), + ] + + operations = [ + 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), + ), + ] diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py index 8b18fc9f..0b279580 100644 --- a/pydis_site/apps/api/models/bot/message.py +++ b/pydis_site/apps/api/models/bot/message.py @@ -49,6 +49,7 @@ class Message(ModelReprMixin, models.Model): pgfields.JSONField( validators=(validate_tag_embed,) ), + blank=True, help_text="Embeds attached to this message." ) attachments = pgfields.ArrayField( -- cgit v1.2.3 From 354d78ea01b11f8197dd6933be460f9b277e4645 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 27 May 2020 09:24:48 +0200 Subject: No longer accept or track avatar_hash. This should completely remove avatar_hash from the site - both in our tests, in the model itself, and from the database (as a result of the migration). --- .../api/migrations/0052_remove_user_avatar_hash.py | 17 +++++++ pydis_site/apps/api/models/bot/user.py | 8 ---- pydis_site/apps/api/serializers.py | 2 +- pydis_site/apps/api/tests/test_deleted_messages.py | 2 - pydis_site/apps/api/tests/test_infractions.py | 7 +-- pydis_site/apps/api/tests/test_models.py | 53 ++++++++++++++-------- pydis_site/apps/api/tests/test_nominations.py | 2 - pydis_site/apps/api/tests/test_reminders.py | 4 -- pydis_site/apps/api/tests/test_users.py | 8 ---- pydis_site/apps/api/viewsets/bot/user.py | 5 -- pydis_site/apps/home/tests/test_signal_listener.py | 5 -- pydis_site/apps/staff/tests/test_logs_view.py | 1 - 12 files changed, 52 insertions(+), 62 deletions(-) create mode 100644 pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py b/pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py new file mode 100644 index 00000000..26b3b954 --- /dev/null +++ b/pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.11 on 2020-05-27 07:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0051_create_news_setting'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='avatar_hash', + ), + ] diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index 5140d2bf..65e8751e 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -31,14 +31,6 @@ class User(ModelReprMixin, models.Model): ), help_text="The discriminator of this user, taken from Discord." ) - avatar_hash = models.CharField( - max_length=100, - help_text=( - "The user's avatar hash, taken from Discord. " - "Null if the user does not have any custom avatar." - ), - null=True - ) roles = models.ManyToManyField( Role, help_text="Any roles this user has on our server." diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index e11c4af2..cc3f167d 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -235,7 +235,7 @@ class UserSerializer(BulkSerializerMixin, ModelSerializer): """Metadata defined for the Django REST Framework.""" model = User - fields = ('id', 'avatar_hash', 'name', 'discriminator', 'roles', 'in_guild') + fields = ('id', 'name', 'discriminator', 'roles', 'in_guild') depth = 1 diff --git a/pydis_site/apps/api/tests/test_deleted_messages.py b/pydis_site/apps/api/tests/test_deleted_messages.py index fb93cae6..f079a8dd 100644 --- a/pydis_site/apps/api/tests/test_deleted_messages.py +++ b/pydis_site/apps/api/tests/test_deleted_messages.py @@ -13,7 +13,6 @@ class DeletedMessagesWithoutActorTests(APISubdomainTestCase): id=55, name='Robbie Rotten', discriminator=55, - avatar_hash=None ) cls.data = { @@ -54,7 +53,6 @@ class DeletedMessagesWithActorTests(APISubdomainTestCase): id=12904, name='Joe Armstrong', discriminator=1245, - avatar_hash=None ) cls.data = { diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py index bc258b77..93ef8171 100644 --- a/pydis_site/apps/api/tests/test_infractions.py +++ b/pydis_site/apps/api/tests/test_infractions.py @@ -47,7 +47,6 @@ class InfractionTests(APISubdomainTestCase): id=5, name='james', discriminator=1, - avatar_hash=None ) cls.ban_hidden = Infraction.objects.create( user_id=cls.user.id, @@ -169,13 +168,11 @@ class CreationTests(APISubdomainTestCase): id=5, name='james', discriminator=1, - avatar_hash=None ) cls.second_user = User.objects.create( id=6, name='carl', discriminator=2, - avatar_hash=None ) def test_accepts_valid_data(self): @@ -522,7 +519,6 @@ class ExpandedTests(APISubdomainTestCase): id=5, name='james', discriminator=1, - avatar_hash=None ) cls.kick = Infraction.objects.create( user_id=cls.user.id, @@ -540,7 +536,7 @@ class ExpandedTests(APISubdomainTestCase): def check_expanded_fields(self, infraction): for key in ('user', 'actor'): obj = infraction[key] - for field in ('id', 'name', 'discriminator', 'avatar_hash', 'roles', 'in_guild'): + for field in ('id', 'name', 'discriminator', 'roles', 'in_guild'): self.assertTrue(field in obj, msg=f'field "{field}" missing from {key}') def test_list_expanded(self): @@ -599,7 +595,6 @@ class SerializerTests(APISubdomainTestCase): id=5, name='james', discriminator=1, - avatar_hash=None ) def create_infraction(self, _type: str, active: bool): diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index a97d3251..b4754484 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -39,12 +39,14 @@ class StringDunderMethodTests(SimpleTestCase): self.nomination = Nomination( id=123, actor=User( - id=9876, name='Mr. Hemlock', - discriminator=6666, avatar_hash=None + id=9876, + name='Mr. Hemlock', + discriminator=6666, ), user=User( - id=9876, name="Hemlock's Cat", - discriminator=7777, avatar_hash=None + id=9876, + name="Hemlock's Cat", + discriminator=7777, ), reason="He purrrrs like the best!", ) @@ -53,15 +55,17 @@ class StringDunderMethodTests(SimpleTestCase): DeletedMessage( id=45, author=User( - id=444, name='bill', - discriminator=5, avatar_hash=None + id=444, + name='bill', + discriminator=5, ), channel_id=666, content="wooey", deletion_context=MessageDeletionContext( actor=User( - id=5555, name='shawn', - discriminator=555, avatar_hash=None + id=5555, + name='shawn', + discriminator=555, ), creation=dt.utcnow() ), @@ -84,8 +88,9 @@ class StringDunderMethodTests(SimpleTestCase): Message( id=45, author=User( - id=444, name='bill', - discriminator=5, avatar_hash=None + id=444, + name='bill', + discriminator=5, ), channel_id=666, content="wooey", @@ -93,8 +98,9 @@ class StringDunderMethodTests(SimpleTestCase): ), MessageDeletionContext( actor=User( - id=5555, name='shawn', - discriminator=555, avatar_hash=None + id=5555, + name='shawn', + discriminator=555, ), creation=dt.utcnow() ), @@ -103,22 +109,29 @@ class StringDunderMethodTests(SimpleTestCase): embed={'content': "the builder"} ), User( - id=5, name='bob', - discriminator=1, avatar_hash=None + id=5, + name='bob', + discriminator=1, ), Infraction( - user_id=5, actor_id=5, - type='kick', reason='He terk my jerb!' + user_id=5, + actor_id=5, + type='kick', + reason='He terk my jerb!' ), Infraction( - user_id=5, actor_id=5, hidden=True, - type='kick', reason='He terk my jerb!', + user_id=5, + actor_id=5, + hidden=True, + type='kick', + reason='He terk my jerb!', expires_at=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc) ), Reminder( author=User( - id=452, name='billy', - discriminator=5, avatar_hash=None + id=452, + name='billy', + discriminator=5, ), channel_id=555, jump_url=( diff --git a/pydis_site/apps/api/tests/test_nominations.py b/pydis_site/apps/api/tests/test_nominations.py index 76cb4112..92c62c87 100644 --- a/pydis_site/apps/api/tests/test_nominations.py +++ b/pydis_site/apps/api/tests/test_nominations.py @@ -13,7 +13,6 @@ class CreationTests(APISubdomainTestCase): id=1234, name='joe dart', discriminator=1111, - avatar_hash=None ) def test_accepts_valid_data(self): @@ -190,7 +189,6 @@ class NominationTests(APISubdomainTestCase): id=1234, name='joe dart', discriminator=1111, - avatar_hash=None ) cls.active_nomination = Nomination.objects.create( diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py index 3441e0cc..c7fa07c9 100644 --- a/pydis_site/apps/api/tests/test_reminders.py +++ b/pydis_site/apps/api/tests/test_reminders.py @@ -53,7 +53,6 @@ class ReminderCreationTests(APISubdomainTestCase): id=1234, name='Mermaid Man', discriminator=1234, - avatar_hash=None, ) def test_accepts_valid_data(self): @@ -86,7 +85,6 @@ class ReminderDeletionTests(APISubdomainTestCase): id=6789, name='Barnacle Boy', discriminator=6789, - avatar_hash=None, ) cls.reminder = Reminder.objects.create( @@ -118,7 +116,6 @@ class ReminderListTests(APISubdomainTestCase): id=6789, name='Patrick Star', discriminator=6789, - avatar_hash=None, ) cls.reminder_one = Reminder.objects.create( @@ -172,7 +169,6 @@ class ReminderUpdateTests(APISubdomainTestCase): id=666, name='Man Ray', discriminator=666, - avatar_hash=None, ) cls.reminder = Reminder.objects.create( diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 86799f19..4f563dc6 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -49,7 +49,6 @@ class CreationTests(APISubdomainTestCase): url = reverse('bot:user-list', host='api') data = { 'id': 42, - 'avatar_hash': "validavatarhashiswear", 'name': "Test", 'discriminator': 42, 'roles': [ @@ -63,7 +62,6 @@ class CreationTests(APISubdomainTestCase): self.assertEqual(response.json(), data) user = User.objects.get(id=42) - self.assertEqual(user.avatar_hash, data['avatar_hash']) self.assertEqual(user.name, data['name']) self.assertEqual(user.discriminator, data['discriminator']) self.assertEqual(user.in_guild, data['in_guild']) @@ -73,7 +71,6 @@ class CreationTests(APISubdomainTestCase): data = [ { 'id': 5, - 'avatar_hash': "hahayes", 'name': "test man", 'discriminator': 42, 'roles': [ @@ -83,7 +80,6 @@ class CreationTests(APISubdomainTestCase): }, { 'id': 8, - 'avatar_hash': "maybenot", 'name': "another test man", 'discriminator': 555, 'roles': [], @@ -99,7 +95,6 @@ class CreationTests(APISubdomainTestCase): url = reverse('bot:user-list', host='api') data = { 'id': 5, - 'avatar_hash': "hahayes", 'name': "test man", 'discriminator': 42, 'roles': [ @@ -114,7 +109,6 @@ class CreationTests(APISubdomainTestCase): url = reverse('bot:user-list', host='api') data = { 'id': True, - 'avatar_hash': 1902831, 'discriminator': "totally!" } @@ -148,7 +142,6 @@ class UserModelTests(APISubdomainTestCase): ) cls.user_with_roles = User.objects.create( id=1, - avatar_hash="coolavatarhash", name="Test User with two roles", discriminator=1111, in_guild=True, @@ -157,7 +150,6 @@ class UserModelTests(APISubdomainTestCase): cls.user_without_roles = User.objects.create( id=2, - avatar_hash="coolavatarhash", name="Test User without roles", discriminator=2222, in_guild=True, diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index a407787e..8f5bccfa 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -17,7 +17,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet): >>> [ ... { ... 'id': 409107086526644234, - ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb", ... 'name': "Python", ... 'discriminator': 4329, ... 'roles': [ @@ -39,7 +38,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet): #### Response format >>> { ... 'id': 409107086526644234, - ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb", ... 'name': "Python", ... 'discriminator': 4329, ... 'roles': [ @@ -62,7 +60,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet): #### Request body >>> { ... 'id': int, - ... 'avatar': str, ... 'name': str, ... 'discriminator': int, ... 'roles': List[int], @@ -83,7 +80,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet): #### Request body >>> { ... 'id': int, - ... 'avatar': str, ... 'name': str, ... 'discriminator': int, ... 'roles': List[int], @@ -102,7 +98,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet): #### Request body >>> { ... 'id': int, - ... 'avatar': str, ... 'name': str, ... 'discriminator': int, ... 'roles': List[int], diff --git a/pydis_site/apps/home/tests/test_signal_listener.py b/pydis_site/apps/home/tests/test_signal_listener.py index 66a67252..fb9a17db 100644 --- a/pydis_site/apps/home/tests/test_signal_listener.py +++ b/pydis_site/apps/home/tests/test_signal_listener.py @@ -81,14 +81,12 @@ class SignalListenerTests(TestCase): id=0, name="user", discriminator=0, - avatar_hash=None ) cls.discord_unmapped = DiscordUser.objects.create( id=2, name="unmapped", discriminator=0, - avatar_hash=None ) cls.discord_unmapped.roles.add(cls.unmapped_role) @@ -98,7 +96,6 @@ class SignalListenerTests(TestCase): id=3, name="not-in-guild", discriminator=0, - avatar_hash=None, in_guild=False ) @@ -106,7 +103,6 @@ class SignalListenerTests(TestCase): id=1, name="admin", discriminator=0, - avatar_hash=None ) cls.discord_admin.roles.set([cls.admin_role]) @@ -116,7 +112,6 @@ class SignalListenerTests(TestCase): id=4, name="admin", discriminator=0, - avatar_hash=None ) cls.discord_moderator.roles.set([cls.moderator_role]) diff --git a/pydis_site/apps/staff/tests/test_logs_view.py b/pydis_site/apps/staff/tests/test_logs_view.py index 1415c558..936d1ad5 100644 --- a/pydis_site/apps/staff/tests/test_logs_view.py +++ b/pydis_site/apps/staff/tests/test_logs_view.py @@ -21,7 +21,6 @@ class TestLogsView(TestCase): id=12345678901, name='Alan Turing', discriminator=1912, - avatar_hash=None ) cls.author.roles.add(cls.developers_role) -- cgit v1.2.3 From 0571baa63c039be061ddf448a8f0b0d3b24ea32e Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:12:01 +0100 Subject: Convert the roles field on the user model from a many-to-many field to a postgres array with roles --- pydis_site/apps/api/models/bot/user.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index 65e8751e..cc4e7873 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -1,10 +1,22 @@ +from django.contrib.postgres.fields import ArrayField from django.core.validators import MaxValueValidator, MinValueValidator +from django.core.exceptions import ValidationError from django.db import models from pydis_site.apps.api.models.bot.role import Role from pydis_site.apps.api.models.utils import ModelReprMixin +def _validate_existing_role(value: int) -> None: + """Validate that a role exists when given in to the user model.""" + role = Role.objects.filter(id=value) + + if not role: + raise ValidationError( + f"Role with ID {value} does not exist" + ) + + class User(ModelReprMixin, models.Model): """A Discord user.""" @@ -31,9 +43,18 @@ class User(ModelReprMixin, models.Model): ), help_text="The discriminator of this user, taken from Discord." ) - roles = models.ManyToManyField( - Role, - help_text="Any roles this user has on our server." + roles = ArrayField( + models.BigIntegerField( + validators=( + MinValueValidator( + limit_value=0, + message="Role IDs cannot be negative." + ), + _validate_existing_role + ) + ), + default=list, + help_text="IDs of roles the user has on the server" ) in_guild = models.BooleanField( default=True, @@ -51,7 +72,7 @@ class User(ModelReprMixin, models.Model): This will fall back to the Developers role if the user does not have any roles. """ - roles = self.roles.all() + roles = Role.objects.filter(id__in=self.roles) if not roles: return Role.objects.get(name="Developers") - return max(self.roles.all()) + return max(roles) -- cgit v1.2.3 From fac0d15afade7b00e883e3f9798a846956f222cc Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:13:01 +0100 Subject: Add migrations to switch user field to array --- .../api/migrations/0053_user_roles_to_array.py | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0053_user_roles_to_array.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0053_user_roles_to_array.py b/pydis_site/apps/api/migrations/0053_user_roles_to_array.py new file mode 100644 index 00000000..7ff3a548 --- /dev/null +++ b/pydis_site/apps/api/migrations/0053_user_roles_to_array.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.11 on 2020-06-02 13:42 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0052_remove_user_avatar_hash'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='roles', + ), + migrations.AddField( + model_name='user', + name='roles', + field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.')]), default=list, help_text='IDs of roles the user has on the server', size=None), + ), + ] -- cgit v1.2.3 From 044fb2661de97c710c37e48f908d99a4b173a264 Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:13:19 +0100 Subject: Add validator to ensure roles passed to user model exist --- .../migrations/0054_user_invalidate_unknown_role.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py b/pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py new file mode 100644 index 00000000..96230015 --- /dev/null +++ b/pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.11 on 2020-06-02 20:08 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models +import pydis_site.apps.api.models.bot.user + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0053_user_roles_to_array'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='roles', + field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.'), pydis_site.apps.api.models.bot.user._validate_existing_role]), default=list, help_text='IDs of roles the user has on the server', size=None), + ), + ] -- cgit v1.2.3 From 967ac5cb20280d4b3d70e9c9d15b435475cd70df Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:13:30 +0100 Subject: Remove primary key from user serializer --- pydis_site/apps/api/serializers.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index cc3f167d..f2d5144c 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -229,8 +229,6 @@ class TagSerializer(ModelSerializer): class UserSerializer(BulkSerializerMixin, ModelSerializer): """A class providing (de-)serialization of `User` instances.""" - roles = PrimaryKeyRelatedField(many=True, queryset=Role.objects.all(), required=False) - class Meta: """Metadata defined for the Django REST Framework.""" -- cgit v1.2.3 From 9c439d61b46dc37dd6b4fb4f5daa25aa671139e8 Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:13:46 +0100 Subject: Alter signals to handle OAuth2 with groups and role mappings --- pydis_site/apps/home/signals.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/home/signals.py b/pydis_site/apps/home/signals.py index 4cb4564b..8af48c15 100644 --- a/pydis_site/apps/home/signals.py +++ b/pydis_site/apps/home/signals.py @@ -150,7 +150,7 @@ class AllauthSignalListener: social_account = SocialAccount.objects.get(user=user, provider=DiscordProvider.id) discord_user = DiscordUser.objects.get(id=int(social_account.uid)) - mappings = RoleMapping.objects.filter(role__in=discord_user.roles.all()).all() + mappings = RoleMapping.objects.filter(role__id__in=discord_user.roles).all() is_staff = any(m.is_staff for m in mappings) if user.is_staff != is_staff: @@ -185,7 +185,7 @@ class AllauthSignalListener: self.mapping_model_deleted(RoleMapping, instance=old_instance) accounts = SocialAccount.objects.filter( - uid__in=(u.id for u in instance.role.user_set.all()) + uid__in=(u.id for u in DiscordUser.objects.filter(roles__contains=[instance.role.id])) ) for account in accounts: @@ -198,7 +198,7 @@ class AllauthSignalListener: discord_user = DiscordUser.objects.get(id=int(account.uid)) mappings = RoleMapping.objects.filter( - role__in=discord_user.roles.all() + role__id__in=discord_user.roles ).exclude(id=instance.id).all() is_staff = any(m.is_staff for m in mappings) @@ -289,9 +289,9 @@ class AllauthSignalListener: new_groups = [] is_staff = False - for role in user.roles.all(): + for role in user.roles: try: - mapping = mappings.get(role=role) + mapping = mappings.get(role__id=role) except RoleMapping.DoesNotExist: continue # No mapping exists -- cgit v1.2.3 From a32c9f1a7c5e2a84627c87b049a43c281fa26b5b Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:14:11 +0100 Subject: Alter account utilities to check list length instead of queryset length --- pydis_site/utils/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/utils/account.py b/pydis_site/utils/account.py index 2d699c88..b4e41198 100644 --- a/pydis_site/utils/account.py +++ b/pydis_site/utils/account.py @@ -54,7 +54,7 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter): raise ImmediateHttpResponse(redirect(reverse("home"))) - if user.roles.count() <= 1: + if len(user.roles) <= 1: add_message(request, ERROR, ERROR_JOIN_DISCORD) raise ImmediateHttpResponse(redirect(reverse("home"))) -- cgit v1.2.3 From 3cbe2b173db184d655939fd6d83fee4870b7da49 Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:14:28 +0100 Subject: Alter API tests to use new user roles format --- pydis_site/apps/api/tests/test_users.py | 2 +- pydis_site/apps/api/viewsets/bot/user.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 4f563dc6..4c0f6e27 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -146,7 +146,7 @@ class UserModelTests(APISubdomainTestCase): discriminator=1111, in_guild=True, ) - cls.user_with_roles.roles.add(cls.role_bottom, cls.role_top) + cls.user_with_roles.roles.extend([cls.role_bottom.id, cls.role_top.id]) cls.user_without_roles = User.objects.create( id=2, diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index 8f5bccfa..9571b3d7 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -118,4 +118,4 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet): """ serializer_class = UserSerializer - queryset = User.objects.prefetch_related('roles') + queryset = User.objects -- cgit v1.2.3 From 89437db9dc943d03799f65281e9c97dd6ff9af7f Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:15:08 +0100 Subject: Alter account utils test to use new user roles format --- pydis_site/tests/test_utils_account.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/tests/test_utils_account.py b/pydis_site/tests/test_utils_account.py index d2946368..e8db7b64 100644 --- a/pydis_site/tests/test_utils_account.py +++ b/pydis_site/tests/test_utils_account.py @@ -61,16 +61,18 @@ class AccountUtilsTests(TestCase): position=0 ) - self.discord_user_role.roles.add(everyone_role) - self.discord_user_two_roles.roles.add(everyone_role) + self.discord_user_role.roles.append(everyone_role.id) + self.discord_user_two_roles.roles.append(everyone_role.id) - self.discord_user_two_roles.roles.add(Role.objects.create( + developers_role = Role.objects.create( id=1, name="Developers", colour=0, permissions=0, position=1 - )) + ) + + self.discord_user_two_roles.roles.append(developers_role.id) def test_account_adapter(self): """Test that our Allauth account adapter functions correctly.""" -- cgit v1.2.3 From 3bb6ad7eb3ae6b24e8dc13e2458cc3a38b533d7f Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:15:20 +0100 Subject: Alter staff tests to make use of new user roles syntax --- pydis_site/apps/staff/tests/test_logs_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/staff/tests/test_logs_view.py b/pydis_site/apps/staff/tests/test_logs_view.py index 936d1ad5..17910bb6 100644 --- a/pydis_site/apps/staff/tests/test_logs_view.py +++ b/pydis_site/apps/staff/tests/test_logs_view.py @@ -23,7 +23,7 @@ class TestLogsView(TestCase): discriminator=1912, ) - cls.author.roles.add(cls.developers_role) + cls.author.roles.append(cls.developers_role.id) cls.deletion_context = MessageDeletionContext.objects.create( actor=cls.actor, -- cgit v1.2.3 From 034a0886b61a61b5ef90ca4f28d56a297b0af11e Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:15:30 +0100 Subject: Alter signal tests to make use of new user roles syntax --- pydis_site/apps/home/tests/test_signal_listener.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/home/tests/test_signal_listener.py b/pydis_site/apps/home/tests/test_signal_listener.py index fb9a17db..d99d81a5 100644 --- a/pydis_site/apps/home/tests/test_signal_listener.py +++ b/pydis_site/apps/home/tests/test_signal_listener.py @@ -89,7 +89,7 @@ class SignalListenerTests(TestCase): discriminator=0, ) - cls.discord_unmapped.roles.add(cls.unmapped_role) + cls.discord_unmapped.roles.append(cls.unmapped_role.id) cls.discord_unmapped.save() cls.discord_not_in_guild = DiscordUser.objects.create( @@ -105,7 +105,7 @@ class SignalListenerTests(TestCase): discriminator=0, ) - cls.discord_admin.roles.set([cls.admin_role]) + cls.discord_admin.roles = [cls.admin_role.id] cls.discord_admin.save() cls.discord_moderator = DiscordUser.objects.create( @@ -114,7 +114,7 @@ class SignalListenerTests(TestCase): discriminator=0, ) - cls.discord_moderator.roles.set([cls.moderator_role]) + cls.discord_moderator.roles = [cls.moderator_role.id] cls.discord_moderator.save() cls.django_user_discordless = DjangoUser.objects.create(username="no-discord") @@ -333,7 +333,7 @@ class SignalListenerTests(TestCase): handler._apply_groups(self.discord_admin, self.social_admin) self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - self.discord_admin.roles.add(self.admin_role) + self.discord_admin.roles.append(self.admin_role.id) self.discord_admin.save() def test_apply_groups_moderator(self): @@ -360,7 +360,7 @@ class SignalListenerTests(TestCase): handler._apply_groups(self.discord_moderator, self.social_moderator) self.assertEqual(self.django_user_discordless.groups.all().count(), 0) - self.discord_moderator.roles.add(self.moderator_role) + self.discord_moderator.roles.append(self.moderator_role.id) self.discord_moderator.save() def test_apply_groups_other(self): -- cgit v1.2.3 From f992f4b2900716ce5085aae1cb6fd104b0e6fd38 Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Tue, 2 Jun 2020 21:20:58 +0100 Subject: Alter import order in user model --- pydis_site/apps/api/models/bot/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index cc4e7873..c7510d8c 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -1,6 +1,6 @@ from django.contrib.postgres.fields import ArrayField -from django.core.validators import MaxValueValidator, MinValueValidator from django.core.exceptions import ValidationError +from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from pydis_site.apps.api.models.bot.role import Role -- cgit v1.2.3 From 2a02c68c0b05b307fdf48341b06a2ea2f6755fd3 Mon Sep 17 00:00:00 2001 From: Dennis Pham Date: Thu, 4 Jun 2020 21:07:16 -0400 Subject: Update navbar for Code Jam 7 Changed `Most Recent: Game Jam 2020` to `Upcoming: Code Jam 7` --- pydis_site/templates/base/navbar.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/templates/base/navbar.html b/pydis_site/templates/base/navbar.html index d2ea9589..f07662ae 100644 --- a/pydis_site/templates/base/navbar.html +++ b/pydis_site/templates/base/navbar.html @@ -87,8 +87,8 @@ - - Most Recent: Game Jam 2020 + + Upcoming: Code Jam 7 All events -- cgit v1.2.3 From 56fabe78d27286b431d391cf63e1adabdb8787a0 Mon Sep 17 00:00:00 2001 From: Joseph Banks Date: Fri, 5 Jun 2020 12:48:06 +0100 Subject: Remove very generous newline in role validator Co-authored-by: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> --- pydis_site/apps/api/models/bot/user.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index c7510d8c..bff4d642 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -12,9 +12,7 @@ def _validate_existing_role(value: int) -> None: role = Role.objects.filter(id=value) if not role: - raise ValidationError( - f"Role with ID {value} does not exist" - ) + raise ValidationError(f"Role with ID {value} does not exist") class User(ModelReprMixin, models.Model): -- cgit v1.2.3 From 7048cf15868f234cd59fe6072e1b77a81d3140a0 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 7 Jun 2020 01:49:04 +0200 Subject: Add Summer Code Jam image on landing page. --- .../static/images/events/summer_code_jam_2020.png | Bin 0 -> 271282 bytes pydis_site/templates/home/index.html | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 pydis_site/static/images/events/summer_code_jam_2020.png (limited to 'pydis_site') diff --git a/pydis_site/static/images/events/summer_code_jam_2020.png b/pydis_site/static/images/events/summer_code_jam_2020.png new file mode 100644 index 00000000..63c311b0 Binary files /dev/null and b/pydis_site/static/images/events/summer_code_jam_2020.png differ diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html index c30cbee6..3e96cc91 100644 --- a/pydis_site/templates/home/index.html +++ b/pydis_site/templates/home/index.html @@ -39,8 +39,8 @@ {# Right column container #} -- cgit v1.2.3 From 228a56ce1b61a2bf8750228779fde7347f835873 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 7 Jun 2020 22:17:06 +0200 Subject: Replace django-crispy-bulma -> flake8-annotations --- pydis_site/apps/home/views/home.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py index 4cf22594..79c7695b 100644 --- a/pydis_site/apps/home/views/home.py +++ b/pydis_site/apps/home/views/home.py @@ -23,8 +23,8 @@ class HomeView(View): "python-discord/bot", "python-discord/snekbox", "python-discord/seasonalbot", + "python-discord/flake8-annotations", "python-discord/django-simple-bulma", - "python-discord/django-crispy-bulma", ] def _get_api_data(self) -> Dict[str, Dict[str, str]]: -- cgit v1.2.3 From 27a27e5be8d873b8cbdc5f335dc38a627e4732be Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 7 Jun 2020 22:41:02 +0200 Subject: Fix broken github api test data --- pydis_site/apps/home/tests/mock_github_api_response.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/home/tests/mock_github_api_response.json b/pydis_site/apps/home/tests/mock_github_api_response.json index 37dc672e..35604a85 100644 --- a/pydis_site/apps/home/tests/mock_github_api_response.json +++ b/pydis_site/apps/home/tests/mock_github_api_response.json @@ -28,7 +28,7 @@ "forks_count": 31 }, { - "full_name": "python-discord/django-crispy-bulma", + "full_name": "python-discord/flake8-annotations", "description": "test", "stargazers_count": 97, "language": "Python", -- cgit v1.2.3 From 61ea5ad7439b43d80d6e2184fd663924a5a7dcc7 Mon Sep 17 00:00:00 2001 From: Dennis Pham Date: Mon, 22 Jun 2020 16:10:25 -0400 Subject: Catch ConnectionError when trying to get updated repository data This will prevent a 500 server error on our homepage when GitHub's API is down, allowing us to use the cached data we have instead. --- pydis_site/apps/home/views/home.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py index 79c7695b..20e38ab0 100644 --- a/pydis_site/apps/home/views/home.py +++ b/pydis_site/apps/home/views/home.py @@ -61,7 +61,7 @@ class HomeView(View): # Try to get new data from the API. If it fails, return the cached data. try: api_repositories = self._get_api_data() - except TypeError: + except (TypeError, ConnectionError): return RepositoryMetadata.objects.all() database_repositories = [] -- cgit v1.2.3 From 1f4beeb10ccec010aa2d503ed73b4b64e9c1895f Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Tue, 14 Jul 2020 14:21:16 +0200 Subject: Rename utils.py to mixins.py. More precise. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/migrations/0049_offensivemessage.py | 4 ++-- pydis_site/apps/api/models/__init__.py | 2 +- pydis_site/apps/api/models/bot/bot_setting.py | 2 +- pydis_site/apps/api/models/bot/documentation_link.py | 2 +- pydis_site/apps/api/models/bot/infraction.py | 2 +- pydis_site/apps/api/models/bot/message.py | 2 +- .../apps/api/models/bot/message_deletion_context.py | 2 +- pydis_site/apps/api/models/bot/nomination.py | 2 +- .../apps/api/models/bot/off_topic_channel_name.py | 2 +- pydis_site/apps/api/models/bot/offensive_message.py | 2 +- pydis_site/apps/api/models/bot/reminder.py | 2 +- pydis_site/apps/api/models/bot/role.py | 2 +- pydis_site/apps/api/models/bot/tag.py | 2 +- pydis_site/apps/api/models/bot/user.py | 2 +- pydis_site/apps/api/models/log_entry.py | 2 +- pydis_site/apps/api/models/mixins.py | 17 +++++++++++++++++ pydis_site/apps/api/models/utils.py | 17 ----------------- 17 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 pydis_site/apps/api/models/mixins.py delete mode 100644 pydis_site/apps/api/models/utils.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0049_offensivemessage.py b/pydis_site/apps/api/migrations/0049_offensivemessage.py index fe4a1961..f342cec3 100644 --- a/pydis_site/apps/api/migrations/0049_offensivemessage.py +++ b/pydis_site/apps/api/migrations/0049_offensivemessage.py @@ -3,7 +3,7 @@ import django.core.validators from django.db import migrations, models import pydis_site.apps.api.models.bot.offensive_message -import pydis_site.apps.api.models.utils +import pydis_site.apps.api.models.mixins class Migration(migrations.Migration): @@ -20,6 +20,6 @@ class Migration(migrations.Migration): ('channel_id', models.BigIntegerField(help_text='The channel ID that the message was sent in, taken from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Channel IDs cannot be negative.')])), ('delete_date', models.DateTimeField(help_text='The date on which the message will be auto-deleted.', validators=[pydis_site.apps.api.models.bot.offensive_message.future_date_validator])), ], - bases=(pydis_site.apps.api.models.utils.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 450d18cd..644b8757 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -15,4 +15,4 @@ from .bot import ( User ) from .log_entry import LogEntry -from .utils import ModelReprMixin +from .mixins import ModelReprMixin diff --git a/pydis_site/apps/api/models/bot/bot_setting.py b/pydis_site/apps/api/models/bot/bot_setting.py index 8d48eac7..2a3944f8 100644 --- a/pydis_site/apps/api/models/bot/bot_setting.py +++ b/pydis_site/apps/api/models/bot/bot_setting.py @@ -2,7 +2,7 @@ from django.contrib.postgres import fields as pgfields from django.core.exceptions import ValidationError from django.db import models -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin def validate_bot_setting_name(name: str) -> None: diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py index f844ae04..5a46460b 100644 --- a/pydis_site/apps/api/models/bot/documentation_link.py +++ b/pydis_site/apps/api/models/bot/documentation_link.py @@ -1,6 +1,6 @@ from django.db import models -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin class DocumentationLink(ModelReprMixin, models.Model): diff --git a/pydis_site/apps/api/models/bot/infraction.py b/pydis_site/apps/api/models/bot/infraction.py index f58e89a3..7660cbba 100644 --- a/pydis_site/apps/api/models/bot/infraction.py +++ b/pydis_site/apps/api/models/bot/infraction.py @@ -2,7 +2,7 @@ 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 +from pydis_site.apps.api.models.mixins import ModelReprMixin class Infraction(ModelReprMixin, models.Model): diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py index 8b18fc9f..7694ac75 100644 --- a/pydis_site/apps/api/models/bot/message.py +++ b/pydis_site/apps/api/models/bot/message.py @@ -7,7 +7,7 @@ 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.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin class Message(ModelReprMixin, models.Model): 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 44a0c8ae..04ae8d34 100644 --- a/pydis_site/apps/api/models/bot/message_deletion_context.py +++ b/pydis_site/apps/api/models/bot/message_deletion_context.py @@ -1,7 +1,7 @@ from django.db import models from pydis_site.apps.api.models.bot.user import User -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin class MessageDeletionContext(ModelReprMixin, models.Model): diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index cd9951aa..21e34e87 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -1,7 +1,7 @@ from django.db import models from pydis_site.apps.api.models.bot.user import User -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin class Nomination(ModelReprMixin, models.Model): 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 29280c27..20e77b9f 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 @@ -1,7 +1,7 @@ from django.core.validators import RegexValidator from django.db import models -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin class OffTopicChannelName(ModelReprMixin, models.Model): diff --git a/pydis_site/apps/api/models/bot/offensive_message.py b/pydis_site/apps/api/models/bot/offensive_message.py index b466d9c2..6c0e5ffb 100644 --- a/pydis_site/apps/api/models/bot/offensive_message.py +++ b/pydis_site/apps/api/models/bot/offensive_message.py @@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator from django.db import models -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin def future_date_validator(date: datetime.date) -> None: diff --git a/pydis_site/apps/api/models/bot/reminder.py b/pydis_site/apps/api/models/bot/reminder.py index d53fedb5..28722435 100644 --- a/pydis_site/apps/api/models/bot/reminder.py +++ b/pydis_site/apps/api/models/bot/reminder.py @@ -2,7 +2,7 @@ from django.core.validators import MinValueValidator from django.db import models from pydis_site.apps.api.models.bot.user import User -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin class Reminder(ModelReprMixin, models.Model): diff --git a/pydis_site/apps/api/models/bot/role.py b/pydis_site/apps/api/models/bot/role.py index 58bbf8b4..721e4815 100644 --- a/pydis_site/apps/api/models/bot/role.py +++ b/pydis_site/apps/api/models/bot/role.py @@ -3,7 +3,7 @@ from __future__ import annotations from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin class Role(ModelReprMixin, models.Model): diff --git a/pydis_site/apps/api/models/bot/tag.py b/pydis_site/apps/api/models/bot/tag.py index 5d4cc393..5e53582f 100644 --- a/pydis_site/apps/api/models/bot/tag.py +++ b/pydis_site/apps/api/models/bot/tag.py @@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError from django.core.validators import MaxLengthValidator, MinLengthValidator from django.db import models -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin def is_bool_validator(value: Any) -> None: diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index bff4d642..d7f203aa 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -4,7 +4,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from pydis_site.apps.api.models.bot.role import Role -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin def _validate_existing_role(value: int) -> None: diff --git a/pydis_site/apps/api/models/log_entry.py b/pydis_site/apps/api/models/log_entry.py index 488af48e..752cd2ca 100644 --- a/pydis_site/apps/api/models/log_entry.py +++ b/pydis_site/apps/api/models/log_entry.py @@ -1,7 +1,7 @@ from django.db import models from django.utils import timezone -from pydis_site.apps.api.models.utils import ModelReprMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin class LogEntry(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..0540c4de --- /dev/null +++ b/pydis_site/apps/api/models/mixins.py @@ -0,0 +1,17 @@ +from operator import itemgetter + + +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})>' diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/utils.py deleted file mode 100644 index 0540c4de..00000000 --- a/pydis_site/apps/api/models/utils.py +++ /dev/null @@ -1,17 +0,0 @@ -from operator import itemgetter - - -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})>' -- cgit v1.2.3 From a9fb4ac8213c8131a8a6f7f339d2ba6b341e6cdb Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Tue, 14 Jul 2020 14:29:27 +0200 Subject: Add a mixin for adding created and updated times. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/models/__init__.py | 2 +- pydis_site/apps/api/models/mixins.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 644b8757..1c9e1d07 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -15,4 +15,4 @@ from .bot import ( User ) from .log_entry import LogEntry -from .mixins import ModelReprMixin +from .mixins import ModelReprMixin, ModelTimestampMixin diff --git a/pydis_site/apps/api/models/mixins.py b/pydis_site/apps/api/models/mixins.py index 0540c4de..942edaa1 100644 --- a/pydis_site/apps/api/models/mixins.py +++ b/pydis_site/apps/api/models/mixins.py @@ -1,5 +1,7 @@ from operator import itemgetter +from django.db import models + class ModelReprMixin: """Mixin providing a `__repr__()` to display model class name and initialisation parameters.""" @@ -15,3 +17,13 @@ class ModelReprMixin: 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: + abstract = True -- cgit v1.2.3 From ee743f622dd0ba6f8c0b9801b1db1d85d60fa697 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Tue, 14 Jul 2020 15:48:34 +0200 Subject: Add the AllowList model and serializer. This is the model which we will use for items that are either blacklisted or whitelisted. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/models/__init__.py | 1 + pydis_site/apps/api/models/bot/__init__.py | 1 + pydis_site/apps/api/models/bot/allowlist.py | 28 ++++++++++++++++++++++++++++ pydis_site/apps/api/serializers.py | 12 +++++++++++- 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 pydis_site/apps/api/models/bot/allowlist.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 1c9e1d07..04d0fc50 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa from .bot import ( + AllowList, BotSetting, DocumentationLink, DeletedMessage, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index 8ae47746..b373ee84 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -1,4 +1,5 @@ # flake8: noqa +from .allowlist import AllowList from .bot_setting import BotSetting from .deleted_message import DeletedMessage from .documentation_link import DocumentationLink diff --git a/pydis_site/apps/api/models/bot/allowlist.py b/pydis_site/apps/api/models/bot/allowlist.py new file mode 100644 index 00000000..c8fa2e33 --- /dev/null +++ b/pydis_site/apps/api/models/bot/allowlist.py @@ -0,0 +1,28 @@ +from django.db import models + +from pydis_site.apps.api.models import ModelReprMixin, ModelTimestampMixin + + +class AllowList(ModelTimestampMixin, ModelReprMixin, models.Model): + """An item that is either allowed or denied.""" + + AllowListType = models.TextChoices( + 'guild_invite_id', + 'file_format', + 'domain_name', + 'word_watchlist', + ) + type = models.CharField( + max_length=50, + help_text=( + "The type of allowlist this is on. The value must be one of the following: " + f"{','.join(AllowListType.choices)}." + ), + choices=AllowListType.choices, + ) + allowed = models.BooleanField( + help_text="Whether this item is on the allowlist or the denylist." + ) + content = models.TextField( + help_text="The data to add to the allowlist." + ) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index f2d5144c..24ba0ec0 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -8,7 +8,7 @@ from .models import ( DocumentationLink, Infraction, LogEntry, MessageDeletionContext, Nomination, OffTopicChannelName, - OffensiveMessage, + OffensiveMessage, AllowList, Reminder, Role, Tag, User ) @@ -97,6 +97,16 @@ class DocumentationLinkSerializer(ModelSerializer): fields = ('package', 'base_url', 'inventory_url') +class AllowListSerializer(ModelSerializer): + """A class providing (de-)serialization of `AllowList` instances.""" + + class Meta: + """Metadata defined for the Django REST Framework.""" + + model = AllowList + fields = ('created_at', 'updated_at', 'type', 'allowed', 'content') + + class InfractionSerializer(ModelSerializer): """A class providing (de-)serialization of `Infraction` instances.""" -- cgit v1.2.3 From fbd00e184e60f21630e25a3bcc36c46f05f44bae Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 14 Jul 2020 13:29:10 -0700 Subject: Merge migrations --- pydis_site/apps/api/migrations/0055_merge_20200714_2027.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0055_merge_20200714_2027.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0055_merge_20200714_2027.py b/pydis_site/apps/api/migrations/0055_merge_20200714_2027.py new file mode 100644 index 00000000..f2a0e638 --- /dev/null +++ b/pydis_site/apps/api/migrations/0055_merge_20200714_2027.py @@ -0,0 +1,14 @@ +# Generated by Django 3.0.8 on 2020-07-14 20:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0051_allow_blank_message_embeds'), + ('api', '0054_user_invalidate_unknown_role'), + ] + + operations = [ + ] -- cgit v1.2.3 From 9208e0ac7943ce4ce14957f49c1597fb723cbd95 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Tue, 14 Jul 2020 13:37:19 -0700 Subject: Allow empty list for user roles This is the same issue as 145beb37fcb4fa2f487f18b234dd72bc4e10c279. See that commit for more information. --- .../api/migrations/0056_allow_blank_user_roles.py | 21 +++++++++++++++++++++ pydis_site/apps/api/models/bot/user.py | 1 + 2 files changed, 22 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py b/pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py new file mode 100644 index 00000000..489941c7 --- /dev/null +++ b/pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py @@ -0,0 +1,21 @@ +# Generated by Django 3.0.8 on 2020-07-14 20:35 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models +import pydis_site.apps.api.models.bot.user + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0055_merge_20200714_2027'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='roles', + field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.'), pydis_site.apps.api.models.bot.user._validate_existing_role]), blank=True, default=list, help_text='IDs of roles the user has on the server', size=None), + ), + ] diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index bff4d642..0d8c574a 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -52,6 +52,7 @@ class User(ModelReprMixin, models.Model): ) ), default=list, + blank=True, help_text="IDs of roles the user has on the server" ) in_guild = models.BooleanField( -- cgit v1.2.3 From 3293eec240a0a2c7bfd8116d75484794d3a6210e Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Tue, 14 Jul 2020 23:04:27 +0200 Subject: Removes django_crispy_bulma dependency. --- Pipfile | 1 - Pipfile.lock | 419 ++++++++++++------------- pydis_site/apps/home/forms/account_deletion.py | 7 +- pydis_site/settings.py | 3 +- 4 files changed, 203 insertions(+), 227 deletions(-) (limited to 'pydis_site') diff --git a/Pipfile b/Pipfile index ef05d090..0f8872ea 100644 --- a/Pipfile +++ b/Pipfile @@ -13,7 +13,6 @@ djangorestframework = "~=3.9.2" djangorestframework-bulk = "~=0.2.1" psycopg2-binary = "~=2.8" django-simple-bulma = ">=1.1.7,<2.0" -django-crispy-bulma = ">=0.1.2,<2.0" whitenoise = "==4.1.2" requests = "~=2.21" pygments = "~=2.3.1" diff --git a/Pipfile.lock b/Pipfile.lock index bc0187bb..3d35f4c3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "59427be022c8717e155b979772436843a4c1e37e2915874a6ee19957e9a0d141" + "sha256": "809dbb7f9b5383ead59aa1706b755eb60149cc9c1160c8516f2d4f54a193a482" }, "pipfile-spec": 6, "requires": { @@ -25,10 +25,10 @@ }, "certifi": { "hashes": [ - "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", - "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" + "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", + "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" ], - "version": "==2020.4.5.1" + "version": "==2020.6.20" }, "chardet": { "hashes": [ @@ -46,18 +46,18 @@ }, "django": { "hashes": [ - "sha256:84f370f6acedbe1f3c41e1a02de44ac206efda3355e427139ecb785b5f596d80", - "sha256:e8fe3c2b2212dce6126becab7a693157f1a441a07b62ec994c046c76af5bb66d" + "sha256:edf0ecf6657713b0435b6757e6069466925cae70d634a3283c96b80c01e06191", + "sha256:f2250bd35d0f6c23e930c544629934144e5dd39a4c06092e1050c731c1712ba8" ], "index": "pypi", - "version": "==2.2.13" + "version": "==2.2.14" }, "django-allauth": { "hashes": [ - "sha256:7ab91485b80d231da191d5c7999ba93170ef1bf14ab6487d886598a1ad03e1d8" + "sha256:f17209410b7f87da0a84639fd79d3771b596a6d3fc1a8e48ce50dabc7f441d30" ], "index": "pypi", - "version": "==0.41.0" + "version": "==0.42.0" }, "django-classy-tags": { "hashes": [ @@ -65,14 +65,6 @@ ], "version": "==1.0.0" }, - "django-crispy-bulma": { - "hashes": [ - "sha256:6ce8f6df87442040fbba39baede7aba7026d71359376e3ce7eecb7695b09c02b", - "sha256:72a61a1ed87611c87347553f57879dd05e21b4cedffaf9c676971488a0f065b2" - ], - "index": "pypi", - "version": "==0.2" - }, "django-crispy-forms": { "hashes": [ "sha256:5952bab971110d0b86c278132dae0aa095beee8f723e625c3d3fa28888f1675f", @@ -157,17 +149,17 @@ }, "html5lib": { "hashes": [ - "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", - "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736" + "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", + "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f" ], - "version": "==1.0.1" + "version": "==1.1" }, "idna": { "hashes": [ - "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", - "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "version": "==2.9" + "version": "==2.10" }, "libsass": { "hashes": [ @@ -203,68 +195,70 @@ }, "pillow": { "hashes": [ - "sha256:04766c4930c174b46fd72d450674612ab44cca977ebbcc2dde722c6933290107", - "sha256:0e2a3bceb0fd4e0cb17192ae506d5f082b309ffe5fc370a5667959c9b2f85fa3", - "sha256:0f01e63c34f0e1e2580cc0b24e86a5ccbbfa8830909a52ee17624c4193224cd9", - "sha256:12e4bad6bddd8546a2f9771485c7e3d2b546b458ae8ff79621214119ac244523", - "sha256:1f694e28c169655c50bb89a3fa07f3b854d71eb47f50783621de813979ba87f3", - "sha256:3d25dd8d688f7318dca6d8cd4f962a360ee40346c15893ae3b95c061cdbc4079", - "sha256:4b02b9c27fad2054932e89f39703646d0c543f21d3cc5b8e05434215121c28cd", - "sha256:9744350687459234867cbebfe9df8f35ef9e1538f3e729adbd8fde0761adb705", - "sha256:a0b49960110bc6ff5fead46013bcb8825d101026d466f3a4de3476defe0fb0dd", - "sha256:ae2b270f9a0b8822b98655cb3a59cdb1bd54a34807c6c56b76dd2e786c3b7db3", - "sha256:b37bb3bd35edf53125b0ff257822afa6962649995cbdfde2791ddb62b239f891", - "sha256:b532bcc2f008e96fd9241177ec580829dee817b090532f43e54074ecffdcd97f", - "sha256:b67a6c47ed963c709ed24566daa3f95a18f07d3831334da570c71da53d97d088", - "sha256:b943e71c2065ade6fef223358e56c167fc6ce31c50bc7a02dd5c17ee4338e8ac", - "sha256:ccc9ad2460eb5bee5642eaf75a0438d7f8887d484490d5117b98edd7f33118b7", - "sha256:d23e2aa9b969cf9c26edfb4b56307792b8b374202810bd949effd1c6e11ebd6d", - "sha256:eaa83729eab9c60884f362ada982d3a06beaa6cc8b084cf9f76cae7739481dfa", - "sha256:ee94fce8d003ac9fd206496f2707efe9eadcb278d94c271f129ab36aa7181344", - "sha256:f455efb7a98557412dc6f8e463c1faf1f1911ec2432059fa3e582b6000fc90e2", - "sha256:f46e0e024346e1474083c729d50de909974237c72daca05393ee32389dabe457", - "sha256:f54be399340aa602066adb63a86a6a5d4f395adfdd9da2b9a0162ea808c7b276", - "sha256:f784aad988f12c80aacfa5b381ec21fd3f38f851720f652b9f33facc5101cf4d" - ], - "version": "==7.1.2" + "sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f", + "sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8", + "sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad", + "sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f", + "sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae", + "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d", + "sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5", + "sha256:52125833b070791fcb5710fabc640fc1df07d087fc0c0f02d3661f76c23c5b8b", + "sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8", + "sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233", + "sha256:6d7741e65835716ceea0fd13a7d0192961212fd59e741a46bbed7a473c634ed6", + "sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727", + "sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f", + "sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38", + "sha256:94cf49723928eb6070a892cb39d6c156f7b5a2db4e8971cb958f7b6b104fb4c4", + "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626", + "sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d", + "sha256:a060cf8aa332052df2158e5a119303965be92c3da6f2d93b6878f0ebca80b2f6", + "sha256:c79f9c5fb846285f943aafeafda3358992d64f0ef58566e23484132ecd8d7d63", + "sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f", + "sha256:d08b23fdb388c0715990cbc06866db554e1822c4bdcf6d4166cf30ac82df8c41", + "sha256:d350f0f2c2421e65fbc62690f26b59b0bcda1b614beb318c81e38647e0f673a1", + "sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d", + "sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9", + "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a", + "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce" + ], + "version": "==7.2.0" }, "psycopg2-binary": { "hashes": [ - "sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29", - "sha256:086f7e89ec85a6704db51f68f0dcae432eff9300809723a6e8782c41c2f48e03", - "sha256:18ca813fdb17bc1db73fe61b196b05dd1ca2165b884dd5ec5568877cabf9b039", - "sha256:19dc39616850342a2a6db70559af55b22955f86667b5f652f40c0e99253d9881", - "sha256:2166e770cb98f02ed5ee2b0b569d40db26788e0bf2ec3ae1a0d864ea6f1d8309", - "sha256:3a2522b1d9178575acee4adf8fd9f979f9c0449b00b4164bb63c3475ea6528ed", - "sha256:3aa773580f85a28ffdf6f862e59cb5a3cc7ef6885121f2de3fca8d6ada4dbf3b", - "sha256:3b5deaa3ee7180585a296af33e14c9b18c218d148e735c7accf78130765a47e3", - "sha256:407af6d7e46593415f216c7f56ba087a9a42bd6dc2ecb86028760aa45b802bd7", - "sha256:4c3c09fb674401f630626310bcaf6cd6285daf0d5e4c26d6e55ca26a2734e39b", - "sha256:4c6717962247445b4f9e21c962ea61d2e884fc17df5ddf5e35863b016f8a1f03", - "sha256:50446fae5681fc99f87e505d4e77c9407e683ab60c555ec302f9ac9bffa61103", - "sha256:5057669b6a66aa9ca118a2a860159f0ee3acf837eda937bdd2a64f3431361a2d", - "sha256:5dd90c5438b4f935c9d01fcbad3620253da89d19c1f5fca9158646407ed7df35", - "sha256:659c815b5b8e2a55193ede2795c1e2349b8011497310bb936da7d4745652823b", - "sha256:69b13fdf12878b10dc6003acc8d0abf3ad93e79813fd5f3812497c1c9fb9be49", - "sha256:7a1cb80e35e1ccea3e11a48afe65d38744a0e0bde88795cc56a4d05b6e4f9d70", - "sha256:7e6e3c52e6732c219c07bd97fff6c088f8df4dae3b79752ee3a817e6f32e177e", - "sha256:7f42a8490c4fe854325504ce7a6e4796b207960dabb2cbafe3c3959cb00d1d7e", - "sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e", - "sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103", - "sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6", - "sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1", - "sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9", - "sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e", - "sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f", - "sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd", - "sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8", - "sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f", - "sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4", - "sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964", - "sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08" - ], - "index": "pypi", - "version": "==2.8.4" + "sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac", + "sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a", + "sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5", + "sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04", + "sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1", + "sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5", + "sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce", + "sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434", + "sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9", + "sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057", + "sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98", + "sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522", + "sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505", + "sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa", + "sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3", + "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f", + "sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4", + "sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4", + "sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266", + "sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66", + "sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38", + "sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3", + "sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389", + "sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab", + "sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb", + "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6", + "sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d", + "sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162", + "sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e", + "sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd" + ], + "index": "pypi", + "version": "==2.8.5" }, "pygments": { "hashes": [ @@ -276,10 +270,10 @@ }, "python3-openid": { "hashes": [ - "sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa", - "sha256:628d365d687e12da12d02c6691170f4451db28d6d68d050007e4a40065868502" + "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf", + "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b" ], - "version": "==3.1.0" + "version": "==3.2.0" }, "pytz": { "hashes": [ @@ -290,61 +284,47 @@ }, "pyuwsgi": { "hashes": [ - "sha256:15a4626740753b0d0dfeeac7d367f9b2e89ab6af16c195927e60f75359fc1bbc", - "sha256:24c40c3b889eb9f283d43feffbc0f7c7fc024e914451425156ddb68af3df1e71", - "sha256:393737bd43a7e38f0a4a1601a37a69c4bf893635b37665ff958170fdb604fdb7", - "sha256:5a08308f87e639573c1efaa5966a6d04410cd45a73c4586a932fe3ee4b56369d", - "sha256:5f4b36c0dbb9931c4da8008aa423158be596e3b4a23cec95a958631603a94e45", - "sha256:7c31794f71bbd0ccf542cab6bddf38aa69e84e31ae0f9657a2e18ebdc150c01a", - "sha256:802ec6dad4b6707b934370926ec1866603abe31ba03c472f56149001b3533ba1", - "sha256:814d73d4569add69a6c19bb4a27cd5adb72b196e5e080caed17dbda740402072", - "sha256:829299cd117cf8abe837796bf587e61ce6bfe18423a3a1c510c21e9825789c2c", - "sha256:85f2210ceae5f48b7d8fad2240d831f4b890cac85cd98ca82683ac6aa481dfc8", - "sha256:861c94442b28cd64af033e88e0f63c66dbd5609f67952dc18694098b47a43f3a", - "sha256:957bc6316ffc8463795d56d9953d58e7f32aa5aad1c5ac80bc45c69f3299961e", - "sha256:9760c3f56fb5f15852d163429096600906478e9ed2c189a52f2bb21d8a2a986c", - "sha256:9fdfb98a2992de01e8efad2aeed22c825e36db628b144b2d6b93d81fb549f811", - "sha256:a4b24703ea818196d0be1dc64b3b57b79c67e8dee0cfa207a4216220912035a7", - "sha256:ad7f4968c1ddbf139a306d9b075360d959cc554d994ba5e1f512af9a40e62357", - "sha256:b1127d34b90f74faf1707718c57a4193ac028b9f4aec0238638983132297d456", - "sha256:bcb04d6ec644b3e08d03c64851e06edd7110489261e50627a4bcadf66ff6920e", - "sha256:bebfebb9ee83d7cf37668bf54275b677b7ae283e84a944f9f3ac6a4b66f95d4b", - "sha256:c29892dafc65a8b6eb95823fa4bac7754ca3fd1c28ab8d2a973289531b340a27", - "sha256:cb296b50b51ba022b0090b28d032ff1dd395a6db03672b65a39e83532edad527", - "sha256:ce777ebdf49ce736fc04abf555b5c41ab3f130127543a689dcf8d4871cd18fe4", - "sha256:d8b4bf930b6a19bc9ee982b9163d948c87501ad91b71516924e8ed25fe85d2ee", - "sha256:e2a420f2c4d35f3ec0b7e752a80d7bd385e2c5a64f67c05f2d2d74230e3114b6", - "sha256:ef5eb630f541af6b69378d58594be90a0922fa6d6a50a9248c25b9502585f6bf", - "sha256:fed899ce96f4f2b4d1b9f338dd145a4040ee1d8a5152213af0dd8d4a4d36e9fe" + "sha256:1a4dd8d99b8497f109755e09484b0bd2aeaa533f7621e7c7e2a120a72111219d", + "sha256:206937deaebbac5c87692657c3151a5a9d40ecbc9b051b94154205c50a48e963", + "sha256:2cf35d9145208cc7c96464d688caa3de745bfc969e1a1ae23cb046fc10b0ac7e", + "sha256:3ab84a168633eeb55847d59475d86e9078d913d190c2a1aed804c562a10301a3", + "sha256:430406d1bcf288a87f14fde51c66877eaf5e98516838a1c6f761af5d814936fc", + "sha256:72be25ce7aa86c5616c59d12c2961b938e7bde47b7ff6a996ff83b89f7c5cd27", + "sha256:aa4d615de430e2066a1c76d9cc2a70abf2dfc703a82c21aee625b445866f2c3b", + "sha256:aadd231256a672cf4342ef9fb976051949e4d5b616195e696bcb7b8a9c07789e", + "sha256:b15ee6a7759b0465786d856334b8231d882deda5291cf243be6a343a8f3ef910", + "sha256:bd1d0a8d4cb87eb63417a72e6b1bac47053f9b0be550adc6d2a375f4cbaa22f0", + "sha256:d5787779ec24b67ac8898be9dc2b2b4e35f17d79f14361f6cf303d6283a848f2", + "sha256:ecfae85d6504e0ecbba100a795032a88ce8f110b62b93243f2df1bd116eca67f" ], "index": "pypi", "markers": "sys_platform != 'win32'", - "version": "==2.0.18.post0" + "version": "==2.0.19.1" }, "pyyaml": { "hashes": [ - "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", - "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", - "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", - "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", - "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", - "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", - "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", - "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", - "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", - "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", - "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" ], "index": "pypi", - "version": "==5.3" + "version": "==5.3.1" }, "requests": { "hashes": [ - "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", - "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", + "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" ], "index": "pypi", - "version": "==2.23.0" + "version": "==2.24.0" }, "requests-oauthlib": { "hashes": [ @@ -355,11 +335,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:480eee754e60bcae983787a9a13bc8f155a111aef199afaa4f289d6a76aa622a", - "sha256:a920387dc3ee252a66679d0afecd34479fb6fc52c2bc20763793ed69e5b0dcc0" + "sha256:2f023ff348359ec5f0b73a840e8b08e6a8d3b2613a98c57d11c222ef43879237", + "sha256:380a280cfc7c4ade5912294e6d9aa71ce776b5fca60a3782e9331b0bcd2866bf" ], "index": "pypi", - "version": "==0.14.2" + "version": "==0.16.1" }, "six": { "hashes": [ @@ -444,53 +424,50 @@ }, "coverage": { "hashes": [ - "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3", - "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c", - "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0", - "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477", - "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a", - "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf", - "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691", - "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73", - "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987", - "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894", - "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e", - "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef", - "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf", - "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68", - "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8", - "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954", - "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2", - "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40", - "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc", - "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc", - "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e", - "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d", - "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f", - "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc", - "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301", - "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea", - "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb", - "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af", - "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52", - "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37", - "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0" - ], - "index": "pypi", - "version": "==5.0.3" + "sha256:0fc4e0d91350d6f43ef6a61f64a48e917637e1dcfcba4b4b7d543c628ef82c2d", + "sha256:10f2a618a6e75adf64329f828a6a5b40244c1c50f5ef4ce4109e904e69c71bd2", + "sha256:12eaccd86d9a373aea59869bc9cfa0ab6ba8b1477752110cb4c10d165474f703", + "sha256:1874bdc943654ba46d28f179c1846f5710eda3aeb265ff029e0ac2b52daae404", + "sha256:1dcebae667b73fd4aa69237e6afb39abc2f27520f2358590c1b13dd90e32abe7", + "sha256:1e58fca3d9ec1a423f1b7f2aa34af4f733cbfa9020c8fe39ca451b6071237405", + "sha256:214eb2110217f2636a9329bc766507ab71a3a06a8ea30cdeebb47c24dce5972d", + "sha256:25fe74b5b2f1b4abb11e103bb7984daca8f8292683957d0738cd692f6a7cc64c", + "sha256:32ecee61a43be509b91a526819717d5e5650e009a8d5eda8631a59c721d5f3b6", + "sha256:3740b796015b889e46c260ff18b84683fa2e30f0f75a171fb10d2bf9fb91fc70", + "sha256:3b2c34690f613525672697910894b60d15800ac7e779fbd0fccf532486c1ba40", + "sha256:41d88736c42f4a22c494c32cc48a05828236e37c991bd9760f8923415e3169e4", + "sha256:42fa45a29f1059eda4d3c7b509589cc0343cd6bbf083d6118216830cd1a51613", + "sha256:4bb385a747e6ae8a65290b3df60d6c8a692a5599dc66c9fa3520e667886f2e10", + "sha256:509294f3e76d3f26b35083973fbc952e01e1727656d979b11182f273f08aa80b", + "sha256:5c74c5b6045969b07c9fb36b665c9cac84d6c174a809fc1b21bdc06c7836d9a0", + "sha256:60a3d36297b65c7f78329b80120f72947140f45b5c7a017ea730f9112b40f2ec", + "sha256:6f91b4492c5cde83bfe462f5b2b997cdf96a138f7c58b1140f05de5751623cf1", + "sha256:7403675df5e27745571aba1c957c7da2dacb537c21e14007ec3a417bf31f7f3d", + "sha256:87bdc8135b8ee739840eee19b184804e5d57f518578ffc797f5afa2c3c297913", + "sha256:8a3decd12e7934d0254939e2bf434bf04a5890c5bf91a982685021786a08087e", + "sha256:9702e2cb1c6dec01fb8e1a64c015817c0800a6eca287552c47a5ee0ebddccf62", + "sha256:a4d511012beb967a39580ba7d2549edf1e6865a33e5fe51e4dce550522b3ac0e", + "sha256:bbb387811f7a18bdc61a2ea3d102be0c7e239b0db9c83be7bfa50f095db5b92a", + "sha256:bfcc811883699ed49afc58b1ed9f80428a18eb9166422bce3c31a53dba00fd1d", + "sha256:c32aa13cc3fe86b0f744dfe35a7f879ee33ac0a560684fef0f3e1580352b818f", + "sha256:ca63dae130a2e788f2b249200f01d7fa240f24da0596501d387a50e57aa7075e", + "sha256:d54d7ea74cc00482a2410d63bf10aa34ebe1c49ac50779652106c867f9986d6b", + "sha256:d67599521dff98ec8c34cd9652cbcfe16ed076a2209625fca9dc7419b6370e5c", + "sha256:d82db1b9a92cb5c67661ca6616bdca6ff931deceebb98eecbd328812dab52032", + "sha256:d9ad0a988ae20face62520785ec3595a5e64f35a21762a57d115dae0b8fb894a", + "sha256:ebf2431b2d457ae5217f3a1179533c456f3272ded16f8ed0b32961a6d90e38ee", + "sha256:ed9a21502e9223f563e071759f769c3d6a2e1ba5328c31e86830368e8d78bc9c", + "sha256:f50632ef2d749f541ca8e6c07c9928a37f87505ce3a9f20c8446ad310f1aa87b" + ], + "index": "pypi", + "version": "==5.2" }, "distlib": { "hashes": [ - "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" + "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", + "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" ], - "version": "==0.3.0" - }, - "entrypoints": { - "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" - ], - "version": "==0.3" + "version": "==0.3.1" }, "filelock": { "hashes": [ @@ -501,19 +478,19 @@ }, "flake8": { "hashes": [ - "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", - "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" + "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c", + "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208" ], "index": "pypi", - "version": "==3.7.9" + "version": "==3.8.3" }, "flake8-annotations": { "hashes": [ - "sha256:a38b44d01abd480586a92a02a2b0a36231ec42dcc5e114de78fa5db016d8d3f9", - "sha256:d5b0e8704e4e7728b352fa1464e23539ff2341ba11cc153b536fa2cf921ee659" + "sha256:7816a5d8f65ffdf37b8e21e5b17e0fd1e492aa92638573276de066e889a22b26", + "sha256:8d18db74a750dd97f40b483cc3ef80d07d03f687525bad8fd83365dcd3bfd414" ], "index": "pypi", - "version": "==2.0.1" + "version": "==2.3.0" }, "flake8-bandit": { "hashes": [ @@ -563,11 +540,11 @@ }, "flake8-tidy-imports": { "hashes": [ - "sha256:8aa34384b45137d4cf33f5818b8e7897dc903b1d1e10a503fa7dd193a9a710ba", - "sha256:b26461561bcc80e8012e46846630ecf0aaa59314f362a94cb7800dfdb32fa413" + "sha256:62059ca07d8a4926b561d392cbab7f09ee042350214a25cf12823384a45d27dd", + "sha256:c30b40337a2e6802ba3bb611c26611154a27e94c53fc45639e3e282169574fd3" ], "index": "pypi", - "version": "==4.0.0" + "version": "==4.1.0" }, "flake8-todo": { "hashes": [ @@ -585,25 +562,25 @@ }, "gitpython": { "hashes": [ - "sha256:e107af4d873daed64648b4f4beb89f89f0cfbe3ef558fc7821ed2331c2f8da1a", - "sha256:ef1d60b01b5ce0040ad3ec20bc64f783362d41fa0822a2742d3586e1f49bb8ac" + "sha256:2db287d71a284e22e5c2846042d0602465c7434d910406990d5b74df4afb0858", + "sha256:fa3b92da728a457dd75d62bb5f3eb2816d99a7fe6c67398e260637a40e3fafb5" ], - "version": "==3.1.3" + "version": "==3.1.7" }, "identify": { "hashes": [ - "sha256:249ebc7e2066d6393d27c1b1be3b70433f824a120b1d8274d362f1eb419e3b52", - "sha256:781fd3401f5d2b17b22a8b18b493a48d5d948e3330634e82742e23f9c20234ef" + "sha256:882c4b08b4569517b5f2257ecca180e01f38400a17f429f5d0edff55530c41c7", + "sha256:f89add935982d5bc62913ceee16c9297d8ff14b226e9d3072383a4e38136b656" ], - "version": "==1.4.19" + "version": "==1.4.23" }, "importlib-metadata": { "hashes": [ - "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545", - "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958" + "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83", + "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070" ], "markers": "python_version < '3.8'", - "version": "==1.6.1" + "version": "==1.7.0" }, "mccabe": { "hashes": [ @@ -628,26 +605,26 @@ }, "pep8-naming": { "hashes": [ - "sha256:45f330db8fcfb0fba57458c77385e288e7a3be1d01e8ea4268263ef677ceea5f", - "sha256:a33d38177056321a167decd6ba70b890856ba5025f0a8eca6a3eda607da93caf" + "sha256:a1dd47dd243adfe8a83616e27cf03164960b507530f155db94e10b36a6cd6724", + "sha256:f43bfe3eea7e0d73e8b5d07d6407ab47f2476ccaeff6937c84275cd30b016738" ], "index": "pypi", - "version": "==0.9.1" + "version": "==0.11.1" }, "pre-commit": { "hashes": [ - "sha256:09ebe467f43ce24377f8c2f200fe3cd2570d328eb2ce0568c8e96ce19da45fa6", - "sha256:f8d555e31e2051892c7f7b3ad9f620bd2c09271d87e9eedb2ad831737d6211eb" + "sha256:1657663fdd63a321a4a739915d7d03baedd555b25054449090f97bb0cb30a915", + "sha256:e8b1315c585052e729ab7e99dcca5698266bedce9067d21dc909c23e3ceed626" ], "index": "pypi", - "version": "==2.1.1" + "version": "==2.6.0" }, "pycodestyle": { "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", + "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" ], - "version": "==2.5.0" + "version": "==2.6.0" }, "pydocstyle": { "hashes": [ @@ -658,27 +635,27 @@ }, "pyflakes": { "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", + "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], - "version": "==2.1.1" + "version": "==2.2.0" }, "pyyaml": { "hashes": [ - "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", - "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", - "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", - "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", - "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", - "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", - "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", - "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", - "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", - "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", - "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" ], "index": "pypi", - "version": "==5.3" + "version": "==5.3.1" }, "six": { "hashes": [ @@ -703,10 +680,10 @@ }, "stevedore": { "hashes": [ - "sha256:001e90cd704be6470d46cc9076434e2d0d566c1379187e7013eb296d3a6032d9", - "sha256:471c920412265cc809540ae6fb01f3f02aba89c79bbc7091372f4745a50f9691" + "sha256:79270bd5fb4a052e76932e9fef6e19afa77090c4000f2680eb8c2e887d2e6e36", + "sha256:9fb12884b510fdc25f8a883bb390b8ff82f67863fb360891a33135bcb2ce8c54" ], - "version": "==2.0.0" + "version": "==3.1.0" }, "toml": { "hashes": [ @@ -752,10 +729,10 @@ }, "virtualenv": { "hashes": [ - "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf", - "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70" + "sha256:c11a475400e98450403c0364eb3a2d25d42f71cf1493da64390487b666de4324", + "sha256:e10cc66f40cbda459720dfe1d334c4dc15add0d80f09108224f171006a97a172" ], - "version": "==20.0.21" + "version": "==20.0.26" }, "zipp": { "hashes": [ diff --git a/pydis_site/apps/home/forms/account_deletion.py b/pydis_site/apps/home/forms/account_deletion.py index 17ffe5c1..9498a341 100644 --- a/pydis_site/apps/home/forms/account_deletion.py +++ b/pydis_site/apps/home/forms/account_deletion.py @@ -1,7 +1,6 @@ from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout +from crispy_forms.layout import Layout, Submit, Field from django.forms import CharField, Form -from django_crispy_bulma.layout import IconField, Submit class AccountDeletionForm(Form): @@ -12,10 +11,10 @@ class AccountDeletionForm(Form): self.helper = FormHelper() self.helper.form_method = "post" - self.helper.add_input(Submit("submit", "I understand, delete my account")) + self.helper.add_input(Submit("submit", "I understand, delete my account", css_class='button is-primary')) self.helper.layout = Layout( - IconField("username", icon_prepend="user") + Field("username") ) username = CharField( diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 5f80a414..4487a9d3 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -52,6 +52,8 @@ if DEBUG: 'api.pythondiscord.local', 'admin.pythondiscord.local', 'staff.pythondiscord.local', + '0.0.0.0', + 'localhost', 'web', 'api.web', 'admin.web', @@ -105,7 +107,6 @@ INSTALLED_APPS = [ 'allauth.socialaccount.providers.github', 'crispy_forms', - 'django_crispy_bulma', 'django_hosts', 'django_filters', 'django_nyt.apps.DjangoNytConfig', -- cgit v1.2.3 From 11503b660d2a4249edde02a50889ebae270f07f4 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 15 Jul 2020 00:06:25 +0200 Subject: Removes django_crispy_forms dependency, too. --- Pipfile | 1 - Pipfile.lock | 10 +--------- pydis_site/apps/home/forms/account_deletion.py | 14 -------------- pydis_site/settings.py | 13 ------------- pydis_site/templates/home/account/delete.html | 9 ++++++--- 5 files changed, 7 insertions(+), 40 deletions(-) (limited to 'pydis_site') diff --git a/Pipfile b/Pipfile index 0f8872ea..ab77e824 100644 --- a/Pipfile +++ b/Pipfile @@ -5,7 +5,6 @@ verify_ssl = true [packages] django = "~=2.2.13" -django-crispy-forms = "~=1.7.2" django-environ = "~=0.4.5" django-filter = "~=2.1.0" django-hosts = "~=3.0" diff --git a/Pipfile.lock b/Pipfile.lock index 3d35f4c3..3166b224 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "809dbb7f9b5383ead59aa1706b755eb60149cc9c1160c8516f2d4f54a193a482" + "sha256": "009be40262ffc13460a05cefe0e66c41c4c14ed9ea7aca9854ae7bddeeba5da9" }, "pipfile-spec": 6, "requires": { @@ -65,14 +65,6 @@ ], "version": "==1.0.0" }, - "django-crispy-forms": { - "hashes": [ - "sha256:5952bab971110d0b86c278132dae0aa095beee8f723e625c3d3fa28888f1675f", - "sha256:705ededc554ad8736157c666681165fe22ead2dec0d5446d65fc9dd976a5a876" - ], - "index": "pypi", - "version": "==1.7.2" - }, "django-environ": { "hashes": [ "sha256:6c9d87660142608f63ec7d5ce5564c49b603ea8ff25da595fd6098f6dc82afde", diff --git a/pydis_site/apps/home/forms/account_deletion.py b/pydis_site/apps/home/forms/account_deletion.py index 9498a341..b2160657 100644 --- a/pydis_site/apps/home/forms/account_deletion.py +++ b/pydis_site/apps/home/forms/account_deletion.py @@ -1,22 +1,8 @@ -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Submit, Field from django.forms import CharField, Form class AccountDeletionForm(Form): """Account deletion form, to collect username for confirmation of removal.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.helper = FormHelper() - - self.helper.form_method = "post" - self.helper.add_input(Submit("submit", "I understand, delete my account", css_class='button is-primary')) - - self.helper.layout = Layout( - Field("username") - ) - username = CharField( label="Username", required=True diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 4487a9d3..206bec7d 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -106,7 +106,6 @@ INSTALLED_APPS = [ 'allauth.socialaccount.providers.discord', 'allauth.socialaccount.providers.github', - 'crispy_forms', 'django_hosts', 'django_filters', 'django_nyt.apps.DjangoNytConfig', @@ -289,7 +288,6 @@ LOGGING = { } # Django Messages framework config - MESSAGE_TAGS = { messages.DEBUG: 'primary', messages.INFO: 'info', @@ -298,17 +296,6 @@ MESSAGE_TAGS = { messages.ERROR: 'danger', } -# Custom settings for Crispyforms -CRISPY_ALLOWED_TEMPLATE_PACKS = ( - "bootstrap", - "uni_form", - "bootstrap3", - "bootstrap4", - "bulma", -) - -CRISPY_TEMPLATE_PACK = "bulma" - # Custom settings for django-simple-bulma BULMA_SETTINGS = { "variables": { # If you update these colours, please update the notification.css file diff --git a/pydis_site/templates/home/account/delete.html b/pydis_site/templates/home/account/delete.html index 1020a82b..0d44e32a 100644 --- a/pydis_site/templates/home/account/delete.html +++ b/pydis_site/templates/home/account/delete.html @@ -1,6 +1,4 @@ {% extends 'base/base.html' %} - -{% load crispy_forms_tags %} {% load static %} {% block title %}Delete Account{% endblock %} @@ -36,7 +34,12 @@
- {% crispy form %} +
+ {% csrf_token %} + + + +
-- cgit v1.2.3 From 4df7239adb60114f833e5c22eacff726b61e1d5a Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 15 Jul 2020 00:11:51 +0200 Subject: Fix linting issues caused by refactor. --- pydis_site/apps/home/forms/account_deletion.py | 1 + pydis_site/settings.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/home/forms/account_deletion.py b/pydis_site/apps/home/forms/account_deletion.py index b2160657..eec70bea 100644 --- a/pydis_site/apps/home/forms/account_deletion.py +++ b/pydis_site/apps/home/forms/account_deletion.py @@ -3,6 +3,7 @@ from django.forms import CharField, Form class AccountDeletionForm(Form): """Account deletion form, to collect username for confirmation of removal.""" + username = CharField( label="Username", required=True diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 206bec7d..2c87007c 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -52,7 +52,7 @@ if DEBUG: 'api.pythondiscord.local', 'admin.pythondiscord.local', 'staff.pythondiscord.local', - '0.0.0.0', + '0.0.0.0', # noqa: S104 'localhost', 'web', 'api.web', -- cgit v1.2.3 From cb5416bf54e512a66d36b72bff6f7b12a1c8cacd Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 15 Jul 2020 13:10:44 +0200 Subject: Allowlist viewset. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/viewsets/bot/allowlist.py | 54 +++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 pydis_site/apps/api/viewsets/bot/allowlist.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/viewsets/bot/allowlist.py b/pydis_site/apps/api/viewsets/bot/allowlist.py new file mode 100644 index 00000000..9b907d05 --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/allowlist.py @@ -0,0 +1,54 @@ +from rest_framework.viewsets import ModelViewSet + +from pydis_site.apps.api.models.bot.allowlist import AllowList +from pydis_site.apps.api.serializers import AllowListSerializer + + +class AllowListViewSet(ModelViewSet): + """ + View providing CRUD operations on items whitelisted or blacklisted by our bot. + + ## Routes + ### GET /bot/allowlists + Returns all allowlist items in the database. + + #### Response format + >>> [ + ... { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... }, + ... ... + ... ] + + #### Status codes + - 200: returned on success + + ### POST /bot/allowedlists + Adds a single allowedlist item to the database. + + #### Request body + >>> { + ... 'type': str, + ... 'allowed': bool, + ... 'content': str, + ... } + + #### Status codes + - 201: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/allowedlists/ + 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 = AllowListSerializer + queryset = AllowList.objects.all() -- cgit v1.2.3 From 0a73c2a019cd973c00cefbcf239a0bc7ca947cfa Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 15 Jul 2020 14:27:51 +0200 Subject: Minor fixes for imports and __init__ files. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/migrations/0007_tag.py | 2 +- pydis_site/apps/api/migrations/0009_snakefact.py | 2 +- pydis_site/apps/api/migrations/0010_snakeidiom.py | 2 +- pydis_site/apps/api/migrations/0012_specialsnake.py | 2 +- pydis_site/apps/api/migrations/0018_messagedeletioncontext.py | 2 +- pydis_site/apps/api/migrations/0019_deletedmessage.py | 2 +- pydis_site/apps/api/migrations/0020_infraction.py | 2 +- pydis_site/apps/api/migrations/0030_reminder.py | 2 +- pydis_site/apps/api/migrations/0031_nomination.py | 2 +- pydis_site/apps/api/migrations/0032_botsetting.py | 2 +- pydis_site/apps/api/migrations/0035_create_table_log_entry.py | 2 +- pydis_site/apps/api/models/__init__.py | 1 - pydis_site/apps/api/viewsets/__init__.py | 1 + pydis_site/apps/api/viewsets/bot/__init__.py | 1 + 14 files changed, 13 insertions(+), 12 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0007_tag.py b/pydis_site/apps/api/migrations/0007_tag.py index c22715f9..b6d146fe 100644 --- a/pydis_site/apps/api/migrations/0007_tag.py +++ b/pydis_site/apps/api/migrations/0007_tag.py @@ -18,6 +18,6 @@ class Migration(migrations.Migration): ('title', models.CharField(help_text='The title of this tag, shown in searches and providing a quick overview over what this embed contains.', max_length=100, primary_key=True, serialize=False)), ('embed', django.contrib.postgres.fields.jsonb.JSONField(help_text='The actual embed shown by this tag.')), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0009_snakefact.py b/pydis_site/apps/api/migrations/0009_snakefact.py index 4fc63bc9..fd583846 100644 --- a/pydis_site/apps/api/migrations/0009_snakefact.py +++ b/pydis_site/apps/api/migrations/0009_snakefact.py @@ -16,6 +16,6 @@ class Migration(migrations.Migration): fields=[ ('fact', models.CharField(help_text='A fact about snakes.', max_length=200, primary_key=True, serialize=False)), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0010_snakeidiom.py b/pydis_site/apps/api/migrations/0010_snakeidiom.py index be089cf4..7d06ce5f 100644 --- a/pydis_site/apps/api/migrations/0010_snakeidiom.py +++ b/pydis_site/apps/api/migrations/0010_snakeidiom.py @@ -16,6 +16,6 @@ class Migration(migrations.Migration): fields=[ ('idiom', models.CharField(help_text='A snake idiom', max_length=140, primary_key=True, serialize=False)), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0012_specialsnake.py b/pydis_site/apps/api/migrations/0012_specialsnake.py index 77072526..ed0c1563 100644 --- a/pydis_site/apps/api/migrations/0012_specialsnake.py +++ b/pydis_site/apps/api/migrations/0012_specialsnake.py @@ -17,6 +17,6 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=140, primary_key=True, serialize=False)), ('info', models.TextField()), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py b/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py index dced1288..7e372d04 100644 --- a/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py +++ b/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py @@ -19,6 +19,6 @@ class Migration(migrations.Migration): ('creation', models.DateTimeField(help_text='When this deletion took place.')), ('actor', models.ForeignKey(help_text='The original actor causing this deletion. Could be the author of a manual clean command invocation, the bot when executing automatic actions, or nothing to indicate that the bulk deletion was not issued by us.', null=True, on_delete=django.db.models.deletion.CASCADE, to='api.User')), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0019_deletedmessage.py b/pydis_site/apps/api/migrations/0019_deletedmessage.py index 4b028f0c..33746253 100644 --- a/pydis_site/apps/api/migrations/0019_deletedmessage.py +++ b/pydis_site/apps/api/migrations/0019_deletedmessage.py @@ -25,6 +25,6 @@ class Migration(migrations.Migration): options={ 'abstract': False, }, - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0020_infraction.py b/pydis_site/apps/api/migrations/0020_infraction.py index 6bef6b77..96c71687 100644 --- a/pydis_site/apps/api/migrations/0020_infraction.py +++ b/pydis_site/apps/api/migrations/0020_infraction.py @@ -25,6 +25,6 @@ class Migration(migrations.Migration): ('actor', models.ForeignKey(help_text='The user which applied the infraction.', on_delete=django.db.models.deletion.CASCADE, related_name='infractions_given', to='api.User')), ('user', models.ForeignKey(help_text='The user to which the infraction was applied.', on_delete=django.db.models.deletion.CASCADE, related_name='infractions_received', to='api.User')), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0030_reminder.py b/pydis_site/apps/api/migrations/0030_reminder.py index 8c42f6dc..e1f1afc3 100644 --- a/pydis_site/apps/api/migrations/0030_reminder.py +++ b/pydis_site/apps/api/migrations/0030_reminder.py @@ -22,6 +22,6 @@ class Migration(migrations.Migration): ('expiration', models.DateTimeField(help_text='When this reminder should be sent.')), ('author', models.ForeignKey(help_text='The creator of this reminder.', on_delete=django.db.models.deletion.CASCADE, to='api.User')), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0031_nomination.py b/pydis_site/apps/api/migrations/0031_nomination.py index 75e69701..f39436c1 100644 --- a/pydis_site/apps/api/migrations/0031_nomination.py +++ b/pydis_site/apps/api/migrations/0031_nomination.py @@ -21,6 +21,6 @@ class Migration(migrations.Migration): ('inserted_at', models.DateTimeField(auto_now_add=True, help_text='The creation date of this nomination.')), ('author', models.ForeignKey(help_text='The staff member that nominated this user.', on_delete=django.db.models.deletion.CASCADE, related_name='nomination_set', to='api.User')), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0032_botsetting.py b/pydis_site/apps/api/migrations/0032_botsetting.py index 25186a2b..3304edef 100644 --- a/pydis_site/apps/api/migrations/0032_botsetting.py +++ b/pydis_site/apps/api/migrations/0032_botsetting.py @@ -18,6 +18,6 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=50, primary_key=True, serialize=False)), ('data', django.contrib.postgres.fields.jsonb.JSONField(help_text='The actual settings of this setting.')), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/migrations/0035_create_table_log_entry.py b/pydis_site/apps/api/migrations/0035_create_table_log_entry.py index a8256a0e..c9a1ad19 100644 --- a/pydis_site/apps/api/migrations/0035_create_table_log_entry.py +++ b/pydis_site/apps/api/migrations/0035_create_table_log_entry.py @@ -24,6 +24,6 @@ class Migration(migrations.Migration): ('line', models.PositiveSmallIntegerField(help_text='The line at which the log line was emitted.')), ('message', models.TextField(help_text='The textual content of the log line.')), ], - bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model), + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), ] diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 04d0fc50..2839fbba 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -16,4 +16,3 @@ from .bot import ( User ) from .log_entry import LogEntry -from .mixins import ModelReprMixin, ModelTimestampMixin diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py index 3cf9f641..98d3d586 100644 --- a/pydis_site/apps/api/viewsets/__init__.py +++ b/pydis_site/apps/api/viewsets/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa from .bot import ( + AllowListViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py index b3e0fa4d..86bfc910 100644 --- a/pydis_site/apps/api/viewsets/bot/__init__.py +++ b/pydis_site/apps/api/viewsets/bot/__init__.py @@ -1,4 +1,5 @@ # flake8: noqa +from .allowlist import AllowListViewSet from .bot_setting import BotSettingViewSet from .deleted_message import DeletedMessageViewSet from .documentation_link import DocumentationLinkViewSet -- cgit v1.2.3 From d03ac5fbf06bc3749e68a606601c0b793f1f0766 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 15 Jul 2020 14:29:32 +0200 Subject: Set up url forwarding for the viewset. https://github.com/python-discord/site/issues/305 --- Pipfile.lock | 31 +---------------------------- pydis_site/apps/api/models/bot/allowlist.py | 16 +++++++-------- pydis_site/apps/api/urls.py | 21 ++++++++++++++----- 3 files changed, 24 insertions(+), 44 deletions(-) (limited to 'pydis_site') diff --git a/Pipfile.lock b/Pipfile.lock index 097c4f81..9cd105f5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -21,7 +21,6 @@ "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a", "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed" ], - "markers": "python_version >= '3.5'", "version": "==3.2.10" }, "bleach": { @@ -29,7 +28,6 @@ "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f", "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==3.1.5" }, "certifi": { @@ -51,7 +49,6 @@ "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==0.6.0" }, "django": { @@ -111,7 +108,6 @@ "sha256:90eb236eb4f1a92124bd7c37852bbe09c0d21158477cc237556d59842a91c509", "sha256:dfdb3af75ad27cdd4458b0544ec8574174f2b90f99bc2cafab6a15b4bc1895a8" ], - "markers": "python_version >= '3.5'", "version": "==0.11.0" }, "django-nyt": { @@ -155,7 +151,6 @@ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, "importlib-metadata": { @@ -189,7 +184,6 @@ "sha256:1fafe3f1ecabfb514a5285fca634a53c1b32a81cb0feb154264d55bf2ff22c17", "sha256:c467cd6233885534bf0fe96e62e3cf46cfc1605112356c4f9981512b8174de59" ], - "markers": "python_version >= '3.5'", "version": "==3.2.2" }, "oauthlib": { @@ -197,7 +191,6 @@ "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==3.1.0" }, "packaging": { @@ -205,7 +198,6 @@ "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.4" }, "pillow": { @@ -237,7 +229,6 @@ "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a", "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce" ], - "markers": "python_version >= '3.5'", "version": "==7.2.0" }, "psycopg2-binary": { @@ -289,7 +280,6 @@ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.4.7" }, "python3-openid": { @@ -353,8 +343,7 @@ "requests-oauthlib": { "hashes": [ "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", - "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", - "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" + "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" ], "version": "==1.3.0" }, @@ -371,7 +360,6 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "sorl-thumbnail": { @@ -379,7 +367,6 @@ "sha256:66771521f3c0ed771e1ce8e1aaf1639ebff18f7f5a40cfd3083da8f0fe6c7c99", "sha256:7162639057dff222a651bacbdb6bd6f558fc32946531d541fc71e10c0167ebdf" ], - "markers": "python_version >= '3.4'", "version": "==12.6.3" }, "sqlparse": { @@ -387,7 +374,6 @@ "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.3.1" }, "urllib3": { @@ -395,7 +381,6 @@ "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", "version": "==1.25.9" }, "webencodings": { @@ -426,7 +411,6 @@ "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "markers": "python_version >= '3.6'", "version": "==3.1.0" } }, @@ -443,7 +427,6 @@ "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==19.3.0" }, "bandit": { @@ -458,7 +441,6 @@ "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53", "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513" ], - "markers": "python_full_version >= '3.6.1'", "version": "==3.1.0" }, "coverage": { @@ -597,7 +579,6 @@ "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" ], - "markers": "python_version >= '3.4'", "version": "==4.0.5" }, "gitpython": { @@ -605,7 +586,6 @@ "sha256:2db287d71a284e22e5c2846042d0602465c7434d910406990d5b74df4afb0858", "sha256:fa3b92da728a457dd75d62bb5f3eb2816d99a7fe6c67398e260637a40e3fafb5" ], - "markers": "python_version >= '3.4'", "version": "==3.1.7" }, "identify": { @@ -613,7 +593,6 @@ "sha256:882c4b08b4569517b5f2257ecca180e01f38400a17f429f5d0edff55530c41c7", "sha256:f89add935982d5bc62913ceee16c9297d8ff14b226e9d3072383a4e38136b656" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.4.23" }, "importlib-metadata": { @@ -666,7 +645,6 @@ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.6.0" }, "pydocstyle": { @@ -674,7 +652,6 @@ "sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586", "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5" ], - "markers": "python_version >= '3.5'", "version": "==5.0.2" }, "pyflakes": { @@ -682,7 +659,6 @@ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.2.0" }, "pyyaml": { @@ -707,7 +683,6 @@ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.15.0" }, "smmap": { @@ -715,7 +690,6 @@ "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==3.0.4" }, "snowballstemmer": { @@ -730,7 +704,6 @@ "sha256:79270bd5fb4a052e76932e9fef6e19afa77090c4000f2680eb8c2e887d2e6e36", "sha256:9fb12884b510fdc25f8a883bb390b8ff82f67863fb360891a33135bcb2ce8c54" ], - "markers": "python_version >= '3.6'", "version": "==3.1.0" }, "toml": { @@ -780,7 +753,6 @@ "sha256:c11a475400e98450403c0364eb3a2d25d42f71cf1493da64390487b666de4324", "sha256:e10cc66f40cbda459720dfe1d334c4dc15add0d80f09108224f171006a97a172" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.0.26" }, "zipp": { @@ -788,7 +760,6 @@ "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "markers": "python_version >= '3.6'", "version": "==3.1.0" } } diff --git a/pydis_site/apps/api/models/bot/allowlist.py b/pydis_site/apps/api/models/bot/allowlist.py index c8fa2e33..b0aea066 100644 --- a/pydis_site/apps/api/models/bot/allowlist.py +++ b/pydis_site/apps/api/models/bot/allowlist.py @@ -1,23 +1,21 @@ from django.db import models -from pydis_site.apps.api.models import ModelReprMixin, ModelTimestampMixin +from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin class AllowList(ModelTimestampMixin, ModelReprMixin, models.Model): """An item that is either allowed or denied.""" AllowListType = models.TextChoices( - 'guild_invite_id', - 'file_format', - 'domain_name', - 'word_watchlist', + 'AllowListType', + 'GUILD_INVITE_ID ' + 'FILE_FORMAT ' + 'DOMAIN_NAME ' + 'WORD_WATCHLIST ' ) type = models.CharField( max_length=50, - help_text=( - "The type of allowlist this is on. The value must be one of the following: " - f"{','.join(AllowListType.choices)}." - ), + help_text="The type of allowlist this is on.", choices=AllowListType.choices, ) allowed = models.BooleanField( diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index 3bb5198e..bf41f09f 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -3,17 +3,28 @@ from rest_framework.routers import DefaultRouter from .views import HealthcheckView, RulesView from .viewsets import ( - BotSettingViewSet, DeletedMessageViewSet, - DocumentationLinkViewSet, InfractionViewSet, - LogEntryViewSet, NominationViewSet, + AllowListViewSet, + BotSettingViewSet, + DeletedMessageViewSet, + DocumentationLinkViewSet, + InfractionViewSet, + LogEntryViewSet, + NominationViewSet, OffTopicChannelNameViewSet, - OffensiveMessageViewSet, ReminderViewSet, - RoleViewSet, TagViewSet, UserViewSet + OffensiveMessageViewSet, + ReminderViewSet, + RoleViewSet, + TagViewSet, + UserViewSet ) # http://www.django-rest-framework.org/api-guide/routers/#defaultrouter bot_router = DefaultRouter(trailing_slash=False) +bot_router.register( + 'allowlists', + AllowListViewSet +) bot_router.register( 'bot-settings', BotSettingViewSet -- cgit v1.2.3 From 787b317b4617df7931c96697126b26ea7e2a217d Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 15 Jul 2020 14:29:54 +0200 Subject: Add a migration for the new AllowList model. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/migrations/0057_allowlist.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0057_allowlist.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0057_allowlist.py b/pydis_site/apps/api/migrations/0057_allowlist.py new file mode 100644 index 00000000..7d815e91 --- /dev/null +++ b/pydis_site/apps/api/migrations/0057_allowlist.py @@ -0,0 +1,29 @@ +# Generated by Django 3.0.8 on 2020-07-15 11:23 + +from django.db import migrations, models +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0056_allow_blank_user_roles'), + ] + + operations = [ + migrations.CreateModel( + name='AllowList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('type', models.CharField(choices=[('GUILD_INVITE_ID', 'Guild Invite Id'), ('FILE_FORMAT', 'File Format'), ('DOMAIN_NAME', 'Domain Name'), ('WORD_WATCHLIST', 'Word Watchlist')], help_text='The type of allowlist this is on.', max_length=50)), + ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), + ('content', models.TextField(help_text='The data to add to the allowlist.')), + ], + options={ + 'abstract': False, + }, + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + ] -- cgit v1.2.3 From e8a32c717babee132626d6574f7ca706338739dc Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 15 Jul 2020 15:32:23 +0200 Subject: Add a UniqueConstraint to prevent duplicates. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/migrations/0057_allowlist.py | 29 ----------------- .../migrations/0057_create_new_allowlist_model.py | 37 ++++++++++++++++++++++ pydis_site/apps/api/models/bot/allowlist.py | 13 ++++++++ 3 files changed, 50 insertions(+), 29 deletions(-) delete mode 100644 pydis_site/apps/api/migrations/0057_allowlist.py create mode 100644 pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0057_allowlist.py b/pydis_site/apps/api/migrations/0057_allowlist.py deleted file mode 100644 index 7d815e91..00000000 --- a/pydis_site/apps/api/migrations/0057_allowlist.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.0.8 on 2020-07-15 11:23 - -from django.db import migrations, models -import pydis_site.apps.api.models.mixins - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0056_allow_blank_user_roles'), - ] - - operations = [ - migrations.CreateModel( - name='AllowList', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('type', models.CharField(choices=[('GUILD_INVITE_ID', 'Guild Invite Id'), ('FILE_FORMAT', 'File Format'), ('DOMAIN_NAME', 'Domain Name'), ('WORD_WATCHLIST', 'Word Watchlist')], help_text='The type of allowlist this is on.', max_length=50)), - ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), - ('content', models.TextField(help_text='The data to add to the allowlist.')), - ], - options={ - 'abstract': False, - }, - bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), - ), - ] diff --git a/pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py b/pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py new file mode 100644 index 00000000..45650d86 --- /dev/null +++ b/pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py @@ -0,0 +1,37 @@ +# Generated by Django 3.0.8 on 2020-07-15 11:23 + +from django.db import migrations, models +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0056_allow_blank_user_roles'), + ] + + operations = [ + migrations.CreateModel( + name='AllowList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('type', models.CharField(choices=[('GUILD_INVITE_ID', 'Guild Invite Id'), ('FILE_FORMAT', 'File Format'), ('DOMAIN_NAME', 'Domain Name'), ('WORD_WATCHLIST', 'Word Watchlist')], help_text='The type of allowlist this is on.', max_length=50)), + ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), + ('content', models.TextField(help_text='The data to add to the allowlist.')), + ], + options={ + 'abstract': False, + }, + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + migrations.AlterModelTable( + name='allowlist', + table='allow_list', + ), + migrations.AddConstraint( + model_name='allowlist', + constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_allowlist'), + ) + ] diff --git a/pydis_site/apps/api/models/bot/allowlist.py b/pydis_site/apps/api/models/bot/allowlist.py index b0aea066..fc57ef32 100644 --- a/pydis_site/apps/api/models/bot/allowlist.py +++ b/pydis_site/apps/api/models/bot/allowlist.py @@ -24,3 +24,16 @@ class AllowList(ModelTimestampMixin, ModelReprMixin, models.Model): content = models.TextField( help_text="The data to add to the allowlist." ) + + class Meta: + """Metaconfig for this model.""" + + db_table = 'allow_list' + + # This constraint ensures only one allowlist with the same content + # can exist per type.This means that we cannot have both an allow + # and a deny for the same item, and we cannot have duplicates of the + # same item. + constraints = [ + models.UniqueConstraint(fields=['content', 'type'], name='unique_allowlist'), + ] -- cgit v1.2.3 From 90f325fe94af4a771ea4ed0ddfc5a074d0e88c07 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 15 Jul 2020 15:34:30 +0200 Subject: Return id from the AllowListSerializer. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/serializers.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 24ba0ec0..13030074 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -4,13 +4,20 @@ from rest_framework.validators import UniqueTogetherValidator from rest_framework_bulk import BulkSerializerMixin from .models import ( - BotSetting, DeletedMessage, - DocumentationLink, Infraction, - LogEntry, MessageDeletionContext, - Nomination, OffTopicChannelName, - OffensiveMessage, AllowList, - Reminder, Role, - Tag, User + AllowList, + BotSetting, + DeletedMessage, + DocumentationLink, + Infraction, + LogEntry, + MessageDeletionContext, + Nomination, + OffTopicChannelName, + OffensiveMessage, + Reminder, + Role, + Tag, + User, ) @@ -104,7 +111,7 @@ class AllowListSerializer(ModelSerializer): """Metadata defined for the Django REST Framework.""" model = AllowList - fields = ('created_at', 'updated_at', 'type', 'allowed', 'content') + fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content') class InfractionSerializer(ModelSerializer): -- cgit v1.2.3 From 9fdfdc2a0867aac7fe271412ae41c9349df77180 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Wed, 15 Jul 2020 14:19:41 -0700 Subject: Bump jQuery version The Django wiki updated the jQuery version, so our reference needs to be updated too. Otherwise, we will get a 404 for the script. --- pydis_site/templates/wiki/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/templates/wiki/base.html b/pydis_site/templates/wiki/base.html index 9f904324..846492ab 100644 --- a/pydis_site/templates/wiki/base.html +++ b/pydis_site/templates/wiki/base.html @@ -7,7 +7,7 @@ {% block head %} {{ block.super }} - + -- cgit v1.2.3 From 2c88104434ad1af68877959059ef04a69eae5c33 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 16 Jul 2020 10:38:14 +0800 Subject: Add mentions field to Reminder model --- pydis_site/apps/api/models/bot/reminder.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/models/bot/reminder.py b/pydis_site/apps/api/models/bot/reminder.py index d53fedb5..4b5d15ca 100644 --- a/pydis_site/apps/api/models/bot/reminder.py +++ b/pydis_site/apps/api/models/bot/reminder.py @@ -1,3 +1,4 @@ +from django.contrib.postgres.fields import ArrayField from django.core.validators import MinValueValidator from django.db import models @@ -45,6 +46,19 @@ class Reminder(ModelReprMixin, models.Model): expiration = models.DateTimeField( help_text="When this reminder should be sent." ) + mentions = ArrayField( + models.BigIntegerField( + validators=( + MinValueValidator( + limit_value=0, + message="Mention IDs cannot be negative." + ), + ) + ), + default=list, + blank=True, + help_text="IDs of roles or users to ping with the reminder." + ) def __str__(self): """Returns some info on the current reminder, for display purposes.""" -- cgit v1.2.3 From cdbda598fafccdd4ef0a324e7b18ce070aaf2d70 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 16 Jul 2020 10:38:58 +0800 Subject: Return mentions from ReminderSerializer --- pydis_site/apps/api/serializers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index f2d5144c..80e552a6 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -203,7 +203,9 @@ class ReminderSerializer(ModelSerializer): """Metadata defined for the Django REST Framework.""" model = Reminder - fields = ('active', 'author', 'jump_url', 'channel_id', 'content', 'expiration', 'id') + fields = ( + 'active', 'author', 'jump_url', 'channel_id', 'content', 'expiration', 'id', 'mentions' + ) class RoleSerializer(ModelSerializer): -- cgit v1.2.3 From 1e72079691fb1acf61390edad496736d332362ca Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 16 Jul 2020 10:39:23 +0800 Subject: Document mentions in ReminderViewSet --- pydis_site/apps/api/viewsets/bot/reminder.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py index 147f6dbc..5a44b7d3 100644 --- a/pydis_site/apps/api/viewsets/bot/reminder.py +++ b/pydis_site/apps/api/viewsets/bot/reminder.py @@ -27,6 +27,11 @@ class ReminderViewSet( ... { ... 'active': True, ... 'author': 1020103901030, + ... 'mentions': [ + ... 336843820513755157, + ... 165023948638126080, + ... 267628507062992896 + ... ], ... 'content': "Make dinner", ... 'expiration': '5018-11-20T15:52:00Z', ... 'id': 11 -- cgit v1.2.3 From 02412121e8272d35a03c30a80a4a67e6aabdf0eb Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 16 Jul 2020 10:40:03 +0800 Subject: Add migration for the mentions field in the Reminder model --- .../apps/api/migrations/0055_reminder_mentions.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0055_reminder_mentions.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0055_reminder_mentions.py b/pydis_site/apps/api/migrations/0055_reminder_mentions.py new file mode 100644 index 00000000..d73b450d --- /dev/null +++ b/pydis_site/apps/api/migrations/0055_reminder_mentions.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.14 on 2020-07-15 07:37 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0054_user_invalidate_unknown_role'), + ] + + operations = [ + migrations.AddField( + model_name='reminder', + name='mentions', + field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Mention IDs cannot be negative.')]), blank=True, default=list, help_text='IDs of roles or users to ping with the reminder.', size=None), + ), + ] -- cgit v1.2.3 From 4c288387097dcb65feadce360857c9fbe1b2593f Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 16 Jul 2020 10:46:56 +0800 Subject: Document POSTing mentions in ReminderViewSet --- pydis_site/apps/api/viewsets/bot/reminder.py | 1 + 1 file changed, 1 insertion(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py index 5a44b7d3..b31330a0 100644 --- a/pydis_site/apps/api/viewsets/bot/reminder.py +++ b/pydis_site/apps/api/viewsets/bot/reminder.py @@ -48,6 +48,7 @@ class ReminderViewSet( #### Request body >>> { ... 'author': int, + ... 'mentions': List[int], ... 'content': str, ... 'expiration': str # ISO-formatted datetime ... } -- cgit v1.2.3 From 5cc5f5e2c7d4a6fa5b74aa33a6dbc7ffcf4bcc99 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 16 Jul 2020 10:52:21 +0800 Subject: Document PATCH for reminders --- pydis_site/apps/api/viewsets/bot/reminder.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py index b31330a0..940d19d4 100644 --- a/pydis_site/apps/api/viewsets/bot/reminder.py +++ b/pydis_site/apps/api/viewsets/bot/reminder.py @@ -58,6 +58,22 @@ class ReminderViewSet( - 400: if the body format is invalid - 404: if no user with the given ID could be found + ### PATCH /bot/reminders/ + Update the user with the given `id`. + All fields in the request body are optional. + + #### Request body + >>> { + ... 'mentions': List[int], + ... 'content': str, + ... 'expiration': str # ISO-formatted datetime + ... } + + #### Status codes + - 200: returned on success + - 400: if the body format is invalid + - 404: if no user with the given ID could be found + ### DELETE /bot/reminders/ Delete the reminder with the given `id`. -- cgit v1.2.3 From 806b27bb30ceb32603bd90d31e32ae30bf8f499e Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 16 Jul 2020 14:11:35 +0800 Subject: Document more undocumented stuff --- pydis_site/apps/api/viewsets/bot/reminder.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py index 940d19d4..f4921d44 100644 --- a/pydis_site/apps/api/viewsets/bot/reminder.py +++ b/pydis_site/apps/api/viewsets/bot/reminder.py @@ -34,7 +34,9 @@ class ReminderViewSet( ... ], ... 'content': "Make dinner", ... 'expiration': '5018-11-20T15:52:00Z', - ... 'id': 11 + ... 'id': 11, + ... 'channel_id': 634547009956872193, + ... 'jump_url': "https://discord.com/channels///" ... }, ... ... ... ] @@ -50,7 +52,9 @@ class ReminderViewSet( ... 'author': int, ... 'mentions': List[int], ... 'content': str, - ... 'expiration': str # ISO-formatted datetime + ... 'expiration': str, # ISO-formatted datetime + ... 'channel_id': int, + ... 'jump_url': Optional[str] ... } #### Status codes -- cgit v1.2.3 From 18f055f9f9922033a4ef4172c8fa1eaa9cb2f4cd Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 16 Jul 2020 15:08:28 +0800 Subject: Add mentions field to valid data test --- pydis_site/apps/api/tests/test_reminders.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py index c7fa07c9..5042ea90 100644 --- a/pydis_site/apps/api/tests/test_reminders.py +++ b/pydis_site/apps/api/tests/test_reminders.py @@ -4,7 +4,7 @@ from django.forms.models import model_to_dict from django_hosts.resolvers import reverse from .base import APISubdomainTestCase -from ..models import Reminder, User +from ..models import Reminder, Role, User class UnauthedReminderAPITests(APISubdomainTestCase): @@ -54,6 +54,18 @@ class ReminderCreationTests(APISubdomainTestCase): name='Mermaid Man', discriminator=1234, ) + cls.user = User.objects.create( + id=5678, + name='Fish Dude', + discriminator=5678, + ) + cls.role = Role.objects.create( + id=555, + name="Random role", + colour=2, + permissions=0b01010010101, + position=10, + ) def test_accepts_valid_data(self): data = { @@ -62,6 +74,7 @@ class ReminderCreationTests(APISubdomainTestCase): 'expiration': datetime.utcnow().isoformat(), 'jump_url': "https://www.google.com", 'channel_id': 123, + 'mentions': [self.user.id, self.role.id], } url = reverse('bot:reminder-list', host='api') response = self.client.post(url, data=data) -- cgit v1.2.3 From fbcf39f2bb05b9746ac911d25c2118ac9cf9dff3 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Thu, 16 Jul 2020 15:52:02 +0800 Subject: Merge migrations --- pydis_site/apps/api/migrations/0057_merge_20200716_0751.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0057_merge_20200716_0751.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0057_merge_20200716_0751.py b/pydis_site/apps/api/migrations/0057_merge_20200716_0751.py new file mode 100644 index 00000000..47a6d2d4 --- /dev/null +++ b/pydis_site/apps/api/migrations/0057_merge_20200716_0751.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.14 on 2020-07-16 07:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0055_reminder_mentions'), + ('api', '0056_allow_blank_user_roles'), + ] + + operations = [ + ] -- cgit v1.2.3 From 4470afc5b48fa1c71ed717372acad5414e6ec0ad Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 12:59:43 +0200 Subject: Fix a bug in an old migration. https://github.com/python-discord/site/issues/305 --- .../api/migrations/0025_allow_custom_inserted_at_infraction_field.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py b/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py index 0c02cb91..9589346d 100644 --- a/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py +++ b/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py @@ -1,7 +1,7 @@ # Generated by Django 2.1.4 on 2019-01-06 16:01 -import datetime from django.db import migrations, models +from django.utils import timezone class Migration(migrations.Migration): @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='infraction', name='inserted_at', - field=models.DateTimeField(default=datetime.datetime.utcnow, help_text='The date and time of the creation of this infraction.'), + field=models.DateTimeField(default=timezone.now(), help_text='The date and time of the creation of this infraction.'), ), ] -- cgit v1.2.3 From e3c750a3fdd1beee441c4d62b0c5fe8644255ea6 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 13:00:21 +0200 Subject: Add tests for the AllowList model. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/tests/test_allowlists.py | 104 +++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 pydis_site/apps/api/tests/test_allowlists.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py new file mode 100644 index 00000000..c6004439 --- /dev/null +++ b/pydis_site/apps/api/tests/test_allowlists.py @@ -0,0 +1,104 @@ +from django_hosts.resolvers import reverse + +from .base import APISubdomainTestCase +from ..models import AllowList + +URL = reverse('bot:allowlist-list', host='api') +JPEG_ALLOWLIST = { + "type": 'FILE_FORMAT', + "allowed": True, + "content": ".jpeg", +} +PNG_ALLOWLIST = { + "type": 'FILE_FORMAT', + "allowed": True, + "content": ".png", +} + + +class UnauthenticatedTests(APISubdomainTestCase): + def setUp(self): + super().setUp() + self.client.force_authenticate(user=None) + + def test_cannot_read_allowedlist_list(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 401) + + +class EmptyDatabaseTests(APISubdomainTestCase): + def test_returns_empty_object(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), []) + + +class FetchTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.jpeg_format = AllowList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = AllowList.objects.create(**PNG_ALLOWLIST) + + def test_returns_name_in_list(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()[0]["content"], self.jpeg_format.content) + self.assertEqual(response.json()[1]["content"], self.png_format.content) + + def test_returns_single_item_by_id(self): + response = self.client.get(f'{URL}/{self.jpeg_format.id}') + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json().get("content"), self.jpeg_format.content) + + +class CreationTests(APISubdomainTestCase): + def test_returns_400_for_missing_params(self): + no_type_json = { + "allowed": True, + "content": ".jpeg" + } + no_allowed_json = { + "type": "FILE_FORMAT", + "content": ".jpeg" + } + no_content_json = { + "allowed": True, + "type": "FILE_FORMAT" + } + cases = [{}, no_type_json, no_allowed_json, no_content_json] + + for case in cases: + response = self.client.post(URL, data=case) + self.assertEqual(response.status_code, 400) + + def test_returns_201_for_successful_creation(self): + response = self.client.post(URL, data=JPEG_ALLOWLIST) + self.assertEqual(response.status_code, 201) + + +class DeletionTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.jpeg_format = AllowList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = AllowList.objects.create(**PNG_ALLOWLIST) + + def test_deleting_unknown_id_returns_404(self): + response = self.client.delete(f"{URL}/200") + + self.assertEqual(response.status_code, 404) + + def test_deleting_known_id_returns_204(self): + response = self.client.delete(f"{URL}/{self.jpeg_format.id}") + + self.assertEqual(response.status_code, 204) + + def test_name_gets_deleted(self): + response = self.client.delete(f"{URL}/{self.png_format.id}") + self.assertEqual(response.status_code, 204) + + response = self.client.get(f"{URL}/{self.png_format.id}") + self.assertNotIn(self.png_format.content, response.json()) -- cgit v1.2.3 From ce3d207a65a888e30e55448d7c902475a03906d3 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 13:27:08 +0200 Subject: Improve some docstrings. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/models/mixins.py | 2 ++ pydis_site/apps/api/viewsets/bot/allowlist.py | 30 +++++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/models/mixins.py b/pydis_site/apps/api/models/mixins.py index 942edaa1..5d75b78b 100644 --- a/pydis_site/apps/api/models/mixins.py +++ b/pydis_site/apps/api/models/mixins.py @@ -26,4 +26,6 @@ class ModelTimestampMixin(models.Model): updated_at = models.DateTimeField(auto_now=True) class Meta: + """Metaconfig for the mixin.""" + abstract = True diff --git a/pydis_site/apps/api/viewsets/bot/allowlist.py b/pydis_site/apps/api/viewsets/bot/allowlist.py index 9b907d05..7cc82ff7 100644 --- a/pydis_site/apps/api/viewsets/bot/allowlist.py +++ b/pydis_site/apps/api/viewsets/bot/allowlist.py @@ -27,27 +27,45 @@ class AllowListViewSet(ModelViewSet): #### Status codes - 200: returned on success + - 401: returned if unauthenticated + + ### GET /bot/allowlists/ + Returns a specific AllowList item from the database. + + #### Response format + >>> { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... } + + #### Status codes + - 200: returned on success + - 404: returned if the id was not found. ### POST /bot/allowedlists Adds a single allowedlist item to the database. #### Request body >>> { - ... 'type': str, - ... 'allowed': bool, - ... 'content': str, + ... 'type': str, + ... 'allowed': bool, + ... 'content': str, ... } #### Status codes - 201: returned on success - 400: if one of the given fields is invalid - ### DELETE /bot/allowedlists/ - Deletes the tag with the given `title`. + ### DELETE /bot/allowedlists/ + Deletes the tag with the given `id`. #### Status codes - 204: returned on success - - 404: if a tag with the given `title` does not exist + - 404: if a tag with the given `id` does not exist """ serializer_class = AllowListSerializer -- cgit v1.2.3 From e2aabf684433af3b08079182179fcd964ead8a71 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 20:23:26 +0200 Subject: Fix some broken tests. The test_utils_account.py tests were never running, because the folder they were in had no __init__.py file. The test_models.py file was failing because it had an outdated import of the ModelReprMixin, which has moved to a new file. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/tests/test_models.py | 4 ++-- pydis_site/tests/__init__.py | 0 pydis_site/tests/test_utils_account.py | 11 +++++------ 3 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 pydis_site/tests/__init__.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index b4754484..e0e347bb 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -3,13 +3,12 @@ from datetime import datetime as dt from django.test import SimpleTestCase from django.utils import timezone -from ..models import ( +from pydis_site.apps.api.models import ( DeletedMessage, DocumentationLink, Infraction, Message, MessageDeletionContext, - ModelReprMixin, Nomination, OffTopicChannelName, OffensiveMessage, @@ -18,6 +17,7 @@ from ..models import ( Tag, User ) +from pydis_site.apps.api.models.mixins import ModelReprMixin class SimpleClass(ModelReprMixin): diff --git a/pydis_site/tests/__init__.py b/pydis_site/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pydis_site/tests/test_utils_account.py b/pydis_site/tests/test_utils_account.py index e8db7b64..3edca658 100644 --- a/pydis_site/tests/test_utils_account.py +++ b/pydis_site/tests/test_utils_account.py @@ -5,7 +5,7 @@ from allauth.socialaccount.models import SocialAccount, SocialLogin from django.contrib.auth.models import User from django.contrib.messages.storage.base import BaseStorage from django.http import HttpRequest -from django.test import TestCase +from django.test import RequestFactory, TestCase from pydis_site.apps.api.models import Role, User as DiscordUser from pydis_site.utils.account import AccountAdapter, SocialAccountAdapter @@ -74,6 +74,8 @@ class AccountUtilsTests(TestCase): self.discord_user_two_roles.roles.append(developers_role.id) + self.request_factory = RequestFactory() + def test_account_adapter(self): """Test that our Allauth account adapter functions correctly.""" adapter = AccountAdapter() @@ -86,12 +88,11 @@ class AccountUtilsTests(TestCase): discord_login = SocialLogin(account=self.discord_account) discord_login_role = SocialLogin(account=self.discord_account_role) - discord_login_two_roles = SocialLogin(account=self.discord_account_two_roles) discord_login_not_present = SocialLogin(account=self.discord_account_not_present) github_login = SocialLogin(account=self.github_account) - messages_request = HttpRequest() + messages_request = self.request_factory.get("/") messages_request._messages = BaseStorage(messages_request) with patch("pydis_site.utils.account.reverse") as mock_reverse: @@ -112,8 +113,6 @@ class AccountUtilsTests(TestCase): self.assertEqual(mock_redirect.call_count, 4) self.assertEqual(mock_reverse.call_count, 4) - self.assertTrue(adapter.is_open_for_signup(HttpRequest(), discord_login_two_roles)) - def test_social_account_adapter_populate(self): """Test that our Allauth social account adapter correctly handles data population.""" adapter = SocialAccountAdapter() @@ -126,7 +125,7 @@ class AccountUtilsTests(TestCase): discord_login.account.extra_data["discriminator"] = "0000" user = adapter.populate_user( - HttpRequest(), discord_login, + self.request_factory.get("/"), discord_login, {"username": "user"} ) -- cgit v1.2.3 From 76cd687715e49cee97bac24f5d3c8ca40ffca099 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 20:48:56 +0200 Subject: Rename AllowList to AllowDenyList. https://github.com/python-discord/site/issues/305 --- .../0057_create_new_allowdenylist_model.py | 32 ++++++++++ .../migrations/0057_create_new_allowlist_model.py | 37 ----------- pydis_site/apps/api/models/__init__.py | 2 +- pydis_site/apps/api/models/bot/__init__.py | 2 +- pydis_site/apps/api/models/bot/allow_deny_list.py | 37 +++++++++++ pydis_site/apps/api/models/bot/allowlist.py | 39 ------------ pydis_site/apps/api/serializers.py | 8 +-- pydis_site/apps/api/tests/test_allowlists.py | 14 ++--- pydis_site/apps/api/urls.py | 6 +- pydis_site/apps/api/viewsets/__init__.py | 2 +- pydis_site/apps/api/viewsets/bot/__init__.py | 2 +- .../apps/api/viewsets/bot/allow_deny_list.py | 72 ++++++++++++++++++++++ pydis_site/apps/api/viewsets/bot/allowlist.py | 72 ---------------------- 13 files changed, 159 insertions(+), 166 deletions(-) create mode 100644 pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py delete mode 100644 pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py create mode 100644 pydis_site/apps/api/models/bot/allow_deny_list.py delete mode 100644 pydis_site/apps/api/models/bot/allowlist.py create mode 100644 pydis_site/apps/api/viewsets/bot/allow_deny_list.py delete mode 100644 pydis_site/apps/api/viewsets/bot/allowlist.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py new file mode 100644 index 00000000..c450344b --- /dev/null +++ b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py @@ -0,0 +1,32 @@ +# Generated by Django 3.0.8 on 2020-07-15 11:23 + +from django.db import migrations, models +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + dependencies = [ + ('api', '0056_allow_blank_user_roles'), + ] + + operations = [ + migrations.CreateModel( + name='AllowDenyList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('type', models.CharField( + choices=[('GUILD_INVITE_ID', 'Guild Invite Id'), ('FILE_FORMAT', 'File Format'), + ('DOMAIN_NAME', 'Domain Name'), ('WORD_WATCHLIST', 'Word Watchlist')], + help_text='The type of allowlist this is on.', max_length=50)), + ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), + ('content', models.TextField(help_text='The data to add to the allow or denylist.')), + ], + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + migrations.AddConstraint( + model_name='allowdenylist', + constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_allow_deny_list'), + ) + ] diff --git a/pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py b/pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py deleted file mode 100644 index 45650d86..00000000 --- a/pydis_site/apps/api/migrations/0057_create_new_allowlist_model.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.0.8 on 2020-07-15 11:23 - -from django.db import migrations, models -import pydis_site.apps.api.models.mixins - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0056_allow_blank_user_roles'), - ] - - operations = [ - migrations.CreateModel( - name='AllowList', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('type', models.CharField(choices=[('GUILD_INVITE_ID', 'Guild Invite Id'), ('FILE_FORMAT', 'File Format'), ('DOMAIN_NAME', 'Domain Name'), ('WORD_WATCHLIST', 'Word Watchlist')], help_text='The type of allowlist this is on.', max_length=50)), - ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), - ('content', models.TextField(help_text='The data to add to the allowlist.')), - ], - options={ - 'abstract': False, - }, - bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), - ), - migrations.AlterModelTable( - name='allowlist', - table='allow_list', - ), - migrations.AddConstraint( - model_name='allowlist', - constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_allowlist'), - ) - ] diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 2839fbba..34973a8d 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa from .bot import ( - AllowList, + AllowDenyList, BotSetting, DocumentationLink, DeletedMessage, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index b373ee84..1234b35a 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from .allowlist import AllowList +from .allow_deny_list import AllowDenyList from .bot_setting import BotSetting from .deleted_message import DeletedMessage from .documentation_link import DocumentationLink diff --git a/pydis_site/apps/api/models/bot/allow_deny_list.py b/pydis_site/apps/api/models/bot/allow_deny_list.py new file mode 100644 index 00000000..1eef47ba --- /dev/null +++ b/pydis_site/apps/api/models/bot/allow_deny_list.py @@ -0,0 +1,37 @@ +from django.db import models + +from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin + + +class AllowDenyList(ModelTimestampMixin, ModelReprMixin, models.Model): + """An item that is either allowed or denied.""" + + AllowDenyListType = models.TextChoices( + 'AllowDenyListType', + 'GUILD_INVITE_ID ' + 'FILE_FORMAT ' + 'DOMAIN_NAME ' + 'WORD_WATCHLIST ' + ) + type = models.CharField( + max_length=50, + help_text="The type of allowlist this is on.", + choices=AllowDenyListType.choices, + ) + allowed = models.BooleanField( + help_text="Whether this item is on the allowlist or the denylist." + ) + content = models.TextField( + help_text="The data to add to the allow or denylist." + ) + + class Meta: + """Metaconfig for this model.""" + + # This constraint ensures only one allow or denylist with the + # same content can exist. This means that we cannot have both an allow + # and a deny for the same item, and we cannot have duplicates of the + # same item. + constraints = [ + models.UniqueConstraint(fields=['content', 'type'], name='unique_allow_deny_list'), + ] diff --git a/pydis_site/apps/api/models/bot/allowlist.py b/pydis_site/apps/api/models/bot/allowlist.py deleted file mode 100644 index fc57ef32..00000000 --- a/pydis_site/apps/api/models/bot/allowlist.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.db import models - -from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin - - -class AllowList(ModelTimestampMixin, ModelReprMixin, models.Model): - """An item that is either allowed or denied.""" - - AllowListType = models.TextChoices( - 'AllowListType', - 'GUILD_INVITE_ID ' - 'FILE_FORMAT ' - 'DOMAIN_NAME ' - 'WORD_WATCHLIST ' - ) - type = models.CharField( - max_length=50, - help_text="The type of allowlist this is on.", - choices=AllowListType.choices, - ) - allowed = models.BooleanField( - help_text="Whether this item is on the allowlist or the denylist." - ) - content = models.TextField( - help_text="The data to add to the allowlist." - ) - - class Meta: - """Metaconfig for this model.""" - - db_table = 'allow_list' - - # This constraint ensures only one allowlist with the same content - # can exist per type.This means that we cannot have both an allow - # and a deny for the same item, and we cannot have duplicates of the - # same item. - constraints = [ - models.UniqueConstraint(fields=['content', 'type'], name='unique_allowlist'), - ] diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 13030074..d532dd69 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -4,7 +4,7 @@ from rest_framework.validators import UniqueTogetherValidator from rest_framework_bulk import BulkSerializerMixin from .models import ( - AllowList, + AllowDenyList, BotSetting, DeletedMessage, DocumentationLink, @@ -104,13 +104,13 @@ class DocumentationLinkSerializer(ModelSerializer): fields = ('package', 'base_url', 'inventory_url') -class AllowListSerializer(ModelSerializer): - """A class providing (de-)serialization of `AllowList` instances.""" +class AllowDenyListSerializer(ModelSerializer): + """A class providing (de-)serialization of `AllowDenyList` instances.""" class Meta: """Metadata defined for the Django REST Framework.""" - model = AllowList + model = AllowDenyList fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py index c6004439..5aa50670 100644 --- a/pydis_site/apps/api/tests/test_allowlists.py +++ b/pydis_site/apps/api/tests/test_allowlists.py @@ -1,9 +1,9 @@ from django_hosts.resolvers import reverse -from .base import APISubdomainTestCase -from ..models import AllowList +from pydis_site.apps.api.models import AllowDenyList +from pydis_site.apps.api.tests.base import APISubdomainTestCase -URL = reverse('bot:allowlist-list', host='api') +URL = reverse('bot:allowdenylist-list', host='api') JPEG_ALLOWLIST = { "type": 'FILE_FORMAT', "allowed": True, @@ -38,8 +38,8 @@ class EmptyDatabaseTests(APISubdomainTestCase): class FetchTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): - cls.jpeg_format = AllowList.objects.create(**JPEG_ALLOWLIST) - cls.png_format = AllowList.objects.create(**PNG_ALLOWLIST) + cls.jpeg_format = AllowDenyList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = AllowDenyList.objects.create(**PNG_ALLOWLIST) def test_returns_name_in_list(self): response = self.client.get(URL) @@ -83,8 +83,8 @@ class CreationTests(APISubdomainTestCase): class DeletionTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): - cls.jpeg_format = AllowList.objects.create(**JPEG_ALLOWLIST) - cls.png_format = AllowList.objects.create(**PNG_ALLOWLIST) + cls.jpeg_format = AllowDenyList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = AllowDenyList.objects.create(**PNG_ALLOWLIST) def test_deleting_unknown_id_returns_404(self): response = self.client.delete(f"{URL}/200") diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index bf41f09f..b6ed2914 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -3,7 +3,7 @@ from rest_framework.routers import DefaultRouter from .views import HealthcheckView, RulesView from .viewsets import ( - AllowListViewSet, + AllowDenyListViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, @@ -22,8 +22,8 @@ from .viewsets import ( # http://www.django-rest-framework.org/api-guide/routers/#defaultrouter bot_router = DefaultRouter(trailing_slash=False) bot_router.register( - 'allowlists', - AllowListViewSet + 'allow_deny_lists', + AllowDenyListViewSet ) bot_router.register( 'bot-settings', diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py index 98d3d586..eb7d5098 100644 --- a/pydis_site/apps/api/viewsets/__init__.py +++ b/pydis_site/apps/api/viewsets/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa from .bot import ( - AllowListViewSet, + AllowDenyListViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py index 86bfc910..11638dd8 100644 --- a/pydis_site/apps/api/viewsets/bot/__init__.py +++ b/pydis_site/apps/api/viewsets/bot/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from .allowlist import AllowListViewSet +from .allow_deny_list import AllowDenyListViewSet from .bot_setting import BotSettingViewSet from .deleted_message import DeletedMessageViewSet from .documentation_link import DocumentationLinkViewSet diff --git a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py new file mode 100644 index 00000000..a2499d89 --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py @@ -0,0 +1,72 @@ +from rest_framework.viewsets import ModelViewSet + +from pydis_site.apps.api.models.bot.allow_deny_list import AllowDenyList +from pydis_site.apps.api.serializers import AllowDenyListSerializer + + +class AllowDenyListViewSet(ModelViewSet): + """ + View providing CRUD operations on items allowed or denied by our bot. + + ## Routes + ### GET /bot/allow_deny_lists + Returns all allow and denylist items in the database. + + #### Response format + >>> [ + ... { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... }, + ... ... + ... ] + + #### Status codes + - 200: returned on success + - 401: returned if unauthenticated + + ### GET /bot/allow_deny_lists/ + Returns a specific AllowDenyList item from the database. + + #### Response format + >>> { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... } + + #### Status codes + - 200: returned on success + - 404: returned if the id was not found. + + ### POST /bot/allow_deny_lists + Adds a single AllowDenyList item to the database. + + #### Request body + >>> { + ... 'type': str, + ... 'allowed': bool, + ... 'content': str, + ... } + + #### Status codes + - 201: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/allow_deny_lists/ + Deletes the AllowDenyList item with the given `id`. + + #### Status codes + - 204: returned on success + - 404: if a tag with the given `id` does not exist + """ + + serializer_class = AllowDenyListSerializer + queryset = AllowDenyList.objects.all() diff --git a/pydis_site/apps/api/viewsets/bot/allowlist.py b/pydis_site/apps/api/viewsets/bot/allowlist.py deleted file mode 100644 index 7cc82ff7..00000000 --- a/pydis_site/apps/api/viewsets/bot/allowlist.py +++ /dev/null @@ -1,72 +0,0 @@ -from rest_framework.viewsets import ModelViewSet - -from pydis_site.apps.api.models.bot.allowlist import AllowList -from pydis_site.apps.api.serializers import AllowListSerializer - - -class AllowListViewSet(ModelViewSet): - """ - View providing CRUD operations on items whitelisted or blacklisted by our bot. - - ## Routes - ### GET /bot/allowlists - Returns all allowlist items in the database. - - #### Response format - >>> [ - ... { - ... 'id': "2309268224", - ... 'created_at': "01-01-2020 ...", - ... 'updated_at': "01-01-2020 ...", - ... 'type': "file_format", - ... 'allowed': 'true', - ... 'content': ".jpeg", - ... }, - ... ... - ... ] - - #### Status codes - - 200: returned on success - - 401: returned if unauthenticated - - ### GET /bot/allowlists/ - Returns a specific AllowList item from the database. - - #### Response format - >>> { - ... 'id': "2309268224", - ... 'created_at': "01-01-2020 ...", - ... 'updated_at': "01-01-2020 ...", - ... 'type': "file_format", - ... 'allowed': 'true', - ... 'content': ".jpeg", - ... } - - #### Status codes - - 200: returned on success - - 404: returned if the id was not found. - - ### POST /bot/allowedlists - Adds a single allowedlist item to the database. - - #### Request body - >>> { - ... 'type': str, - ... 'allowed': bool, - ... 'content': str, - ... } - - #### Status codes - - 201: returned on success - - 400: if one of the given fields is invalid - - ### DELETE /bot/allowedlists/ - Deletes the tag with the given `id`. - - #### Status codes - - 204: returned on success - - 404: if a tag with the given `id` does not exist - """ - - serializer_class = AllowListSerializer - queryset = AllowList.objects.all() -- cgit v1.2.3 From f596c4a45b0eb98b0c7bb1b13e79427f5f57a59b Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 22:25:00 +0200 Subject: 100% coverage for account.py https://github.com/python-discord/site/issues/305 --- pydis_site/tests/test_utils_account.py | 59 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 29 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/tests/test_utils_account.py b/pydis_site/tests/test_utils_account.py index 3edca658..e12805bb 100644 --- a/pydis_site/tests/test_utils_account.py +++ b/pydis_site/tests/test_utils_account.py @@ -13,28 +13,43 @@ from pydis_site.utils.account import AccountAdapter, SocialAccountAdapter class AccountUtilsTests(TestCase): def setUp(self): + # Create the user self.django_user = User.objects.create(username="user") + # Create the roles + developers_role = Role.objects.create( + id=1, + name="Developers", + colour=0, + permissions=0, + position=1 + ) + everyone_role = Role.objects.create( + id=0, + name="@everyone", + colour=0, + permissions=0, + position=0 + ) + + # Create the social accounts self.discord_account = SocialAccount.objects.create( user=self.django_user, provider="discord", uid=0 ) - - self.discord_account_role = SocialAccount.objects.create( + self.discord_account_one_role = SocialAccount.objects.create( user=self.django_user, provider="discord", uid=1 ) - self.discord_account_two_roles = SocialAccount.objects.create( user=self.django_user, provider="discord", uid=2 ) - self.discord_account_not_present = SocialAccount.objects.create( user=self.django_user, provider="discord", uid=3 ) - self.github_account = SocialAccount.objects.create( user=self.django_user, provider="github", uid=0 ) + # Create DiscordUsers self.discord_user = DiscordUser.objects.create( id=0, name="user", @@ -44,36 +59,17 @@ class AccountUtilsTests(TestCase): self.discord_user_role = DiscordUser.objects.create( id=1, name="user present", - discriminator=0 + discriminator=0, + roles=[everyone_role.id] ) self.discord_user_two_roles = DiscordUser.objects.create( id=2, name="user with both roles", - discriminator=0 - ) - - everyone_role = Role.objects.create( - id=0, - name="@everyone", - colour=0, - permissions=0, - position=0 - ) - - self.discord_user_role.roles.append(everyone_role.id) - self.discord_user_two_roles.roles.append(everyone_role.id) - - developers_role = Role.objects.create( - id=1, - name="Developers", - colour=0, - permissions=0, - position=1 + discriminator=0, + roles=[everyone_role.id, developers_role.id] ) - self.discord_user_two_roles.roles.append(developers_role.id) - self.request_factory = RequestFactory() def test_account_adapter(self): @@ -87,8 +83,9 @@ class AccountUtilsTests(TestCase): adapter = SocialAccountAdapter() discord_login = SocialLogin(account=self.discord_account) - discord_login_role = SocialLogin(account=self.discord_account_role) + discord_login_role = SocialLogin(account=self.discord_account_one_role) discord_login_not_present = SocialLogin(account=self.discord_account_not_present) + discord_login_two_roles = SocialLogin(account=self.discord_account_two_roles) github_login = SocialLogin(account=self.github_account) @@ -109,6 +106,10 @@ class AccountUtilsTests(TestCase): with self.assertRaises(ImmediateHttpResponse): adapter.is_open_for_signup(messages_request, discord_login_not_present) + self.assertTrue( + adapter.is_open_for_signup(messages_request, discord_login_two_roles) + ) + self.assertEqual(len(messages_request._messages._queued_messages), 4) self.assertEqual(mock_redirect.call_count, 4) self.assertEqual(mock_reverse.call_count, 4) -- cgit v1.2.3 From 1fc9034dd10f8d6f02a43af9170005529803e029 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 16 Jul 2020 22:44:22 +0200 Subject: 100% branch coverage for account.py https://github.com/python-discord/site/issues/305 --- pydis_site/tests/test_utils_account.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/tests/test_utils_account.py b/pydis_site/tests/test_utils_account.py index e12805bb..6f8338b4 100644 --- a/pydis_site/tests/test_utils_account.py +++ b/pydis_site/tests/test_utils_account.py @@ -122,13 +122,18 @@ class AccountUtilsTests(TestCase): account=self.discord_account, user=self.django_user ) - discord_login.account.extra_data["discriminator"] = "0000" - user = adapter.populate_user( + discord_user = adapter.populate_user( self.request_factory.get("/"), discord_login, {"username": "user"} ) + self.assertEqual(discord_user.username, "user#0000") + self.assertEqual(discord_user.first_name, "user#0000") - self.assertEqual(user.username, "user#0000") - self.assertEqual(user.first_name, "user#0000") + discord_login.account.provider = "not_discord" + not_discord_user = adapter.populate_user( + self.request_factory.get("/"), discord_login, + {"username": "user"} + ) + self.assertEqual(not_discord_user.username, "user") -- cgit v1.2.3 From 061ace4c1013abbdb5408f682c0c8401d13a59ec Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Fri, 17 Jul 2020 14:59:59 +0200 Subject: Add an endpoint for getting AllowDenyList types. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/tests/test_allowlists.py | 8 ++++++++ pydis_site/apps/api/viewsets/bot/allow_deny_list.py | 8 ++++++++ 2 files changed, 16 insertions(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py index 5aa50670..1440dc71 100644 --- a/pydis_site/apps/api/tests/test_allowlists.py +++ b/pydis_site/apps/api/tests/test_allowlists.py @@ -54,6 +54,14 @@ class FetchTests(APISubdomainTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.json().get("content"), self.jpeg_format.content) + def test_returns_allow_deny_list_types(self): + response = self.client.get(f'{URL}/get_types') + + self.assertEqual(response.status_code, 200) + for api_type, model_type in zip(response.json(), AllowDenyList.AllowDenyListType.choices): + self.assertEquals(api_type[0], model_type[0]) + self.assertEquals(api_type[1], model_type[1]) + class CreationTests(APISubdomainTestCase): def test_returns_400_for_missing_params(self): diff --git a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py index a2499d89..2b9fd6a7 100644 --- a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py +++ b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py @@ -1,3 +1,6 @@ +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from pydis_site.apps.api.models.bot.allow_deny_list import AllowDenyList @@ -70,3 +73,8 @@ class AllowDenyListViewSet(ModelViewSet): serializer_class = AllowDenyListSerializer queryset = AllowDenyList.objects.all() + + @action(detail=False, methods=["get"]) + def get_types(self, _: Request) -> Response: + """Get a list of all the types of AllowDenyLists we support.""" + return Response(AllowDenyList.AllowDenyListType.choices) -- cgit v1.2.3 From f6111a35e0b5385ccd02cea12d0afc4a86615ba0 Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sun, 19 Jul 2020 12:49:04 +0800 Subject: Use literal integers for mentions ID in test Since the mentions field stores static IDs and not foreign keys, there is no need to create the objects for the test. --- pydis_site/apps/api/tests/test_reminders.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py index 5042ea90..a05d9296 100644 --- a/pydis_site/apps/api/tests/test_reminders.py +++ b/pydis_site/apps/api/tests/test_reminders.py @@ -4,7 +4,7 @@ from django.forms.models import model_to_dict from django_hosts.resolvers import reverse from .base import APISubdomainTestCase -from ..models import Reminder, Role, User +from ..models import Reminder, User class UnauthedReminderAPITests(APISubdomainTestCase): @@ -54,18 +54,6 @@ class ReminderCreationTests(APISubdomainTestCase): name='Mermaid Man', discriminator=1234, ) - cls.user = User.objects.create( - id=5678, - name='Fish Dude', - discriminator=5678, - ) - cls.role = Role.objects.create( - id=555, - name="Random role", - colour=2, - permissions=0b01010010101, - position=10, - ) def test_accepts_valid_data(self): data = { @@ -74,7 +62,7 @@ class ReminderCreationTests(APISubdomainTestCase): 'expiration': datetime.utcnow().isoformat(), 'jump_url': "https://www.google.com", 'channel_id': 123, - 'mentions': [self.user.id, self.role.id], + 'mentions': [8888, 9999], } url = reverse('bot:reminder-list', host='api') response = self.client.post(url, data=data) -- cgit v1.2.3 From 387b763af7dfe3675a476ab6a2b9815e2ac8e83a Mon Sep 17 00:00:00 2001 From: kosayoda Date: Sun, 19 Jul 2020 12:50:01 +0800 Subject: Fix misleading documentation --- pydis_site/apps/api/viewsets/bot/reminder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py index f4921d44..6f8a28f2 100644 --- a/pydis_site/apps/api/viewsets/bot/reminder.py +++ b/pydis_site/apps/api/viewsets/bot/reminder.py @@ -54,7 +54,7 @@ class ReminderViewSet( ... 'content': str, ... 'expiration': str, # ISO-formatted datetime ... 'channel_id': int, - ... 'jump_url': Optional[str] + ... 'jump_url': str ... } #### Status codes -- cgit v1.2.3 From 8874a83fa6372e0ce46e9552e8c21c182e4a1c3e Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 19 Jul 2020 11:55:20 +0200 Subject: Add another AllowDenyList field, 'comment'. This will be used to describe or justify the entries in the blacklist or whitelist, for example for the guild name in the case of guild invite IDs, so that we have some context when we're displaying the list. https://github.com/python-discord/site/issues/305 --- pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py | 1 + pydis_site/apps/api/models/bot/allow_deny_list.py | 4 ++++ pydis_site/apps/api/serializers.py | 2 +- pydis_site/apps/api/viewsets/bot/allow_deny_list.py | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py index c450344b..d36acd70 100644 --- a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py +++ b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py @@ -22,6 +22,7 @@ class Migration(migrations.Migration): help_text='The type of allowlist this is on.', max_length=50)), ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), ('content', models.TextField(help_text='The data to add to the allow or denylist.')), + ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), diff --git a/pydis_site/apps/api/models/bot/allow_deny_list.py b/pydis_site/apps/api/models/bot/allow_deny_list.py index 1eef47ba..b95dd72e 100644 --- a/pydis_site/apps/api/models/bot/allow_deny_list.py +++ b/pydis_site/apps/api/models/bot/allow_deny_list.py @@ -24,6 +24,10 @@ class AllowDenyList(ModelTimestampMixin, ModelReprMixin, models.Model): content = models.TextField( help_text="The data to add to the allow or denylist." ) + comment = models.TextField( + help_text="Optional comment on this entry.", + null=True + ) class Meta: """Metaconfig for this model.""" diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index d532dd69..50f27f1d 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -111,7 +111,7 @@ class AllowDenyListSerializer(ModelSerializer): """Metadata defined for the Django REST Framework.""" model = AllowDenyList - fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content') + fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content', 'comment') class InfractionSerializer(ModelSerializer): diff --git a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py index 2b9fd6a7..72cbc84c 100644 --- a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py +++ b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py @@ -24,6 +24,7 @@ class AllowDenyListViewSet(ModelViewSet): ... 'type': "file_format", ... 'allowed': 'true', ... 'content': ".jpeg", + ... 'comment': "Popular image format.", ... }, ... ... ... ] @@ -43,6 +44,7 @@ class AllowDenyListViewSet(ModelViewSet): ... 'type': "file_format", ... 'allowed': 'true', ... 'content': ".jpeg", + ... 'comment': "Popular image format.", ... } #### Status codes @@ -57,6 +59,7 @@ class AllowDenyListViewSet(ModelViewSet): ... 'type': str, ... 'allowed': bool, ... 'content': str, + ... 'comment': Optional[str], ... } #### Status codes -- cgit v1.2.3 From 74ef6fee279c451dac165d3792242ffdf35441ff Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 19 Jul 2020 12:45:14 +0200 Subject: Simplify AllowDenyListType names. GUILD_INVITE_ID -> GUILD_INVITE WORD_WATCHLIST -> FILTER_TOKEN --- pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py | 4 ++-- pydis_site/apps/api/models/bot/allow_deny_list.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py index d36acd70..ab8c5f3f 100644 --- a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py +++ b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py @@ -17,8 +17,8 @@ class Migration(migrations.Migration): ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('type', models.CharField( - choices=[('GUILD_INVITE_ID', 'Guild Invite Id'), ('FILE_FORMAT', 'File Format'), - ('DOMAIN_NAME', 'Domain Name'), ('WORD_WATCHLIST', 'Word Watchlist')], + choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'), + ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token')], help_text='The type of allowlist this is on.', max_length=50)), ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), ('content', models.TextField(help_text='The data to add to the allow or denylist.')), diff --git a/pydis_site/apps/api/models/bot/allow_deny_list.py b/pydis_site/apps/api/models/bot/allow_deny_list.py index b95dd72e..e1e09a78 100644 --- a/pydis_site/apps/api/models/bot/allow_deny_list.py +++ b/pydis_site/apps/api/models/bot/allow_deny_list.py @@ -8,10 +8,10 @@ class AllowDenyList(ModelTimestampMixin, ModelReprMixin, models.Model): AllowDenyListType = models.TextChoices( 'AllowDenyListType', - 'GUILD_INVITE_ID ' + 'GUILD_INVITE ' 'FILE_FORMAT ' 'DOMAIN_NAME ' - 'WORD_WATCHLIST ' + 'FILTER_TOKEN ' ) type = models.CharField( max_length=50, -- cgit v1.2.3 From 53e4dfef781d0cd8239122b2bb55e08ae1d031f5 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 19 Jul 2020 22:19:04 +0200 Subject: Document the get_types endpoint. --- pydis_site/apps/api/viewsets/bot/allow_deny_list.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py index 72cbc84c..6ea8da56 100644 --- a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py +++ b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py @@ -51,6 +51,20 @@ class AllowDenyListViewSet(ModelViewSet): - 200: returned on success - 404: returned if the id was not found. + ### GET /bot/allow_deny_lists/get_types + Returns a list of valid list types that can be used in POST requests. + + #### Response format + >>> [ + ... ["GUILD_INVITE","Guild Invite"], + ... ["FILE_FORMAT","File Format"], + ... ["DOMAIN_NAME","Domain Name"], + ... ["FILTER_TOKEN","Filter Token"] + ... ] + + #### Status codes + - 200: returned on success + ### POST /bot/allow_deny_lists Adds a single AllowDenyList item to the database. -- cgit v1.2.3 From 99fc3cde63b4f5aa84d2c1d6b9f5cd9f983fe6cb Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Sun, 19 Jul 2020 22:24:10 +0200 Subject: Minor changes to tests, use subTest. --- pydis_site/apps/api/tests/test_allowlists.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py index 1440dc71..fd8772d0 100644 --- a/pydis_site/apps/api/tests/test_allowlists.py +++ b/pydis_site/apps/api/tests/test_allowlists.py @@ -80,8 +80,9 @@ class CreationTests(APISubdomainTestCase): cases = [{}, no_type_json, no_allowed_json, no_content_json] for case in cases: - response = self.client.post(URL, data=case) - self.assertEqual(response.status_code, 400) + with self.subTest(case=case): + response = self.client.post(URL, data=case) + self.assertEqual(response.status_code, 400) def test_returns_201_for_successful_creation(self): response = self.client.post(URL, data=JPEG_ALLOWLIST) @@ -96,17 +97,11 @@ class DeletionTests(APISubdomainTestCase): def test_deleting_unknown_id_returns_404(self): response = self.client.delete(f"{URL}/200") - self.assertEqual(response.status_code, 404) def test_deleting_known_id_returns_204(self): response = self.client.delete(f"{URL}/{self.jpeg_format.id}") - - self.assertEqual(response.status_code, 204) - - def test_name_gets_deleted(self): - response = self.client.delete(f"{URL}/{self.png_format.id}") self.assertEqual(response.status_code, 204) - response = self.client.get(f"{URL}/{self.png_format.id}") + response = self.client.get(f"{URL}/{self.jpeg_format.id}") self.assertNotIn(self.png_format.content, response.json()) -- cgit v1.2.3 From 285c81bc13e996246ad9463951853fc12903d766 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Mon, 27 Jul 2020 09:53:38 +0200 Subject: Rename AllowDenyList to FilterList --- .../0057_create_new_allowdenylist_model.py | 33 ------- .../migrations/0057_create_new_filterlist_model.py | 33 +++++++ pydis_site/apps/api/models/__init__.py | 2 +- pydis_site/apps/api/models/bot/__init__.py | 2 +- pydis_site/apps/api/models/bot/allow_deny_list.py | 41 -------- pydis_site/apps/api/models/bot/filter_list.py | 41 ++++++++ pydis_site/apps/api/serializers.py | 10 +- pydis_site/apps/api/tests/test_allowlists.py | 107 --------------------- pydis_site/apps/api/tests/test_filterlists.py | 107 +++++++++++++++++++++ pydis_site/apps/api/urls.py | 7 +- pydis_site/apps/api/viewsets/__init__.py | 2 +- pydis_site/apps/api/viewsets/bot/__init__.py | 2 +- .../apps/api/viewsets/bot/allow_deny_list.py | 97 ------------------- pydis_site/apps/api/viewsets/bot/filter_list.py | 97 +++++++++++++++++++ 14 files changed, 290 insertions(+), 291 deletions(-) delete mode 100644 pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py create mode 100644 pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py delete mode 100644 pydis_site/apps/api/models/bot/allow_deny_list.py create mode 100644 pydis_site/apps/api/models/bot/filter_list.py delete mode 100644 pydis_site/apps/api/tests/test_allowlists.py create mode 100644 pydis_site/apps/api/tests/test_filterlists.py delete mode 100644 pydis_site/apps/api/viewsets/bot/allow_deny_list.py create mode 100644 pydis_site/apps/api/viewsets/bot/filter_list.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py b/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py deleted file mode 100644 index ab8c5f3f..00000000 --- a/pydis_site/apps/api/migrations/0057_create_new_allowdenylist_model.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.0.8 on 2020-07-15 11:23 - -from django.db import migrations, models -import pydis_site.apps.api.models.mixins - - -class Migration(migrations.Migration): - dependencies = [ - ('api', '0056_allow_blank_user_roles'), - ] - - operations = [ - migrations.CreateModel( - name='AllowDenyList', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('type', models.CharField( - choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'), - ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token')], - help_text='The type of allowlist this is on.', max_length=50)), - ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), - ('content', models.TextField(help_text='The data to add to the allow or denylist.')), - ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)), - ], - bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), - ), - migrations.AddConstraint( - model_name='allowdenylist', - constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_allow_deny_list'), - ) - ] diff --git a/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py b/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py new file mode 100644 index 00000000..44025ee8 --- /dev/null +++ b/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py @@ -0,0 +1,33 @@ +# Generated by Django 3.0.8 on 2020-07-15 11:23 + +from django.db import migrations, models +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + dependencies = [ + ('api', '0056_allow_blank_user_roles'), + ] + + operations = [ + migrations.CreateModel( + name='FilterList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('type', models.CharField( + choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'), + ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token')], + help_text='The type of allowlist this is on.', max_length=50)), + ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), + ('content', models.TextField(help_text='The data to add to the allow or denylist.')), + ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)), + ], + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + migrations.AddConstraint( + model_name='filterlist', + constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_filter_list'), + ) + ] diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py index 34973a8d..1d0ab7ea 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa from .bot import ( - AllowDenyList, + FilterList, BotSetting, DocumentationLink, DeletedMessage, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index 1234b35a..efd98184 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from .allow_deny_list import AllowDenyList +from .filter_list import FilterList from .bot_setting import BotSetting from .deleted_message import DeletedMessage from .documentation_link import DocumentationLink diff --git a/pydis_site/apps/api/models/bot/allow_deny_list.py b/pydis_site/apps/api/models/bot/allow_deny_list.py deleted file mode 100644 index e1e09a78..00000000 --- a/pydis_site/apps/api/models/bot/allow_deny_list.py +++ /dev/null @@ -1,41 +0,0 @@ -from django.db import models - -from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin - - -class AllowDenyList(ModelTimestampMixin, ModelReprMixin, models.Model): - """An item that is either allowed or denied.""" - - AllowDenyListType = models.TextChoices( - 'AllowDenyListType', - 'GUILD_INVITE ' - 'FILE_FORMAT ' - 'DOMAIN_NAME ' - 'FILTER_TOKEN ' - ) - type = models.CharField( - max_length=50, - help_text="The type of allowlist this is on.", - choices=AllowDenyListType.choices, - ) - allowed = models.BooleanField( - help_text="Whether this item is on the allowlist or the denylist." - ) - content = models.TextField( - help_text="The data to add to the allow or denylist." - ) - comment = models.TextField( - help_text="Optional comment on this entry.", - null=True - ) - - class Meta: - """Metaconfig for this model.""" - - # This constraint ensures only one allow or denylist with the - # same content can exist. This means that we cannot have both an allow - # and a deny for the same item, and we cannot have duplicates of the - # same item. - constraints = [ - models.UniqueConstraint(fields=['content', 'type'], name='unique_allow_deny_list'), - ] diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py new file mode 100644 index 00000000..d279e137 --- /dev/null +++ b/pydis_site/apps/api/models/bot/filter_list.py @@ -0,0 +1,41 @@ +from django.db import models + +from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin + + +class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model): + """An item that is either allowed or denied.""" + + FilterListType = models.TextChoices( + 'FilterListType', + 'GUILD_INVITE ' + 'FILE_FORMAT ' + 'DOMAIN_NAME ' + 'FILTER_TOKEN ' + ) + type = models.CharField( + max_length=50, + help_text="The type of allowlist this is on.", + choices=FilterListType.choices, + ) + allowed = models.BooleanField( + help_text="Whether this item is on the allowlist or the denylist." + ) + content = models.TextField( + help_text="The data to add to the allow or denylist." + ) + comment = models.TextField( + help_text="Optional comment on this entry.", + null=True + ) + + class Meta: + """Metaconfig for this model.""" + + # This constraint ensures only one filterlist with the + # same content can exist. This means that we cannot have both an allow + # and a deny for the same item, and we cannot have duplicates of the + # same item. + constraints = [ + models.UniqueConstraint(fields=['content', 'type'], name='unique_filter_list'), + ] diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 50f27f1d..8fd40da7 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -4,10 +4,10 @@ from rest_framework.validators import UniqueTogetherValidator from rest_framework_bulk import BulkSerializerMixin from .models import ( - AllowDenyList, BotSetting, DeletedMessage, DocumentationLink, + FilterList, Infraction, LogEntry, MessageDeletionContext, @@ -17,7 +17,7 @@ from .models import ( Reminder, Role, Tag, - User, + User ) @@ -104,13 +104,13 @@ class DocumentationLinkSerializer(ModelSerializer): fields = ('package', 'base_url', 'inventory_url') -class AllowDenyListSerializer(ModelSerializer): - """A class providing (de-)serialization of `AllowDenyList` instances.""" +class FilterListSerializer(ModelSerializer): + """A class providing (de-)serialization of `FilterList` instances.""" class Meta: """Metadata defined for the Django REST Framework.""" - model = AllowDenyList + model = FilterList fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content', 'comment') diff --git a/pydis_site/apps/api/tests/test_allowlists.py b/pydis_site/apps/api/tests/test_allowlists.py deleted file mode 100644 index fd8772d0..00000000 --- a/pydis_site/apps/api/tests/test_allowlists.py +++ /dev/null @@ -1,107 +0,0 @@ -from django_hosts.resolvers import reverse - -from pydis_site.apps.api.models import AllowDenyList -from pydis_site.apps.api.tests.base import APISubdomainTestCase - -URL = reverse('bot:allowdenylist-list', host='api') -JPEG_ALLOWLIST = { - "type": 'FILE_FORMAT', - "allowed": True, - "content": ".jpeg", -} -PNG_ALLOWLIST = { - "type": 'FILE_FORMAT', - "allowed": True, - "content": ".png", -} - - -class UnauthenticatedTests(APISubdomainTestCase): - def setUp(self): - super().setUp() - self.client.force_authenticate(user=None) - - def test_cannot_read_allowedlist_list(self): - response = self.client.get(URL) - - self.assertEqual(response.status_code, 401) - - -class EmptyDatabaseTests(APISubdomainTestCase): - def test_returns_empty_object(self): - response = self.client.get(URL) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), []) - - -class FetchTests(APISubdomainTestCase): - @classmethod - def setUpTestData(cls): - cls.jpeg_format = AllowDenyList.objects.create(**JPEG_ALLOWLIST) - cls.png_format = AllowDenyList.objects.create(**PNG_ALLOWLIST) - - def test_returns_name_in_list(self): - response = self.client.get(URL) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()[0]["content"], self.jpeg_format.content) - self.assertEqual(response.json()[1]["content"], self.png_format.content) - - def test_returns_single_item_by_id(self): - response = self.client.get(f'{URL}/{self.jpeg_format.id}') - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json().get("content"), self.jpeg_format.content) - - def test_returns_allow_deny_list_types(self): - response = self.client.get(f'{URL}/get_types') - - self.assertEqual(response.status_code, 200) - for api_type, model_type in zip(response.json(), AllowDenyList.AllowDenyListType.choices): - self.assertEquals(api_type[0], model_type[0]) - self.assertEquals(api_type[1], model_type[1]) - - -class CreationTests(APISubdomainTestCase): - def test_returns_400_for_missing_params(self): - no_type_json = { - "allowed": True, - "content": ".jpeg" - } - no_allowed_json = { - "type": "FILE_FORMAT", - "content": ".jpeg" - } - no_content_json = { - "allowed": True, - "type": "FILE_FORMAT" - } - cases = [{}, no_type_json, no_allowed_json, no_content_json] - - for case in cases: - with self.subTest(case=case): - response = self.client.post(URL, data=case) - self.assertEqual(response.status_code, 400) - - def test_returns_201_for_successful_creation(self): - response = self.client.post(URL, data=JPEG_ALLOWLIST) - self.assertEqual(response.status_code, 201) - - -class DeletionTests(APISubdomainTestCase): - @classmethod - def setUpTestData(cls): - cls.jpeg_format = AllowDenyList.objects.create(**JPEG_ALLOWLIST) - cls.png_format = AllowDenyList.objects.create(**PNG_ALLOWLIST) - - def test_deleting_unknown_id_returns_404(self): - response = self.client.delete(f"{URL}/200") - self.assertEqual(response.status_code, 404) - - def test_deleting_known_id_returns_204(self): - response = self.client.delete(f"{URL}/{self.jpeg_format.id}") - self.assertEqual(response.status_code, 204) - - response = self.client.get(f"{URL}/{self.jpeg_format.id}") - self.assertNotIn(self.png_format.content, response.json()) diff --git a/pydis_site/apps/api/tests/test_filterlists.py b/pydis_site/apps/api/tests/test_filterlists.py new file mode 100644 index 00000000..b577c31c --- /dev/null +++ b/pydis_site/apps/api/tests/test_filterlists.py @@ -0,0 +1,107 @@ +from django_hosts.resolvers import reverse + +from pydis_site.apps.api.models import FilterList +from pydis_site.apps.api.tests.base import APISubdomainTestCase + +URL = reverse('bot:filterlist-list', host='api') +JPEG_ALLOWLIST = { + "type": 'FILE_FORMAT', + "allowed": True, + "content": ".jpeg", +} +PNG_ALLOWLIST = { + "type": 'FILE_FORMAT', + "allowed": True, + "content": ".png", +} + + +class UnauthenticatedTests(APISubdomainTestCase): + def setUp(self): + super().setUp() + self.client.force_authenticate(user=None) + + def test_cannot_read_allowedlist_list(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 401) + + +class EmptyDatabaseTests(APISubdomainTestCase): + def test_returns_empty_object(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), []) + + +class FetchTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST) + + def test_returns_name_in_list(self): + response = self.client.get(URL) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()[0]["content"], self.jpeg_format.content) + self.assertEqual(response.json()[1]["content"], self.png_format.content) + + def test_returns_single_item_by_id(self): + response = self.client.get(f'{URL}/{self.jpeg_format.id}') + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json().get("content"), self.jpeg_format.content) + + def test_returns_filter_list_types(self): + response = self.client.get(f'{URL}/get-types') + + self.assertEqual(response.status_code, 200) + for api_type, model_type in zip(response.json(), FilterList.FilterListType.choices): + self.assertEquals(api_type[0], model_type[0]) + self.assertEquals(api_type[1], model_type[1]) + + +class CreationTests(APISubdomainTestCase): + def test_returns_400_for_missing_params(self): + no_type_json = { + "allowed": True, + "content": ".jpeg" + } + no_allowed_json = { + "type": "FILE_FORMAT", + "content": ".jpeg" + } + no_content_json = { + "allowed": True, + "type": "FILE_FORMAT" + } + cases = [{}, no_type_json, no_allowed_json, no_content_json] + + for case in cases: + with self.subTest(case=case): + response = self.client.post(URL, data=case) + self.assertEqual(response.status_code, 400) + + def test_returns_201_for_successful_creation(self): + response = self.client.post(URL, data=JPEG_ALLOWLIST) + self.assertEqual(response.status_code, 201) + + +class DeletionTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST) + cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST) + + def test_deleting_unknown_id_returns_404(self): + response = self.client.delete(f"{URL}/200") + self.assertEqual(response.status_code, 404) + + def test_deleting_known_id_returns_204(self): + response = self.client.delete(f"{URL}/{self.jpeg_format.id}") + self.assertEqual(response.status_code, 204) + + response = self.client.get(f"{URL}/{self.jpeg_format.id}") + self.assertNotIn(self.png_format.content, response.json()) diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index b6ed2914..a4fd5b2e 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -3,10 +3,10 @@ from rest_framework.routers import DefaultRouter from .views import HealthcheckView, RulesView from .viewsets import ( - AllowDenyListViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, + FilterListViewSet, InfractionViewSet, LogEntryViewSet, NominationViewSet, @@ -18,12 +18,11 @@ from .viewsets import ( UserViewSet ) - # http://www.django-rest-framework.org/api-guide/routers/#defaultrouter bot_router = DefaultRouter(trailing_slash=False) bot_router.register( - 'allow_deny_lists', - AllowDenyListViewSet + 'filter-lists', + FilterListViewSet ) bot_router.register( 'bot-settings', diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py index eb7d5098..8699517e 100644 --- a/pydis_site/apps/api/viewsets/__init__.py +++ b/pydis_site/apps/api/viewsets/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa from .bot import ( - AllowDenyListViewSet, + FilterListViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py index 11638dd8..e64e3988 100644 --- a/pydis_site/apps/api/viewsets/bot/__init__.py +++ b/pydis_site/apps/api/viewsets/bot/__init__.py @@ -1,5 +1,5 @@ # flake8: noqa -from .allow_deny_list import AllowDenyListViewSet +from .filter_list import FilterListViewSet from .bot_setting import BotSettingViewSet from .deleted_message import DeletedMessageViewSet from .documentation_link import DocumentationLinkViewSet diff --git a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py b/pydis_site/apps/api/viewsets/bot/allow_deny_list.py deleted file mode 100644 index 6ea8da56..00000000 --- a/pydis_site/apps/api/viewsets/bot/allow_deny_list.py +++ /dev/null @@ -1,97 +0,0 @@ -from rest_framework.decorators import action -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.viewsets import ModelViewSet - -from pydis_site.apps.api.models.bot.allow_deny_list import AllowDenyList -from pydis_site.apps.api.serializers import AllowDenyListSerializer - - -class AllowDenyListViewSet(ModelViewSet): - """ - View providing CRUD operations on items allowed or denied by our bot. - - ## Routes - ### GET /bot/allow_deny_lists - Returns all allow and denylist items in the database. - - #### Response format - >>> [ - ... { - ... 'id': "2309268224", - ... 'created_at': "01-01-2020 ...", - ... 'updated_at': "01-01-2020 ...", - ... 'type': "file_format", - ... 'allowed': 'true', - ... 'content': ".jpeg", - ... 'comment': "Popular image format.", - ... }, - ... ... - ... ] - - #### Status codes - - 200: returned on success - - 401: returned if unauthenticated - - ### GET /bot/allow_deny_lists/ - Returns a specific AllowDenyList item from the database. - - #### Response format - >>> { - ... 'id': "2309268224", - ... 'created_at': "01-01-2020 ...", - ... 'updated_at': "01-01-2020 ...", - ... 'type': "file_format", - ... 'allowed': 'true', - ... 'content': ".jpeg", - ... 'comment': "Popular image format.", - ... } - - #### Status codes - - 200: returned on success - - 404: returned if the id was not found. - - ### GET /bot/allow_deny_lists/get_types - Returns a list of valid list types that can be used in POST requests. - - #### Response format - >>> [ - ... ["GUILD_INVITE","Guild Invite"], - ... ["FILE_FORMAT","File Format"], - ... ["DOMAIN_NAME","Domain Name"], - ... ["FILTER_TOKEN","Filter Token"] - ... ] - - #### Status codes - - 200: returned on success - - ### POST /bot/allow_deny_lists - Adds a single AllowDenyList item to the database. - - #### Request body - >>> { - ... 'type': str, - ... 'allowed': bool, - ... 'content': str, - ... 'comment': Optional[str], - ... } - - #### Status codes - - 201: returned on success - - 400: if one of the given fields is invalid - - ### DELETE /bot/allow_deny_lists/ - Deletes the AllowDenyList item with the given `id`. - - #### Status codes - - 204: returned on success - - 404: if a tag with the given `id` does not exist - """ - - serializer_class = AllowDenyListSerializer - queryset = AllowDenyList.objects.all() - - @action(detail=False, methods=["get"]) - def get_types(self, _: Request) -> Response: - """Get a list of all the types of AllowDenyLists we support.""" - return Response(AllowDenyList.AllowDenyListType.choices) diff --git a/pydis_site/apps/api/viewsets/bot/filter_list.py b/pydis_site/apps/api/viewsets/bot/filter_list.py new file mode 100644 index 00000000..2cb21ab9 --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/filter_list.py @@ -0,0 +1,97 @@ +from rest_framework.decorators import action +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.viewsets import ModelViewSet + +from pydis_site.apps.api.models.bot.filter_list import FilterList +from pydis_site.apps.api.serializers import FilterListSerializer + + +class FilterListViewSet(ModelViewSet): + """ + View providing CRUD operations on items allowed or denied by our bot. + + ## Routes + ### GET /bot/filter-lists + Returns all filterlist items in the database. + + #### Response format + >>> [ + ... { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... 'comment': "Popular image format.", + ... }, + ... ... + ... ] + + #### Status codes + - 200: returned on success + - 401: returned if unauthenticated + + ### GET /bot/filter-lists/ + Returns a specific FilterList item from the database. + + #### Response format + >>> { + ... 'id': "2309268224", + ... 'created_at': "01-01-2020 ...", + ... 'updated_at': "01-01-2020 ...", + ... 'type': "file_format", + ... 'allowed': 'true', + ... 'content': ".jpeg", + ... 'comment': "Popular image format.", + ... } + + #### Status codes + - 200: returned on success + - 404: returned if the id was not found. + + ### GET /bot/filter-lists/get-types + Returns a list of valid list types that can be used in POST requests. + + #### Response format + >>> [ + ... ["GUILD_INVITE","Guild Invite"], + ... ["FILE_FORMAT","File Format"], + ... ["DOMAIN_NAME","Domain Name"], + ... ["FILTER_TOKEN","Filter Token"] + ... ] + + #### Status codes + - 200: returned on success + + ### POST /bot/filter-lists + Adds a single FilterList item to the database. + + #### Request body + >>> { + ... 'type': str, + ... 'allowed': bool, + ... 'content': str, + ... 'comment': Optional[str], + ... } + + #### Status codes + - 201: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/filter-lists/ + Deletes the FilterList item with the given `id`. + + #### Status codes + - 204: returned on success + - 404: if a tag with the given `id` does not exist + """ + + serializer_class = FilterListSerializer + queryset = FilterList.objects.all() + + @action(detail=False, url_path='get-types', methods=["get"]) + def get_types(self, _: Request) -> Response: + """Get a list of all the types of FilterLists we support.""" + return Response(FilterList.FilterListType.choices) -- cgit v1.2.3 From 0d7c95a80692f15c36b36152034e3bbe54e3c37c Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 29 Jul 2020 13:55:46 +0200 Subject: Handle unique validator in DRF, not Django. I was handling this in a Django vanilla kind of way, which was causing the constraint to return a 500 instead of a 400. This changes the approach to use the DRF way, and makes it return 400. It doesn't actually change the way anything behaves, other than returning the right status code. --- .../api/migrations/0057_create_new_filterlist_model.py | 4 ---- pydis_site/apps/api/models/bot/filter_list.py | 11 ----------- pydis_site/apps/api/serializers.py | 15 +++++++++++++++ 3 files changed, 15 insertions(+), 15 deletions(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py b/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py index 44025ee8..b5398568 100644 --- a/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py +++ b/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py @@ -25,9 +25,5 @@ class Migration(migrations.Migration): ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), - ), - migrations.AddConstraint( - model_name='filterlist', - constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_filter_list'), ) ] diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py index d279e137..5961aed7 100644 --- a/pydis_site/apps/api/models/bot/filter_list.py +++ b/pydis_site/apps/api/models/bot/filter_list.py @@ -28,14 +28,3 @@ class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model): help_text="Optional comment on this entry.", null=True ) - - class Meta: - """Metaconfig for this model.""" - - # This constraint ensures only one filterlist with the - # same content can exist. This means that we cannot have both an allow - # and a deny for the same item, and we cannot have duplicates of the - # same item. - constraints = [ - models.UniqueConstraint(fields=['content', 'type'], name='unique_filter_list'), - ] diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 8fd40da7..54bb8a5d 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -113,6 +113,21 @@ class FilterListSerializer(ModelSerializer): model = FilterList fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content', 'comment') + # This validator ensures only one filterlist with the + # same content can exist. This means that we cannot have both an allow + # and a deny for the same item, and we cannot have duplicates of the + # same item. + validators = [ + UniqueTogetherValidator( + queryset=FilterList.objects.all(), + fields=['content', 'type'], + message=( + "A filterlist for this item already exists. " + "Please note that you cannot add the same item to both allow and deny." + ) + ), + ] + class InfractionSerializer(ModelSerializer): """A class providing (de-)serialization of `Infraction` instances.""" -- cgit v1.2.3 From b4b99c5b56833409c68b89d807c9b9f6d385e7f3 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 29 Jul 2020 14:10:31 +0200 Subject: Add a test for checking duplicates. --- pydis_site/apps/api/tests/test_filterlists.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_filterlists.py b/pydis_site/apps/api/tests/test_filterlists.py index b577c31c..47aaaf31 100644 --- a/pydis_site/apps/api/tests/test_filterlists.py +++ b/pydis_site/apps/api/tests/test_filterlists.py @@ -88,6 +88,11 @@ class CreationTests(APISubdomainTestCase): response = self.client.post(URL, data=JPEG_ALLOWLIST) self.assertEqual(response.status_code, 201) + def test_returns_400_for_duplicate_creation(self): + self.client.post(URL, data=JPEG_ALLOWLIST) + response = self.client.post(URL, data=JPEG_ALLOWLIST) + self.assertEqual(response.status_code, 400) + class DeletionTests(APISubdomainTestCase): @classmethod -- cgit v1.2.3 From f15a345556b1902f3314d4edcc8ae3cf23da7a62 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 29 Jul 2020 18:19:15 +0200 Subject: Fix multiple leafs for migration graph. --- .../migrations/0057_create_new_filterlist_model.py | 29 ---------------------- .../migrations/0058_create_new_filterlist_model.py | 29 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 29 deletions(-) delete mode 100644 pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py create mode 100644 pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py b/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py deleted file mode 100644 index b5398568..00000000 --- a/pydis_site/apps/api/migrations/0057_create_new_filterlist_model.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.0.8 on 2020-07-15 11:23 - -from django.db import migrations, models -import pydis_site.apps.api.models.mixins - - -class Migration(migrations.Migration): - dependencies = [ - ('api', '0056_allow_blank_user_roles'), - ] - - operations = [ - migrations.CreateModel( - name='FilterList', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('type', models.CharField( - choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'), - ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token')], - help_text='The type of allowlist this is on.', max_length=50)), - ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), - ('content', models.TextField(help_text='The data to add to the allow or denylist.')), - ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)), - ], - bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), - ) - ] diff --git a/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py b/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py new file mode 100644 index 00000000..fffb8159 --- /dev/null +++ b/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py @@ -0,0 +1,29 @@ +# Generated by Django 3.0.8 on 2020-07-15 11:23 + +from django.db import migrations, models +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + dependencies = [ + ('api', '0057_merge_20200716_0751'), + ] + + operations = [ + migrations.CreateModel( + name='FilterList', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('type', models.CharField( + choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'), + ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token')], + help_text='The type of allowlist this is on.', max_length=50)), + ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')), + ('content', models.TextField(help_text='The data to add to the allow or denylist.')), + ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)), + ], + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ) + ] -- cgit v1.2.3 From 12f447bc9636382a623e6b30ae43ccc036c488b5 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Wed, 29 Jul 2020 19:07:03 +0200 Subject: Add a constraint in the DB model as well. This really should've been handled automatically by DRF, and in the future, it will be. But for now, we need to have constraints both on the serializer (to get status code 400), and on the model (to prevent direct database constraint violations). See https://github.com/encode/django-rest-framework/issues/7173 --- .../apps/api/migrations/0058_create_new_filterlist_model.py | 4 ++++ pydis_site/apps/api/models/bot/filter_list.py | 11 +++++++++++ 2 files changed, 15 insertions(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py b/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py index fffb8159..aecfdad7 100644 --- a/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py +++ b/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py @@ -25,5 +25,9 @@ class Migration(migrations.Migration): ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + migrations.AddConstraint( + model_name='filterlist', + constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_filter_list') ) ] diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py index 5961aed7..d279e137 100644 --- a/pydis_site/apps/api/models/bot/filter_list.py +++ b/pydis_site/apps/api/models/bot/filter_list.py @@ -28,3 +28,14 @@ class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model): help_text="Optional comment on this entry.", null=True ) + + class Meta: + """Metaconfig for this model.""" + + # This constraint ensures only one filterlist with the + # same content can exist. This means that we cannot have both an allow + # and a deny for the same item, and we cannot have duplicates of the + # same item. + constraints = [ + models.UniqueConstraint(fields=['content', 'type'], name='unique_filter_list'), + ] -- cgit v1.2.3 From 82f2297fbdaa98e085655a9e66540980e3081210 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 30 Jul 2020 10:19:41 +0200 Subject: Provide callable, not return value. Co-authored-by: Sebastiaan Zeeff <33516116+SebastiaanZ@users.noreply.github.com> --- .../api/migrations/0025_allow_custom_inserted_at_infraction_field.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py b/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py index 9589346d..c7fac012 100644 --- a/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py +++ b/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='infraction', name='inserted_at', - field=models.DateTimeField(default=timezone.now(), help_text='The date and time of the creation of this infraction.'), + field=models.DateTimeField(default=timezone.now, help_text='The date and time of the creation of this infraction.'), ), ] -- cgit v1.2.3 From 322004e10f023f197e5fd07b0a87e0a10f45ea35 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 30 Jul 2020 11:29:01 +0200 Subject: Add a migration for all the existing data. This will populate the table with domain names, guild invites, filter tokens and file formats. --- .../api/migrations/0059_populate_filterlists.py | 153 +++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0059_populate_filterlists.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0059_populate_filterlists.py b/pydis_site/apps/api/migrations/0059_populate_filterlists.py new file mode 100644 index 00000000..8c550191 --- /dev/null +++ b/pydis_site/apps/api/migrations/0059_populate_filterlists.py @@ -0,0 +1,153 @@ +from django.db import migrations + +guild_invite_whitelist = [ + ("discord.gg/python", "Python Discord", True), + ("discord.gg/4JJdJKb", "RLBot", True), + ("discord.gg/djPtTRJ", "Kivy", True), + ("discord.gg/QXyegWe", "Pyglet", True), + ("discord.gg/9XsucTT", "Panda3D", True), + ("discord.gg/AP3rq2k", "PyWeek", True), + ("discord.gg/vSPsP9t", "Microsoft Python", True), + ("discord.gg/bRCvFy9", "Discord.js Official", True), + ("discord.gg/9zT7NHP", "Programming Discussions", True), + ("discord.gg/ysd6M4r", "JetBrains Community", True), + ("discord.gg/4xJeCgy", "Raspberry Pie", True), + ("discord.gg/AStb3kZ", "Ren'Py", True), + ("discord.gg/t655QNV", "Python Discord: Emojis 1", True), + ("discord.gg/vRZPkqC", "Python Discord: Emojis 2", True), + ("discord.gg/jTtgWuy", "Django", True), + ("discord.gg/W9BypZF", "STEM", True), + ("discord.gg/dpy", "discord.py", True), + ("discord.gg/programming", "Programmers Hangout", True), + ("discord.gg/qhGUjGD", "SpeakJS", True), + ("discord.gg/eTbWSZj", "Functional Programming", True), + ("discord.gg/r8yreB6", "PyGame", True), + ("discord.gg/5UBnR3P", "Python Atlanta", True), + ("discord.gg/ccyrDKv", "C#", True), +] + +domain_name_blacklist = [ + ("pornhub.com", None, False), + ("liveleak.com", None, False), + ("grabify.link", None, False), + ("bmwforum.co", None, False), + ("leancoding.co", None, False), + ("spottyfly.com", None, False), + ("stopify.co", None, False), + ("yoütu.be", None, False), + ("discörd.com", None, False), + ("minecräft.com", None, False), + ("freegiftcards.co", None, False), + ("disçordapp.com", None, False), + ("fortnight.space", None, False), + ("fortnitechat.site", None, False), + ("joinmy.site", None, False), + ("curiouscat.club", None, False), + ("catsnthings.fun", None, False), + ("yourtube.site", None, False), + ("youtubeshort.watch", None, False), + ("catsnthing.com", None, False), + ("youtubeshort.pro", None, False), + ("canadianlumberjacks.online", None, False), + ("poweredbydialup.club", None, False), + ("poweredbydialup.online", None, False), + ("poweredbysecurity.org", None, False), + ("poweredbysecurity.online", None, False), + ("ssteam.site", None, False), + ("steamwalletgift.com", None, False), + ("discord.gift", None, False), + ("lmgtfy.com", None, False), +] + +filter_token_blacklist = [ + ("\bgoo+ks*\b", None, False), + ("\bky+s+\b", None, False), + ("\bki+ke+s*\b", None, False), + ("\bbeaner+s?\b", None, False), + ("\bcoo+ns*\b", None, False), + ("\bnig+lets*\b", None, False), + ("\bslant-eyes*\b", None, False), + ("\btowe?l-?head+s*\b", None, False), + ("\bchi*n+k+s*\b", None, False), + ("\bspick*s*\b", None, False), + ("\bkill* +(?:yo)?urself+\b", None, False), + ("\bjew+s*\b", None, False), + ("\bsuicide\b", None, False), + ("\brape\b", None, False), + ("\b(re+)tar+(d+|t+)(ed)?\b", None, False), + ("\bta+r+d+\b", None, False), + ("\bcunts*\b", None, False), + ("\btrann*y\b", None, False), + ("\bshemale\b", None, False), + ("fa+g+s*", None, False), + ("卐", None, False), + ("卍", None, False), + ("࿖", None, False), + ("࿕", None, False), + ("࿘", None, False), + ("࿗", None, False), + ("cuck(?!oo+)", None, False), + ("nigg+(?:e*r+|a+h*?|u+h+)s?", None, False), + ("fag+o+t+s*", None, False), +] + +file_format_whitelist = [ + (".3gp", None, True), + (".3g2", None, True), + (".avi", None, True), + (".bmp", None, True), + (".gif", None, True), + (".h264", None, True), + (".jpg", None, True), + (".jpeg", None, True), + (".m4v", None, True), + (".mkv", None, True), + (".mov", None, True), + (".mp4", None, True), + (".mpeg", None, True), + (".mpg", None, True), + (".png", None, True), + (".tiff", None, True), + (".wmv", None, True), + (".svg", None, True), + (".psd", "Photoshop", True), + (".ai", "Illustrator", True), + (".aep", "After Effects", True), + (".xcf", "GIMP", True), + (".mp3", None, True), + (".wav", None, True), + (".ogg", None, True), + (".webm", None, True), + (".webp", None, True), +] + +populate_data = { + "FILTER_TOKEN": filter_token_blacklist, + "DOMAIN_NAME": domain_name_blacklist, + "FILE_FORMAT": file_format_whitelist, + "GUILD_INVITE": guild_invite_whitelist, +} + + +class Migration(migrations.Migration): + dependencies = [("api", "0058_create_new_filterlist_model")] + + def populate_filterlists(app, _): + FilterList = app.get_model("api", "FilterList") + + for filterlist_type, metadata in populate_data.items(): + for content, comment, allowed in metadata: + FilterList.objects.create( + type=filterlist_type, + allowed=allowed, + content=content, + comment=comment, + ) + + def clear_filterlists(app, _): + FilterList = app.get_model("api", "FilterList") + FilterList.objects.all().delete() + + operations = [ + migrations.RunPython(populate_filterlists, clear_filterlists) + ] -- cgit v1.2.3 From de89a3c39c306274a75a41b528fa14155b00f392 Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Thu, 30 Jul 2020 12:38:31 +0200 Subject: Delete FilterList objects for tests. Now that we have a migration that adds data, we can no longer have tests that operate on the assumption that the database is going to be empty. So, we're now clearing that table before these tests run. --- pydis_site/apps/api/tests/test_filterlists.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_filterlists.py b/pydis_site/apps/api/tests/test_filterlists.py index 47aaaf31..188c0fff 100644 --- a/pydis_site/apps/api/tests/test_filterlists.py +++ b/pydis_site/apps/api/tests/test_filterlists.py @@ -28,6 +28,10 @@ class UnauthenticatedTests(APISubdomainTestCase): class EmptyDatabaseTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + FilterList.objects.all().delete() + def test_returns_empty_object(self): response = self.client.get(URL) @@ -38,6 +42,7 @@ class EmptyDatabaseTests(APISubdomainTestCase): class FetchTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): + FilterList.objects.all().delete() cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST) cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST) @@ -64,6 +69,10 @@ class FetchTests(APISubdomainTestCase): class CreationTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + FilterList.objects.all().delete() + def test_returns_400_for_missing_params(self): no_type_json = { "allowed": True, @@ -97,6 +106,7 @@ class CreationTests(APISubdomainTestCase): class DeletionTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): + FilterList.objects.all().delete() cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST) cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST) -- cgit v1.2.3 From e4cec0aeb2a791e622be8edd94fb4e82d150deab Mon Sep 17 00:00:00 2001 From: Leon Sandøy Date: Mon, 3 Aug 2020 12:53:39 +0200 Subject: Fix bad migration 0059 This had the wrong data for GUILD_INVITES - it had invites instead of Guild IDs. This new migration solves this problem. --- .../migrations/0060_populate_filterlists_fix.py | 85 ++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py (limited to 'pydis_site') diff --git a/pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py b/pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py new file mode 100644 index 00000000..53846f02 --- /dev/null +++ b/pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py @@ -0,0 +1,85 @@ +from django.db import migrations + +bad_guild_invite_whitelist = [ + ("discord.gg/python", "Python Discord", True), + ("discord.gg/4JJdJKb", "RLBot", True), + ("discord.gg/djPtTRJ", "Kivy", True), + ("discord.gg/QXyegWe", "Pyglet", True), + ("discord.gg/9XsucTT", "Panda3D", True), + ("discord.gg/AP3rq2k", "PyWeek", True), + ("discord.gg/vSPsP9t", "Microsoft Python", True), + ("discord.gg/bRCvFy9", "Discord.js Official", True), + ("discord.gg/9zT7NHP", "Programming Discussions", True), + ("discord.gg/ysd6M4r", "JetBrains Community", True), + ("discord.gg/4xJeCgy", "Raspberry Pie", True), + ("discord.gg/AStb3kZ", "Ren'Py", True), + ("discord.gg/t655QNV", "Python Discord: Emojis 1", True), + ("discord.gg/vRZPkqC", "Python Discord: Emojis 2", True), + ("discord.gg/jTtgWuy", "Django", True), + ("discord.gg/W9BypZF", "STEM", True), + ("discord.gg/dpy", "discord.py", True), + ("discord.gg/programming", "Programmers Hangout", True), + ("discord.gg/qhGUjGD", "SpeakJS", True), + ("discord.gg/eTbWSZj", "Functional Programming", True), + ("discord.gg/r8yreB6", "PyGame", True), + ("discord.gg/5UBnR3P", "Python Atlanta", True), + ("discord.gg/ccyrDKv", "C#", True), +] + +guild_invite_whitelist = [ + ("267624335836053506", "Python Discord", True), + ("348658686962696195", "RLBot", True), + ("423249981340778496", "Kivy", True), + ("438622377094414346", "Pyglet", True), + ("524691714909274162", "Panda3D", True), + ("666560367173828639", "PyWeek", True), + ("702724176489873509", "Microsoft Python", True), + ("222078108977594368", "Discord.js Official", True), + ("238666723824238602", "Programming Discussions", True), + ("433980600391696384", "JetBrains Community", True), + ("204621105720328193", "Raspberry Pie", True), + ("286633898581164032", "Ren'Py", True), + ("440186186024222721", "Python Discord: Emojis 1", True), + ("578587418123304970", "Python Discord: Emojis 2", True), + ("159039020565790721", "Django", True), + ("273944235143593984", "STEM", True), + ("336642139381301249", "discord.py", True), + ("244230771232079873", "Programmers Hangout", True), + ("239433591950540801", "SpeakJS", True), + ("280033776820813825", "Functional Programming", True), + ("349505959032389632", "PyGame", True), + ("488751051629920277", "Python Atlanta", True), + ("143867839282020352", "C#", True), +] + + +class Migration(migrations.Migration): + dependencies = [("api", "0059_populate_filterlists")] + + def fix_filterlist(app, _): + FilterList = app.get_model("api", "FilterList") + FilterList.objects.filter(type="GUILD_INVITE").delete() # Clear out the stuff added in 0059. + + for content, comment, allowed in guild_invite_whitelist: + FilterList.objects.create( + type="GUILD_INVITE", + allowed=allowed, + content=content, + comment=comment, + ) + + def restore_bad_filterlist(app, _): + FilterList = app.get_model("api", "FilterList") + FilterList.objects.filter(type="GUILD_INVITE").delete() + + for content, comment, allowed in bad_guild_invite_whitelist: + FilterList.objects.create( + type="GUILD_INVITE", + allowed=allowed, + content=content, + comment=comment, + ) + + operations = [ + migrations.RunPython(fix_filterlist, restore_bad_filterlist) + ] -- cgit v1.2.3 From 518a39b3e2b56f942328695d5e44674976317c7c Mon Sep 17 00:00:00 2001 From: Dennis Pham Date: Sat, 8 Aug 2020 14:46:04 -0400 Subject: Update rule 6 for the removal of #show-your-projects --- pydis_site/apps/api/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/views.py b/pydis_site/apps/api/views.py index a73d4718..7ac56641 100644 --- a/pydis_site/apps/api/views.py +++ b/pydis_site/apps/api/views.py @@ -140,7 +140,8 @@ class RulesView(APIView): ), ( "No spamming or unapproved advertising, including requests for paid work. " - "Open-source projects can be showcased in #show-your-projects." + "Open-source projects can be shared with others in #python-general and " + "code reviews can be asked for in a help channel." ), ( "Keep discussions relevant to channel topics and guidelines." -- cgit v1.2.3 From 4e67dbc2512653536de3eb603c537c8e40f058f6 Mon Sep 17 00:00:00 2001 From: Dennis Pham Date: Thu, 13 Aug 2020 18:22:14 -0400 Subject: Update Code Jam 7 to most recent in navbar --- pydis_site/templates/base/navbar.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/templates/base/navbar.html b/pydis_site/templates/base/navbar.html index f07662ae..c2915025 100644 --- a/pydis_site/templates/base/navbar.html +++ b/pydis_site/templates/base/navbar.html @@ -88,7 +88,7 @@ Events - Upcoming: Code Jam 7 + Most Recent: Code Jam 7 All events -- cgit v1.2.3 From 0ba6e25f88400e9a54ae9be2c176687ad69bb94f Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Thu, 20 Aug 2020 15:15:21 +0200 Subject: Allow direct fetching of reminders by id --- pydis_site/apps/api/tests/test_reminders.py | 28 ++++++++++++++++++++++++++++ pydis_site/apps/api/viewsets/bot/reminder.py | 8 +++++++- 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py index a05d9296..9dffb668 100644 --- a/pydis_site/apps/api/tests/test_reminders.py +++ b/pydis_site/apps/api/tests/test_reminders.py @@ -163,6 +163,34 @@ class ReminderListTests(APISubdomainTestCase): self.assertEqual(response.json(), [self.rem_dict_one]) +class ReminderRetrieveTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): + cls.author = User.objects.create( + id=6789, + name='Reminder author', + discriminator=6789, + ) + + cls.reminder = Reminder.objects.create( + author=cls.author, + content="Reminder content", + expiration=datetime.utcnow().isoformat(), + jump_url="http://example.com/", + channel_id=123 + ) + + def test_retrieve_unknown_returns_404(self): + url = reverse('bot:reminder-detail', args=("not_an_id",), host='api') + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_retrieve_known_returns_200(self): + url = reverse('bot:reminder-detail', args=(self.reminder.id,), host='api') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + class ReminderUpdateTests(APISubdomainTestCase): @classmethod def setUpTestData(cls): diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py index 6f8a28f2..00413fb1 100644 --- a/pydis_site/apps/api/viewsets/bot/reminder.py +++ b/pydis_site/apps/api/viewsets/bot/reminder.py @@ -4,6 +4,7 @@ from rest_framework.mixins import ( CreateModelMixin, DestroyModelMixin, ListModelMixin, + RetrieveModelMixin, UpdateModelMixin ) from rest_framework.viewsets import GenericViewSet @@ -13,7 +14,12 @@ from pydis_site.apps.api.serializers import ReminderSerializer class ReminderViewSet( - CreateModelMixin, ListModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet + CreateModelMixin, + RetrieveModelMixin, + ListModelMixin, + DestroyModelMixin, + UpdateModelMixin, + GenericViewSet, ): """ View providing CRUD access to reminders. -- cgit v1.2.3 From 700de6b08b70f40dc97f3644d4e0ee32ab8f6ccf Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Thu, 20 Aug 2020 15:24:29 +0200 Subject: Update docstring for new fetching behaviour --- pydis_site/apps/api/viewsets/bot/reminder.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'pydis_site') diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py index 00413fb1..111660d9 100644 --- a/pydis_site/apps/api/viewsets/bot/reminder.py +++ b/pydis_site/apps/api/viewsets/bot/reminder.py @@ -50,6 +50,30 @@ class ReminderViewSet( #### Status codes - 200: returned on success + ### GET /bot/reminders/ + Fetches the reminder with the given id. + + #### Response format + >>> + ... { + ... 'active': True, + ... 'author': 1020103901030, + ... 'mentions': [ + ... 336843820513755157, + ... 165023948638126080, + ... 267628507062992896 + ... ], + ... 'content': "Make dinner", + ... 'expiration': '5018-11-20T15:52:00Z', + ... 'id': 11, + ... 'channel_id': 634547009956872193, + ... 'jump_url': "https://discord.com/channels///" + ... } + + #### Status codes + - 200: returned on success + - 404: returned when the reminder doesn't exist + ### POST /bot/reminders Create a new reminder. -- cgit v1.2.3