From 3e10b0cf19916f10be0aed9c8d60c9dfd7a016a5 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Thu, 27 Jan 2022 20:30:16 +0000 Subject: Use voice_mute and voice_ban to determine voice_gate Previously only voice_ban was used, but now having either of these infractions should mean the user is blocked from reciving the role. --- pydis_site/apps/api/viewsets/bot/user.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index 1a5e79f8..a867a80f 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -1,7 +1,7 @@ import typing from collections import OrderedDict -from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Q from rest_framework import status from rest_framework.decorators import action from rest_framework.pagination import PageNumberPagination @@ -261,12 +261,10 @@ class UserViewSet(ModelViewSet): """Request handler for metricity_data endpoint.""" user = self.get_object() - try: - Infraction.objects.get(user__id=user.id, active=True, type="voice_ban") - except ObjectDoesNotExist: - voice_banned = False - else: - voice_banned = True + has_voice_infraction = Infraction.objects.filter( + Q(user__id=user.id, active=True), + Q(type="voice_ban") | Q(type="voice_mute") + ).exists() with Metricity() as metricity: try: @@ -275,7 +273,7 @@ class UserViewSet(ModelViewSet): data["total_messages"] = metricity.total_messages(user.id) data["activity_blocks"] = metricity.total_message_blocks(user.id) - data["voice_banned"] = voice_banned + data["voice_gate_blocked"] = has_voice_infraction return Response(data, status=status.HTTP_200_OK) except NotFoundError: return Response(dict(detail="User not found in metricity"), -- cgit v1.2.3 From 6c9fb075dc52d5674f90698cb74138712f7d99e1 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 30 Jan 2022 15:34:04 +0100 Subject: Allow searching users by username and discriminator A test case is added to demonstrate this functionality. Closes #578. Co-authored-by: Boris Muratov <8bee278@gmail.com> --- pydis_site/apps/api/tests/test_users.py | 43 ++++++++++++++++++++++++++++++++ pydis_site/apps/api/viewsets/bot/user.py | 5 ++++ 2 files changed, 48 insertions(+) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index e21bb32b..5d10069d 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -1,3 +1,4 @@ +import random from unittest.mock import Mock, patch from django.urls import reverse @@ -520,3 +521,45 @@ class UserMetricityTests(AuthenticatedAPITestCase): self.metricity.total_messages.side_effect = NotFoundError() self.metricity.total_message_blocks.side_effect = NotFoundError() self.metricity.top_channel_activity.side_effect = NotFoundError() + + +class UserViewSetTests(AuthenticatedAPITestCase): + @classmethod + def setUpTestData(cls): + cls.searched_user = User.objects.create( + id=12095219, + name=f"Test user {random.randint(100, 1000)}", + discriminator=random.randint(1, 9999), + in_guild=True, + ) + cls.other_user = User.objects.create( + id=18259125, + name=f"Test user {random.randint(100, 1000)}", + discriminator=random.randint(1, 9999), + in_guild=True, + ) + + def test_search_lookup_of_wanted_user(self) -> None: + """Searching a user by name and discriminator should return that user.""" + url = reverse('api:bot:user-list') + params = { + 'username': self.searched_user.name, + 'discriminator': self.searched_user.discriminator, + } + response = self.client.get(url, params) + result = response.json() + self.assertEqual(result['count'], 1) + [user] = result['results'] + self.assertEqual(user['id'], self.searched_user.id) + + def test_search_lookup_of_unknown_user(self) -> None: + """Searching an unknown user should return no results.""" + url = reverse('api:bot:user-list') + params = { + 'username': "f-string enjoyer", + 'discriminator': 1245, + } + response = self.client.get(url, params) + result = response.json() + self.assertEqual(result['count'], 0) + self.assertEqual(result['results'], []) diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index a867a80f..3318b2b9 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -2,6 +2,7 @@ import typing from collections import OrderedDict from django.db.models import Q +from django_filters.rest_framework import DjangoFilterBackend from rest_framework import status from rest_framework.decorators import action from rest_framework.pagination import PageNumberPagination @@ -77,6 +78,8 @@ class UserViewSet(ModelViewSet): ... } #### Optional Query Parameters + - username: username to search for + - discriminator: discriminator to search for - page_size: number of Users in one page, defaults to 10,000 - page: page number @@ -233,6 +236,8 @@ class UserViewSet(ModelViewSet): serializer_class = UserSerializer queryset = User.objects.all().order_by("id") pagination_class = UserListPagination + filter_backends = (DjangoFilterBackend,) + filter_fields = ('name', 'discriminator') def get_serializer(self, *args, **kwargs) -> ModelSerializer: """Set Serializer many attribute to True if request body contains a list.""" -- cgit v1.2.3 From 15cac33dd74219a762d3c066efca383552f666b0 Mon Sep 17 00:00:00 2001 From: Rohan Reddy Alleti Date: Thu, 17 Feb 2022 02:55:28 +0530 Subject: Return random off topic names which are Active only (#644) Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- .../apps/api/tests/test_off_topic_channel_names.py | 35 +++++++++++++++++----- .../api/viewsets/bot/off_topic_channel_name.py | 3 +- 2 files changed, 29 insertions(+), 9 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/tests/test_off_topic_channel_names.py b/pydis_site/apps/api/tests/test_off_topic_channel_names.py index 2d273756..34098c92 100644 --- a/pydis_site/apps/api/tests/test_off_topic_channel_names.py +++ b/pydis_site/apps/api/tests/test_off_topic_channel_names.py @@ -74,6 +74,9 @@ class ListTests(AuthenticatedAPITestCase): cls.test_name_3 = OffTopicChannelName.objects.create( name="frozen-with-iceman", used=True, active=False ) + cls.test_name_4 = OffTopicChannelName.objects.create( + name="xith-is-cool", used=True, active=True + ) def test_returns_name_in_list(self): """Return all off-topic channel names.""" @@ -86,28 +89,46 @@ class ListTests(AuthenticatedAPITestCase): { self.test_name.name, self.test_name_2.name, - self.test_name_3.name + self.test_name_3.name, + self.test_name_4.name } ) - def test_returns_two_items_with_random_items_param_set_to_2(self): - """Return not-used name instead used.""" + def test_returns_two_active_items_with_random_items_param_set_to_2(self): + """Return not-used active names instead used.""" url = reverse('api:bot:offtopicchannelname-list') response = self.client.get(f'{url}?random_items=2') self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 2) - self.assertEqual(set(response.json()), {self.test_name.name, self.test_name_2.name}) + self.assertTrue( + all( + item in (self.test_name.name, self.test_name_2.name, self.test_name_4.name) + for item in response.json() + ) + ) + + def test_returns_three_active_items_with_random_items_param_set_to_3(self): + """Return not-used active names instead used.""" + url = reverse('api:bot:offtopicchannelname-list') + response = self.client.get(f'{url}?random_items=3') + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()), 3) + self.assertEqual( + set(response.json()), + {self.test_name.name, self.test_name_2.name, self.test_name_4.name} + ) def test_running_out_of_names_with_random_parameter(self): - """Reset names `used` parameter to `False` when running out of names.""" + """Reset names `used` parameter to `False` when running out of active names.""" url = reverse('api:bot:offtopicchannelname-list') response = self.client.get(f'{url}?random_items=3') self.assertEqual(response.status_code, 200) self.assertEqual( set(response.json()), - {self.test_name.name, self.test_name_2.name, self.test_name_3.name} + {self.test_name.name, self.test_name_2.name, self.test_name_4.name} ) def test_returns_inactive_ot_names(self): @@ -129,7 +150,7 @@ class ListTests(AuthenticatedAPITestCase): self.assertEqual(response.status_code, 200) self.assertEqual( set(response.json()), - {self.test_name.name, self.test_name_2.name} + {self.test_name.name, self.test_name_2.name, self.test_name_4.name} ) diff --git a/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py b/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py index 78f8c340..d0519e86 100644 --- a/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py +++ b/pydis_site/apps/api/viewsets/bot/off_topic_channel_name.py @@ -108,7 +108,7 @@ class OffTopicChannelNameViewSet(ModelViewSet): 'random_items': ["Must be a positive integer."] }) - queryset = self.queryset.order_by('used', '?')[:random_count] + queryset = self.queryset.filter(active=True).order_by('used', '?')[:random_count] # When any name is used in our listing then this means we reached end of round # and we need to reset all other names `used` to False @@ -133,7 +133,6 @@ class OffTopicChannelNameViewSet(ModelViewSet): return Response(serialized.data) params = {} - if active_param := request.query_params.get("active"): params["active"] = active_param.lower() == "true" -- cgit v1.2.3 From 26e4f518c874cafdee594c08c01d610e88528dc7 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sun, 20 Feb 2022 17:43:54 +0100 Subject: Prevent race condition with duplicate infractions DRF's `UniqueTogetherValidator` validates uniqueness by querying the database before running the actual insert. This is not, has not, and will never be valid, unless you happen to run a single worker, on a single thread, and your single worker running on a single thread is the only client for the database, in which case it may be valid. For any other cases, it's invalid, and it has never been valid. PostgreSQL spits out an `IntegrityError` for us if we have a duplicate entry, and PostgreSQL is the only valid and correct thing to trust here. The `UniqueTogetherValidator` is removed, and an existing test case calling into this validator to check for uniqueness is removed. Furthermore, to work around a Django quirk, `transaction.atomic()` is added to prevent one `subTest` from messing with another. Closes #665. --- pydis_site/apps/api/serializers.py | 7 --- pydis_site/apps/api/tests/test_infractions.py | 77 +++++++++++--------------- pydis_site/apps/api/viewsets/bot/infraction.py | 18 ++++++ 3 files changed, 50 insertions(+), 52 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 4a702d61..745aff42 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -156,13 +156,6 @@ class InfractionSerializer(ModelSerializer): 'hidden', 'dm_sent' ) - validators = [ - UniqueTogetherValidator( - queryset=Infraction.objects.filter(active=True), - fields=['user', 'type', 'active'], - message='This user already has an active infraction of this type.', - ) - ] def validate(self, attrs: dict) -> dict: """Validate data constraints for the given data and abort if it is invalid.""" diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py index b3dd16ee..aa0604f6 100644 --- a/pydis_site/apps/api/tests/test_infractions.py +++ b/pydis_site/apps/api/tests/test_infractions.py @@ -3,6 +3,7 @@ from datetime import datetime as dt, timedelta, timezone from unittest.mock import patch from urllib.parse import quote +from django.db import transaction from django.db.utils import IntegrityError from django.urls import reverse @@ -492,6 +493,7 @@ class CreationTests(AuthenticatedAPITestCase): ) for infraction_type, hidden in restricted_types: + # https://stackoverflow.com/a/23326971 with self.subTest(infraction_type=infraction_type): invalid_infraction = { 'user': self.user.id, @@ -516,37 +518,38 @@ class CreationTests(AuthenticatedAPITestCase): for infraction_type in active_infraction_types: with self.subTest(infraction_type=infraction_type): - first_active_infraction = { - 'user': self.user.id, - 'actor': self.user.id, - 'type': infraction_type, - 'reason': 'Take me on!', - 'active': True, - 'expires_at': '2019-10-04T12:52:00+00:00' - } - - # Post the first active infraction of a type and confirm it's accepted. - first_response = self.client.post(url, data=first_active_infraction) - self.assertEqual(first_response.status_code, 201) + with transaction.atomic(): + first_active_infraction = { + 'user': self.user.id, + 'actor': self.user.id, + 'type': infraction_type, + 'reason': 'Take me on!', + 'active': True, + 'expires_at': '2019-10-04T12:52:00+00:00' + } - second_active_infraction = { - 'user': self.user.id, - 'actor': self.user.id, - 'type': infraction_type, - 'reason': 'Take on me!', - 'active': True, - 'expires_at': '2019-10-04T12:52:00+00:00' - } - second_response = self.client.post(url, data=second_active_infraction) - self.assertEqual(second_response.status_code, 400) - self.assertEqual( - second_response.json(), - { - 'non_field_errors': [ - 'This user already has an active infraction of this type.' - ] + # Post the first active infraction of a type and confirm it's accepted. + first_response = self.client.post(url, data=first_active_infraction) + self.assertEqual(first_response.status_code, 201) + + second_active_infraction = { + 'user': self.user.id, + 'actor': self.user.id, + 'type': infraction_type, + 'reason': 'Take on me!', + 'active': True, + 'expires_at': '2019-10-04T12:52:00+00:00' } - ) + second_response = self.client.post(url, data=second_active_infraction) + self.assertEqual(second_response.status_code, 400) + self.assertEqual( + second_response.json(), + { + 'non_field_errors': [ + 'This user already has an active infraction of this type.' + ] + } + ) def test_returns_201_for_second_active_infraction_of_different_type(self): """Test if the API accepts a second active infraction of a different type than the first.""" @@ -811,22 +814,6 @@ class SerializerTests(AuthenticatedAPITestCase): self.assertTrue(serializer.is_valid(), msg=serializer.errors) - def test_validation_error_if_active_duplicate(self): - self.create_infraction('ban', active=True) - instance = self.create_infraction('ban', active=False) - - data = {'active': True} - serializer = InfractionSerializer(instance, data=data, partial=True) - - if not serializer.is_valid(): - self.assertIn('non_field_errors', serializer.errors) - - code = serializer.errors['non_field_errors'][0].code - msg = f'Expected failure on unique validator but got {serializer.errors}' - self.assertEqual(code, 'unique', msg=msg) - else: # pragma: no cover - self.fail('Validation unexpectedly succeeded.') - def test_is_valid_for_new_active_infraction(self): self.create_infraction('ban', active=False) diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index 8a48ed1f..31e8ba40 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -1,5 +1,6 @@ from datetime import datetime +from django.db import IntegrityError from django.db.models import QuerySet from django.http.request import HttpRequest from django_filters.rest_framework import DjangoFilterBackend @@ -271,3 +272,20 @@ class InfractionViewSet( """ self.serializer_class = ExpandedInfractionSerializer return self.partial_update(*args, **kwargs) + + def create(self, request: HttpRequest, *args, **kwargs) -> Response: + """ + Create an infraction for a target user. + + Called by the Django Rest Framework in response to the corresponding HTTP request. + """ + try: + return super().create(request, *args, **kwargs) + except IntegrityError: + raise ValidationError( + { + 'non_field_errors': [ + 'This user already has an active infraction of this type.', + ] + } + ) -- cgit v1.2.3 From c7f4d04acbdf4ac25eaa12009ce54fdd7e23591c Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Mon, 21 Feb 2022 23:12:22 +0100 Subject: Assert we're dealing with the unique constraint --- pydis_site/apps/api/viewsets/bot/infraction.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index 31e8ba40..7e7adbca 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -281,11 +281,19 @@ class InfractionViewSet( """ try: return super().create(request, *args, **kwargs) - except IntegrityError: - raise ValidationError( - { - 'non_field_errors': [ - 'This user already has an active infraction of this type.', - ] - } - ) + except IntegrityError as err: + # We need to use `__cause__` here, as Django reraises the internal + # UniqueViolation emitted by psycopg2 (which contains the attribute + # that we actually need) + # + # _meta is documented and mainly named that way to prevent + # name clashes: https://docs.djangoproject.com/en/dev/ref/models/meta/ + if err.__cause__.diag.constraint_name == Infraction._meta.constraints[0].name: + raise ValidationError( + { + 'non_field_errors': [ + 'This user already has an active infraction of this type.', + ] + } + ) + raise # pragma: no cover - no other constraint to test with -- cgit v1.2.3 From 66061855ca2098fae7e80fa2e1abb29f810495f1 Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Sat, 26 Feb 2022 14:36:53 +0100 Subject: Fix timezone awareness warnings Add a `warnings.warnings` clause to prevent these from being raised again in the future, and raise a full traceback if they don't. --- pydis_site/apps/api/tests/test_infractions.py | 4 ++-- pydis_site/apps/api/tests/test_models.py | 7 +++---- pydis_site/apps/api/tests/test_reminders.py | 17 +++++++++-------- pydis_site/apps/api/viewsets/bot/infraction.py | 11 +++++------ pydis_site/settings.py | 17 +++++++++++++++++ 5 files changed, 36 insertions(+), 20 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py index aa0604f6..f1107734 100644 --- a/pydis_site/apps/api/tests/test_infractions.py +++ b/pydis_site/apps/api/tests/test_infractions.py @@ -80,7 +80,7 @@ class InfractionTests(AuthenticatedAPITestCase): type='superstar', reason='This one doesn\'t matter anymore.', active=True, - expires_at=datetime.datetime.utcnow() + datetime.timedelta(hours=5) + expires_at=dt.now(timezone.utc) + datetime.timedelta(hours=5) ) cls.voiceban_expires_later = Infraction.objects.create( user_id=cls.user.id, @@ -88,7 +88,7 @@ class InfractionTests(AuthenticatedAPITestCase): type='voice_ban', reason='Jet engine mic', active=True, - expires_at=datetime.datetime.utcnow() + datetime.timedelta(days=5) + expires_at=dt.now(timezone.utc) + datetime.timedelta(days=5) ) def test_list_all(self): diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py index 5c9ddea4..0fad467c 100644 --- a/pydis_site/apps/api/tests/test_models.py +++ b/pydis_site/apps/api/tests/test_models.py @@ -1,8 +1,7 @@ -from datetime import datetime as dt +from datetime import datetime as dt, timezone from django.core.exceptions import ValidationError from django.test import SimpleTestCase, TestCase -from django.utils import timezone from pydis_site.apps.api.models import ( DeletedMessage, @@ -41,7 +40,7 @@ class NitroMessageLengthTest(TestCase): self.context = MessageDeletionContext.objects.create( id=50, actor=self.user, - creation=dt.utcnow() + creation=dt.now(timezone.utc) ) def test_create(self): @@ -99,7 +98,7 @@ class StringDunderMethodTests(SimpleTestCase): name='shawn', discriminator=555, ), - creation=dt.utcnow() + creation=dt.now(timezone.utc) ), embeds=[] ), diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py index 709685bc..e17569f0 100644 --- a/pydis_site/apps/api/tests/test_reminders.py +++ b/pydis_site/apps/api/tests/test_reminders.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from django.forms.models import model_to_dict from django.urls import reverse @@ -91,7 +91,7 @@ class ReminderDeletionTests(AuthenticatedAPITestCase): cls.reminder = Reminder.objects.create( author=cls.author, content="Don't forget to set yourself a reminder", - expiration=datetime.utcnow().isoformat(), + expiration=datetime.now(timezone.utc), jump_url="https://www.decliningmentalfaculties.com", channel_id=123 ) @@ -122,7 +122,7 @@ class ReminderListTests(AuthenticatedAPITestCase): cls.reminder_one = Reminder.objects.create( author=cls.author, content="We should take Bikini Bottom, and push it somewhere else!", - expiration=datetime.utcnow().isoformat(), + expiration=datetime.now(timezone.utc), jump_url="https://www.icantseemyforehead.com", channel_id=123 ) @@ -130,16 +130,17 @@ class ReminderListTests(AuthenticatedAPITestCase): cls.reminder_two = Reminder.objects.create( author=cls.author, content="Gahhh-I love being purple!", - expiration=datetime.utcnow().isoformat(), + expiration=datetime.now(timezone.utc), jump_url="https://www.goofygoobersicecreampartyboat.com", channel_id=123, active=False ) + drf_format = '%Y-%m-%dT%H:%M:%S.%fZ' cls.rem_dict_one = model_to_dict(cls.reminder_one) - cls.rem_dict_one['expiration'] += 'Z' # Massaging a quirk of the response time format + cls.rem_dict_one['expiration'] = cls.rem_dict_one['expiration'].strftime(drf_format) cls.rem_dict_two = model_to_dict(cls.reminder_two) - cls.rem_dict_two['expiration'] += 'Z' # Massaging a quirk of the response time format + cls.rem_dict_two['expiration'] = cls.rem_dict_two['expiration'].strftime(drf_format) def test_reminders_in_full_list(self): url = reverse('api:bot:reminder-list') @@ -175,7 +176,7 @@ class ReminderRetrieveTests(AuthenticatedAPITestCase): cls.reminder = Reminder.objects.create( author=cls.author, content="Reminder content", - expiration=datetime.utcnow().isoformat(), + expiration=datetime.now(timezone.utc), jump_url="http://example.com/", channel_id=123 ) @@ -203,7 +204,7 @@ class ReminderUpdateTests(AuthenticatedAPITestCase): cls.reminder = Reminder.objects.create( author=cls.author, content="Squash those do-gooders", - expiration=datetime.utcnow().isoformat(), + expiration=datetime.now(timezone.utc), jump_url="https://www.decliningmentalfaculties.com", channel_id=123 ) diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index 7e7adbca..d82090aa 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -3,6 +3,7 @@ from datetime import datetime from django.db import IntegrityError from django.db.models import QuerySet from django.http.request import HttpRequest +from django.utils import timezone from django_filters.rest_framework import DjangoFilterBackend from rest_framework.decorators import action from rest_framework.exceptions import ValidationError @@ -184,20 +185,18 @@ class InfractionViewSet( filter_expires_after = self.request.query_params.get('expires_after') if filter_expires_after: try: - additional_filters['expires_at__gte'] = datetime.fromisoformat( - filter_expires_after - ) + expires_after_parsed = datetime.fromisoformat(filter_expires_after) except ValueError: raise ValidationError({'expires_after': ['failed to convert to datetime']}) + additional_filters['expires_at__gte'] = timezone.make_aware(expires_after_parsed) filter_expires_before = self.request.query_params.get('expires_before') if filter_expires_before: try: - additional_filters['expires_at__lte'] = datetime.fromisoformat( - filter_expires_before - ) + expires_before_parsed = datetime.fromisoformat(filter_expires_before) except ValueError: raise ValidationError({'expires_before': ['failed to convert to datetime']}) + additional_filters['expires_at__lte'] = timezone.make_aware(expires_before_parsed) if 'expires_at__lte' in additional_filters and 'expires_at__gte' in additional_filters: if additional_filters['expires_at__gte'] > additional_filters['expires_at__lte']: diff --git a/pydis_site/settings.py b/pydis_site/settings.py index 4f2b4aef..17f220f3 100644 --- a/pydis_site/settings.py +++ b/pydis_site/settings.py @@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/ import os import secrets import sys +import warnings from pathlib import Path from socket import gethostbyname, gethostname @@ -53,10 +54,26 @@ if DEBUG: ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['*']) SECRET_KEY = "yellow polkadot bikini" # noqa: S105 + # Prevent verbose warnings emitted when passing a non-timezone aware + # datetime object to the database, whilst we have time zone support + # active. See the Django documentation for more details: + # https://docs.djangoproject.com/en/dev/topics/i18n/timezones/ + warnings.filterwarnings( + 'error', r"DateTimeField .* received a naive datetime", + RuntimeWarning, r'django\.db\.models\.fields', + ) + elif 'CI' in os.environ: ALLOWED_HOSTS = ['*'] SECRET_KEY = secrets.token_urlsafe(32) + # See above. We run with `CI=true`, but debug unset in GitHub Actions, + # so we also want to filter it there. + warnings.filterwarnings( + 'error', r"DateTimeField .* received a naive datetime", + RuntimeWarning, r'django\.db\.models\.fields', + ) + else: ALLOWED_HOSTS = env.list( 'ALLOWED_HOSTS', -- cgit v1.2.3 From 2734a68ed35598fcf0615353077a875549db4b0d Mon Sep 17 00:00:00 2001 From: Johannes Christ Date: Wed, 2 Mar 2022 19:45:25 +0100 Subject: Explicitly pass timezone --- pydis_site/apps/api/viewsets/bot/infraction.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index d82090aa..7f31292f 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -188,7 +188,10 @@ class InfractionViewSet( expires_after_parsed = datetime.fromisoformat(filter_expires_after) except ValueError: raise ValidationError({'expires_after': ['failed to convert to datetime']}) - additional_filters['expires_at__gte'] = timezone.make_aware(expires_after_parsed) + additional_filters['expires_at__gte'] = timezone.make_aware( + expires_after_parsed, + timezone=timezone.utc, + ) filter_expires_before = self.request.query_params.get('expires_before') if filter_expires_before: @@ -196,7 +199,10 @@ class InfractionViewSet( expires_before_parsed = datetime.fromisoformat(filter_expires_before) except ValueError: raise ValidationError({'expires_before': ['failed to convert to datetime']}) - additional_filters['expires_at__lte'] = timezone.make_aware(expires_before_parsed) + additional_filters['expires_at__lte'] = timezone.make_aware( + expires_before_parsed, + timezone=timezone.utc, + ) if 'expires_at__lte' in additional_filters and 'expires_at__gte' in additional_filters: if additional_filters['expires_at__gte'] > additional_filters['expires_at__lte']: -- cgit v1.2.3 From a6b8c27e68b529b1060b1213b465457c5c0d685a Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Mon, 7 Mar 2022 20:18:18 +0100 Subject: Add support for storing AoC related data in site --- .../apps/api/migrations/0080_add_aoc_tables.py | 33 +++++++++++ pydis_site/apps/api/models/__init__.py | 2 + pydis_site/apps/api/models/bot/__init__.py | 2 + .../apps/api/models/bot/aoc_completionist_block.py | 21 +++++++ pydis_site/apps/api/models/bot/aoc_link.py | 20 +++++++ pydis_site/apps/api/serializers.py | 22 +++++++ pydis_site/apps/api/urls.py | 10 ++++ pydis_site/apps/api/viewsets/__init__.py | 2 + pydis_site/apps/api/viewsets/bot/__init__.py | 2 + .../api/viewsets/bot/aoc_completionist_block.py | 69 ++++++++++++++++++++++ pydis_site/apps/api/viewsets/bot/aoc_link.py | 69 ++++++++++++++++++++++ 11 files changed, 252 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0080_add_aoc_tables.py create mode 100644 pydis_site/apps/api/models/bot/aoc_completionist_block.py create mode 100644 pydis_site/apps/api/models/bot/aoc_link.py create mode 100644 pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py create mode 100644 pydis_site/apps/api/viewsets/bot/aoc_link.py (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py new file mode 100644 index 00000000..f129d86f --- /dev/null +++ b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py @@ -0,0 +1,33 @@ +# Generated by Django 3.1.14 on 2022-03-06 16:07 + +from django.db import migrations, models +import django.db.models.deletion +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0079_merge_20220125_2022'), + ] + + operations = [ + migrations.CreateModel( + name='AocCompletionistBlock', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), + ('user', models.ForeignKey(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ], + bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), + ), + migrations.CreateModel( + name='AocAccountLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('aoc_username', models.CharField(help_text='The AoC username associated with the Discord User.', max_length=120)), + ('user', models.ForeignKey(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ], + 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 fd5bf220..4f616986 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -10,6 +10,8 @@ from .bot import ( Nomination, NominationEntry, OffensiveMessage, + AocAccountLink, + AocCompletionistBlock, OffTopicChannelName, Reminder, Role, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index ac864de3..ec0e701c 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -5,6 +5,8 @@ from .deleted_message import DeletedMessage from .documentation_link import DocumentationLink from .infraction import Infraction from .message import Message +from .aoc_completionist_block import AocCompletionistBlock +from .aoc_link import AocAccountLink from .message_deletion_context import MessageDeletionContext from .nomination import Nomination, NominationEntry from .off_topic_channel_name import OffTopicChannelName diff --git a/pydis_site/apps/api/models/bot/aoc_completionist_block.py b/pydis_site/apps/api/models/bot/aoc_completionist_block.py new file mode 100644 index 00000000..cac41ff1 --- /dev/null +++ b/pydis_site/apps/api/models/bot/aoc_completionist_block.py @@ -0,0 +1,21 @@ +from django.db import models + +from pydis_site.apps.api.models.bot.user import User +from pydis_site.apps.api.models.mixins import ModelReprMixin + + +class AocCompletionistBlock(ModelReprMixin, models.Model): + """A Discord user blocked from getting the AoC completionist Role.""" + + user = models.ForeignKey( + User, + on_delete=models.CASCADE, + help_text="The user that is blocked from getting the AoC Completionist Role" + ) + + is_blocked = models.BooleanField( + default=True, + help_text="Whether this user is actively being blocked " + "from getting the AoC Completionist Role", + verbose_name="Blocked" + ) diff --git a/pydis_site/apps/api/models/bot/aoc_link.py b/pydis_site/apps/api/models/bot/aoc_link.py new file mode 100644 index 00000000..6c7cc591 --- /dev/null +++ b/pydis_site/apps/api/models/bot/aoc_link.py @@ -0,0 +1,20 @@ +from django.db import models + +from pydis_site.apps.api.models.bot.user import User +from pydis_site.apps.api.models.mixins import ModelReprMixin + + +class AocAccountLink(ModelReprMixin, models.Model): + """An AoC account link for a Discord User.""" + + user = models.ForeignKey( + User, + on_delete=models.CASCADE, + help_text="The user that is blocked from getting the AoC Completionist Role" + ) + + aoc_username = models.CharField( + max_length=120, + help_text="The AoC username associated with the Discord User.", + blank=False + ) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 745aff42..0b0e4237 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -13,6 +13,8 @@ from rest_framework.settings import api_settings from rest_framework.validators import UniqueTogetherValidator from .models import ( + AocAccountLink, + AocCompletionistBlock, BotSetting, DeletedMessage, DocumentationLink, @@ -250,6 +252,26 @@ class ReminderSerializer(ModelSerializer): ) +class AocCompletionistBlockSerializer(ModelSerializer): + """A class providing (de-)serialization of `AocCompletionistBlock` instances.""" + + class Meta: + """Metadata defined for the Django REST Framework.""" + + model = AocCompletionistBlock + fields = ("user", "is_blocked") + + +class AocAccountLinkSerializer(ModelSerializer): + """A class providing (de-)serialization of `AocAccountLink` instances.""" + + class Meta: + """Metadata defined for the Django REST Framework.""" + + model = AocAccountLink + fields = ("user", "aoc_username") + + class RoleSerializer(ModelSerializer): """A class providing (de-)serialization of `Role` instances.""" diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index b0ab545b..7c55fc92 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -3,6 +3,8 @@ from rest_framework.routers import DefaultRouter from .views import HealthcheckView, RulesView from .viewsets import ( + AocAccountLinkViewSet, + AocCompletionistBlockViewSet, BotSettingViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, @@ -34,6 +36,14 @@ bot_router.register( 'documentation-links', DocumentationLinkViewSet ) +bot_router.register( + "aoc-account-links", + AocAccountLinkViewSet +) +bot_router.register( + "aoc-completionist-blocks", + AocCompletionistBlockViewSet +) bot_router.register( 'infractions', InfractionViewSet diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py index f133e77f..5fc1d64f 100644 --- a/pydis_site/apps/api/viewsets/__init__.py +++ b/pydis_site/apps/api/viewsets/__init__.py @@ -7,6 +7,8 @@ from .bot import ( InfractionViewSet, NominationViewSet, OffensiveMessageViewSet, + AocAccountLinkViewSet, + AocCompletionistBlockViewSet, OffTopicChannelNameViewSet, ReminderViewSet, RoleViewSet, diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py index 84b87eab..f1d84729 100644 --- a/pydis_site/apps/api/viewsets/bot/__init__.py +++ b/pydis_site/apps/api/viewsets/bot/__init__.py @@ -7,6 +7,8 @@ from .infraction import InfractionViewSet from .nomination import NominationViewSet from .off_topic_channel_name import OffTopicChannelNameViewSet from .offensive_message import OffensiveMessageViewSet +from .aoc_link import AocAccountLinkViewSet +from .aoc_completionist_block import AocCompletionistBlockViewSet from .reminder import ReminderViewSet from .role import RoleViewSet from .user import UserViewSet diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py new file mode 100644 index 00000000..53bcb546 --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -0,0 +1,69 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.mixins import ( + CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin +) +from rest_framework.viewsets import GenericViewSet + +from pydis_site.apps.api.models.bot import AocCompletionistBlock +from pydis_site.apps.api.serializers import AocCompletionistBlockSerializer + + +class AocCompletionistBlockViewSet( + GenericViewSet, CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin +): + """ + View providing management for Users blocked from gettign the AoC completionist Role. + + ## Routes + + ### GET /bot/aoc-completionist-blocks/ + Returns all the AoC completionist blocks + + #### Response format + >>> [ + ... { + ... "user": 2, + ... "is_blocked": False + ... } + ... ] + + + ### GET /bot/aoc-completionist-blocks/ + Retrieve a single Block by User ID + + #### Response format + >>> + ... { + ... "user": 2, + ... "is_blocked": False + ... } + + #### Status codes + - 200: returned on success + - 404: returned if an AoC completionist block with the given user__id was not found. + + ### POST /bot/aoc-completionist-blocks + Adds a single AoC completionist block + + #### Request body + >>> { + ... 'user': int, + ... 'is_blocked': bool + ... } + + #### Status codes + - 204: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/aoc-completionist-blocks/ + Deletes the AoC Completionist block item with the given `user__id`. + #### Status codes + - 204: returned on success + - 404: if the AoC Completionist block with the given user__id does not exist + + """ + + serializer_class = AocCompletionistBlockSerializer + queryset = AocCompletionistBlock.objects.all() + filter_backends = (DjangoFilterBackend,) + filter_fields = ("user__id",) diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py new file mode 100644 index 00000000..b5b5420e --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -0,0 +1,69 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.mixins import ( + CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin +) +from rest_framework.viewsets import GenericViewSet + +from pydis_site.apps.api.models.bot import AocAccountLink +from pydis_site.apps.api.serializers import AocAccountLinkSerializer + + +class AocAccountLinkViewSet( + GenericViewSet, CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin +): + """ + View providing management for Users who linked their AoC accounts to their Discord Account. + + ## Routes + + ### GET /bot/aoc-account-links + Returns all the AoC account links + + #### Response format + >>> [ + ... { + ... "user": 2, + ... "aoc_username": "AoCUser1" + ... } + ... ] + + + ### GET /bot/aoc-account-links + Retrieve a AoC account link by User ID + + #### Response format + >>> + ... { + ... "user": 2, + ... "aoc_username": "AoCUser1" + ... } + + #### Status codes + - 200: returned on success + - 404: returned if an AoC account link with the given user__id was not found. + + ### POST /bot/aoc-account-links + Adds a single AoC account link block + + #### Request body + >>> { + ... 'user': int, + ... 'aoc_username': str + ... } + + #### Status codes + - 204: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/aoc-account-links/ + Deletes the AoC account link item with the given `user__id`. + #### Status codes + - 204: returned on success + - 404: if the AoC account link with the given user__id does not exist + + """ + + serializer_class = AocAccountLinkSerializer + queryset = AocAccountLink.objects.all() + filter_backends = (DjangoFilterBackend,) + filter_fields = ("user__id",) -- cgit v1.2.3 From 19c6e7b66cf5078a198f4a70fec38d66dd029564 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Tue, 8 Mar 2022 15:54:34 +0100 Subject: Enhance comments and table structure in AoC related modules - Set the user reference to be a OneToOne relation, on tables: AocCompletionistBlock and AocAccountLink. --- pydis_site/apps/api/migrations/0080_add_aoc_tables.py | 4 ++-- pydis_site/apps/api/models/bot/aoc_completionist_block.py | 2 +- pydis_site/apps/api/models/bot/aoc_link.py | 2 +- pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py | 4 ++-- pydis_site/apps/api/viewsets/bot/aoc_link.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py index f129d86f..c58a5d29 100644 --- a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py +++ b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py @@ -17,7 +17,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), - ('user', models.ForeignKey(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), @@ -26,7 +26,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('aoc_username', models.CharField(help_text='The AoC username associated with the Discord User.', max_length=120)), - ('user', models.ForeignKey(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), diff --git a/pydis_site/apps/api/models/bot/aoc_completionist_block.py b/pydis_site/apps/api/models/bot/aoc_completionist_block.py index cac41ff1..a89f9760 100644 --- a/pydis_site/apps/api/models/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/models/bot/aoc_completionist_block.py @@ -7,7 +7,7 @@ from pydis_site.apps.api.models.mixins import ModelReprMixin class AocCompletionistBlock(ModelReprMixin, models.Model): """A Discord user blocked from getting the AoC completionist Role.""" - user = models.ForeignKey( + user = models.OneToOneField( User, on_delete=models.CASCADE, help_text="The user that is blocked from getting the AoC Completionist Role" diff --git a/pydis_site/apps/api/models/bot/aoc_link.py b/pydis_site/apps/api/models/bot/aoc_link.py index 6c7cc591..9b47456d 100644 --- a/pydis_site/apps/api/models/bot/aoc_link.py +++ b/pydis_site/apps/api/models/bot/aoc_link.py @@ -7,7 +7,7 @@ from pydis_site.apps.api.models.mixins import ModelReprMixin class AocAccountLink(ModelReprMixin, models.Model): """An AoC account link for a Discord User.""" - user = models.ForeignKey( + user = models.OneToOneField( User, on_delete=models.CASCADE, help_text="The user that is blocked from getting the AoC Completionist Role" diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py index 53bcb546..c5568129 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -47,8 +47,8 @@ class AocCompletionistBlockViewSet( #### Request body >>> { - ... 'user': int, - ... 'is_blocked': bool + ... 'user': int, + ... 'is_blocked': bool ... } #### Status codes diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py index b5b5420e..263b548d 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_link.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -47,8 +47,8 @@ class AocAccountLinkViewSet( #### Request body >>> { - ... 'user': int, - ... 'aoc_username': str + ... 'user': int, + ... 'aoc_username': str ... } #### Status codes -- cgit v1.2.3 From b93dce5abcf225579b9407358f938ca3932e67a2 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Wed, 9 Mar 2022 19:37:30 +0100 Subject: Add reason field to AoC completionist block table --- pydis_site/apps/api/migrations/0080_add_aoc_tables.py | 1 + pydis_site/apps/api/models/bot/aoc_completionist_block.py | 4 ++++ pydis_site/apps/api/serializers.py | 2 +- pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py | 7 +++++-- 4 files changed, 11 insertions(+), 3 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py index c58a5d29..917c5b7f 100644 --- a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py +++ b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py @@ -17,6 +17,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), + ('reason', models.TextField(help_text='The reason for the AoC Completionist Role Block.', null=True)), ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), diff --git a/pydis_site/apps/api/models/bot/aoc_completionist_block.py b/pydis_site/apps/api/models/bot/aoc_completionist_block.py index a89f9760..6605cbc4 100644 --- a/pydis_site/apps/api/models/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/models/bot/aoc_completionist_block.py @@ -19,3 +19,7 @@ class AocCompletionistBlock(ModelReprMixin, models.Model): "from getting the AoC Completionist Role", verbose_name="Blocked" ) + reason = models.TextField( + null=True, + help_text="The reason for the AoC Completionist Role Block." + ) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index 0b0e4237..c97f7dba 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -259,7 +259,7 @@ class AocCompletionistBlockSerializer(ModelSerializer): """Metadata defined for the Django REST Framework.""" model = AocCompletionistBlock - fields = ("user", "is_blocked") + fields = ("user", "is_blocked", "reason") class AocAccountLinkSerializer(ModelSerializer): diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py index c5568129..8e7d821c 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -24,6 +24,7 @@ class AocCompletionistBlockViewSet( ... { ... "user": 2, ... "is_blocked": False + ... "reason": "Too good to be true" ... } ... ] @@ -36,6 +37,7 @@ class AocCompletionistBlockViewSet( ... { ... "user": 2, ... "is_blocked": False + ... "reason": "Too good to be true" ... } #### Status codes @@ -47,8 +49,9 @@ class AocCompletionistBlockViewSet( #### Request body >>> { - ... 'user': int, - ... 'is_blocked': bool + ... "user": int, + ... "is_blocked": bool + ... "reason": string ... } #### Status codes -- cgit v1.2.3 From d18d0198f2a43066b7f6cb9542a25adea6e6b3f4 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Wed, 9 Mar 2022 23:07:38 +0100 Subject: Patch AoC tables to use the Discord user as PK. --- pydis_site/apps/api/migrations/0080_add_aoc_tables.py | 16 +++++++--------- .../apps/api/models/bot/aoc_completionist_block.py | 3 ++- pydis_site/apps/api/models/bot/aoc_link.py | 3 ++- pydis_site/apps/api/viewsets/bot/aoc_link.py | 8 +++++--- 4 files changed, 16 insertions(+), 14 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py index 917c5b7f..2c0c689a 100644 --- a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py +++ b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py @@ -13,21 +13,19 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='AocCompletionistBlock', + name='AocAccountLink', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), - ('reason', models.TextField(help_text='The reason for the AoC Completionist Role Block.', null=True)), - ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='api.user')), + ('aoc_username', models.CharField(help_text='The AoC username associated with the Discord User.', max_length=120)), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), migrations.CreateModel( - name='AocAccountLink', + name='AocCompletionistBlock', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('aoc_username', models.CharField(help_text='The AoC username associated with the Discord User.', max_length=120)), - ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, to='api.user')), + ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='api.user')), + ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')), + ('reason', models.TextField(help_text='The reason for the AoC Completionist Role Block.', null=True)), ], bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model), ), diff --git a/pydis_site/apps/api/models/bot/aoc_completionist_block.py b/pydis_site/apps/api/models/bot/aoc_completionist_block.py index 6605cbc4..acbc0eba 100644 --- a/pydis_site/apps/api/models/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/models/bot/aoc_completionist_block.py @@ -10,7 +10,8 @@ class AocCompletionistBlock(ModelReprMixin, models.Model): user = models.OneToOneField( User, on_delete=models.CASCADE, - help_text="The user that is blocked from getting the AoC Completionist Role" + help_text="The user that is blocked from getting the AoC Completionist Role", + primary_key=True ) is_blocked = models.BooleanField( diff --git a/pydis_site/apps/api/models/bot/aoc_link.py b/pydis_site/apps/api/models/bot/aoc_link.py index 9b47456d..4e9d4882 100644 --- a/pydis_site/apps/api/models/bot/aoc_link.py +++ b/pydis_site/apps/api/models/bot/aoc_link.py @@ -10,7 +10,8 @@ class AocAccountLink(ModelReprMixin, models.Model): user = models.OneToOneField( User, on_delete=models.CASCADE, - help_text="The user that is blocked from getting the AoC Completionist Role" + help_text="The user that is blocked from getting the AoC Completionist Role", + primary_key=True ) aoc_username = models.CharField( diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py index 263b548d..c3fa6854 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_link.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -24,7 +24,8 @@ class AocAccountLinkViewSet( ... { ... "user": 2, ... "aoc_username": "AoCUser1" - ... } + ... }, + ... ... ... ] @@ -32,11 +33,12 @@ class AocAccountLinkViewSet( Retrieve a AoC account link by User ID #### Response format - >>> + >>> [ ... { ... "user": 2, ... "aoc_username": "AoCUser1" - ... } + ... }, + ... ] #### Status codes - 200: returned on success -- cgit v1.2.3 From 8c95f13c96d16d6f4d0736ee136563d603926c63 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Thu, 10 Mar 2022 17:28:39 +0100 Subject: Enhance code, documentation consistency in AoC related code Co-authored-by: Mark <1515135+MarkKoz@users.noreply.github.com> --- pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py | 7 ++++--- pydis_site/apps/api/viewsets/bot/aoc_link.py | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py index 8e7d821c..d3167d7b 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -23,7 +23,7 @@ class AocCompletionistBlockViewSet( >>> [ ... { ... "user": 2, - ... "is_blocked": False + ... "is_blocked": False, ... "reason": "Too good to be true" ... } ... ] @@ -36,7 +36,7 @@ class AocCompletionistBlockViewSet( >>> ... { ... "user": 2, - ... "is_blocked": False + ... "is_blocked": False, ... "reason": "Too good to be true" ... } @@ -50,7 +50,7 @@ class AocCompletionistBlockViewSet( #### Request body >>> { ... "user": int, - ... "is_blocked": bool + ... "is_blocked": bool, ... "reason": string ... } @@ -60,6 +60,7 @@ class AocCompletionistBlockViewSet( ### DELETE /bot/aoc-completionist-blocks/ Deletes the AoC Completionist block item with the given `user__id`. + #### Status codes - 204: returned on success - 404: if the AoC Completionist block with the given user__id does not exist diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py index c3fa6854..5f6f3a84 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_link.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -59,6 +59,7 @@ class AocAccountLinkViewSet( ### DELETE /bot/aoc-account-links/ Deletes the AoC account link item with the given `user__id`. + #### Status codes - 204: returned on success - 404: if the AoC account link with the given user__id does not exist -- cgit v1.2.3 From 083cdf3f49805fd3c6d01fe538b7e01e12ca9b79 Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Thu, 10 Mar 2022 19:18:05 +0100 Subject: Add new filter field, and patch the docs in AoC viewsets - Add the possibility to filter by `is_blocked` in the AoC completionist block viewset. - Patch various tense, and formatting inconsistencies in AoC viewsets --- .../apps/api/viewsets/bot/aoc_completionist_block.py | 6 +++--- pydis_site/apps/api/viewsets/bot/aoc_link.py | 19 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py index d3167d7b..3a4cec60 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py @@ -42,7 +42,7 @@ class AocCompletionistBlockViewSet( #### Status codes - 200: returned on success - - 404: returned if an AoC completionist block with the given user__id was not found. + - 404: returned if an AoC completionist block with the given `user__id` was not found. ### POST /bot/aoc-completionist-blocks Adds a single AoC completionist block @@ -63,11 +63,11 @@ class AocCompletionistBlockViewSet( #### Status codes - 204: returned on success - - 404: if the AoC Completionist block with the given user__id does not exist + - 404: returned if the AoC Completionist block with the given `user__id` was not found """ serializer_class = AocCompletionistBlockSerializer queryset = AocCompletionistBlock.objects.all() filter_backends = (DjangoFilterBackend,) - filter_fields = ("user__id",) + filter_fields = ("user__id", "is_blocked") diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py index 5f6f3a84..9f22c1a1 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_link.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -29,20 +29,19 @@ class AocAccountLinkViewSet( ... ] - ### GET /bot/aoc-account-links + ### GET /bot/aoc-account-links/ Retrieve a AoC account link by User ID #### Response format - >>> [ - ... { - ... "user": 2, - ... "aoc_username": "AoCUser1" - ... }, - ... ] + >>> + ... { + ... "user": 2, + ... "aoc_username": "AoCUser1" + ... } #### Status codes - 200: returned on success - - 404: returned if an AoC account link with the given user__id was not found. + - 404: returned if an AoC account link with the given `user__id` was not found. ### POST /bot/aoc-account-links Adds a single AoC account link block @@ -55,14 +54,14 @@ class AocAccountLinkViewSet( #### Status codes - 204: returned on success - - 400: if one of the given fields is invalid + - 400: if one of the given fields was invalid ### DELETE /bot/aoc-account-links/ Deletes the AoC account link item with the given `user__id`. #### Status codes - 204: returned on success - - 404: if the AoC account link with the given user__id does not exist + - 404: returned if the AoC account link with the given `user__id` was not found """ -- cgit v1.2.3 From 0aed5f7913e7ce268ddb56127f84a5386ede5739 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sat, 19 Feb 2022 17:59:26 +0000 Subject: Add support for BumpedThreads to be stored in site Following our move to use Redis as just a cache, this PR allows the site to store a list of threads that need to be bumped. The bot will interact with this within the ThreadBumper cog. --- .../apps/api/migrations/0081_bumpedthread.py | 22 ++++++++ pydis_site/apps/api/models/__init__.py | 1 + pydis_site/apps/api/models/bot/__init__.py | 1 + pydis_site/apps/api/models/bot/bumped_thread.py | 22 ++++++++ pydis_site/apps/api/serializers.py | 11 ++++ pydis_site/apps/api/urls.py | 5 ++ pydis_site/apps/api/viewsets/__init__.py | 1 + pydis_site/apps/api/viewsets/bot/__init__.py | 1 + pydis_site/apps/api/viewsets/bot/bumped_thread.py | 65 ++++++++++++++++++++++ 9 files changed, 129 insertions(+) create mode 100644 pydis_site/apps/api/migrations/0081_bumpedthread.py create mode 100644 pydis_site/apps/api/models/bot/bumped_thread.py create mode 100644 pydis_site/apps/api/viewsets/bot/bumped_thread.py (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/migrations/0081_bumpedthread.py b/pydis_site/apps/api/migrations/0081_bumpedthread.py new file mode 100644 index 00000000..03e66cc1 --- /dev/null +++ b/pydis_site/apps/api/migrations/0081_bumpedthread.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1.14 on 2022-02-19 16:26 + +import django.core.validators +from django.db import migrations, models +import pydis_site.apps.api.models.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0080_add_aoc_tables'), + ] + + operations = [ + migrations.CreateModel( + name='BumpedThread', + fields=[ + ('thread_id', models.BigIntegerField(help_text='The thread ID that should be bumped.', primary_key=True, serialize=False, validators=[django.core.validators.MinValueValidator(limit_value=0, message='Thread IDs cannot be negative.')], verbose_name='Thread ID')), + ], + 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 e83473c9..a197e988 100644 --- a/pydis_site/apps/api/models/__init__.py +++ b/pydis_site/apps/api/models/__init__.py @@ -1,6 +1,7 @@ # flake8: noqa from .bot import ( BotSetting, + BumpedThread, DocumentationLink, DeletedMessage, FilterList, diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py index 64676fdb..013bb85e 100644 --- a/pydis_site/apps/api/models/bot/__init__.py +++ b/pydis_site/apps/api/models/bot/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa from .bot_setting import BotSetting +from .bumped_thread import BumpedThread from .deleted_message import DeletedMessage from .documentation_link import DocumentationLink from .filter_list import FilterList diff --git a/pydis_site/apps/api/models/bot/bumped_thread.py b/pydis_site/apps/api/models/bot/bumped_thread.py new file mode 100644 index 00000000..cdf9a950 --- /dev/null +++ b/pydis_site/apps/api/models/bot/bumped_thread.py @@ -0,0 +1,22 @@ +from django.core.validators import MinValueValidator +from django.db import models + +from pydis_site.apps.api.models.mixins import ModelReprMixin + + +class BumpedThread(ModelReprMixin, models.Model): + """A list of thread IDs to be bumped.""" + + thread_id = models.BigIntegerField( + primary_key=True, + help_text=( + "The thread ID that should be bumped." + ), + validators=( + MinValueValidator( + limit_value=0, + message="Thread IDs cannot be negative." + ), + ), + verbose_name="Thread ID", + ) diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index c97f7dba..b9e06081 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -16,6 +16,7 @@ from .models import ( AocAccountLink, AocCompletionistBlock, BotSetting, + BumpedThread, DeletedMessage, DocumentationLink, FilterList, @@ -41,6 +42,16 @@ class BotSettingSerializer(ModelSerializer): fields = ('name', 'data') +class BumpedThreadSerializer(ModelSerializer): + """A class providing (de-)serialization of `BumpedThread` instances.""" + + class Meta: + """Metadata defined for the Django REST Framework.""" + + model = BumpedThread + fields = ('thread_id',) + + class DeletedMessageSerializer(ModelSerializer): """ A class providing (de-)serialization of `DeletedMessage` instances. diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index 6b881fac..1e564b29 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -6,6 +6,7 @@ from .viewsets import ( AocAccountLinkViewSet, AocCompletionistBlockViewSet, BotSettingViewSet, + BumpedThreadViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, FilterListViewSet, @@ -32,6 +33,10 @@ bot_router.register( 'bot-settings', BotSettingViewSet ) +bot_router.register( + 'bumped-threads', + BumpedThreadViewSet +) bot_router.register( 'deleted-messages', DeletedMessageViewSet diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py index a62a9c01..ec52416a 100644 --- a/pydis_site/apps/api/viewsets/__init__.py +++ b/pydis_site/apps/api/viewsets/__init__.py @@ -1,6 +1,7 @@ # flake8: noqa from .bot import ( BotSettingViewSet, + BumpedThreadViewSet, DeletedMessageViewSet, DocumentationLinkViewSet, FilterListViewSet, diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py index f1d84729..262aa59f 100644 --- a/pydis_site/apps/api/viewsets/bot/__init__.py +++ b/pydis_site/apps/api/viewsets/bot/__init__.py @@ -1,6 +1,7 @@ # flake8: noqa from .filter_list import FilterListViewSet from .bot_setting import BotSettingViewSet +from .bumped_thread import BumpedThreadViewSet from .deleted_message import DeletedMessageViewSet from .documentation_link import DocumentationLinkViewSet from .infraction import InfractionViewSet diff --git a/pydis_site/apps/api/viewsets/bot/bumped_thread.py b/pydis_site/apps/api/viewsets/bot/bumped_thread.py new file mode 100644 index 00000000..6594ac6e --- /dev/null +++ b/pydis_site/apps/api/viewsets/bot/bumped_thread.py @@ -0,0 +1,65 @@ +from rest_framework.mixins import ( + CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin +) +from rest_framework.viewsets import GenericViewSet + +from pydis_site.apps.api.models.bot import BumpedThread +from pydis_site.apps.api.serializers import BumpedThreadSerializer + + +class BumpedThreadViewSet( + GenericViewSet, CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin +): + """ + View providing CRUD (Minus the U) operations on threads to be bumped. + + ## Routes + ### GET /bot/bumped-threads + Returns all BumpedThread items in the database. + + #### Response format + >>> [ + ... { + ... 'thread_id': "941705627405811793", + ... }, + ... ... + ... ] + + #### Status codes + - 200: returned on success + - 401: returned if unauthenticated + + ### GET /bot/bumped-threads/ + Returns a specific BumpedThread item from the database. + + #### Response format + >>> { + ... 'thread_id': "941705627405811793", + ... } + + #### Status codes + - 200: returned on success + - 404: returned if a BumpedThread with the given thread_id was not found. + + ### POST /bot/bumped-threads + Adds a single BumpedThread item to the database. + + #### Request body + >>> { + ... 'thread_id': int, + ... } + + #### Status codes + - 201: returned on success + - 400: if one of the given fields is invalid + + ### DELETE /bot/bumped-threads/ + Deletes the BumpedThread item with the given `thread_id`. + + #### Status codes + - 204: returned on success + - 404: if a BumpedThread with the given `thread_id` does not exist + """ + + serializer_class = BumpedThreadSerializer + queryset = BumpedThread.objects.all() -- cgit v1.2.3 From 3e9557056c06a39c077b76d718eb35b99a365711 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Wed, 16 Mar 2022 21:46:46 +0000 Subject: Only return list of ints when retrieving all BumpedThreads --- pydis_site/apps/api/serializers.py | 17 +++++++++++++++++ pydis_site/apps/api/viewsets/bot/bumped_thread.py | 7 +------ 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py index b9e06081..dfdda915 100644 --- a/pydis_site/apps/api/serializers.py +++ b/pydis_site/apps/api/serializers.py @@ -42,12 +42,29 @@ class BotSettingSerializer(ModelSerializer): fields = ('name', 'data') +class ListBumpedThreadSerializer(ListSerializer): + """Custom ListSerializer to override to_representation() when list views are triggered.""" + + def to_representation(self, objects: list[BumpedThread]) -> int: + """ + Used by the `ListModelMixin` to return just the list of bumped thread ids. + + We want to only return the thread_id attribute, hence it is unnecessary + to create a nested dictionary. + + Additionally, this allows bumped thread routes to simply return an + array of thread_id ints instead of objects, saving on bandwidth. + """ + return [obj.thread_id for obj in objects] + + class BumpedThreadSerializer(ModelSerializer): """A class providing (de-)serialization of `BumpedThread` instances.""" class Meta: """Metadata defined for the Django REST Framework.""" + list_serializer_class = ListBumpedThreadSerializer model = BumpedThread fields = ('thread_id',) diff --git a/pydis_site/apps/api/viewsets/bot/bumped_thread.py b/pydis_site/apps/api/viewsets/bot/bumped_thread.py index 6594ac6e..0972379b 100644 --- a/pydis_site/apps/api/viewsets/bot/bumped_thread.py +++ b/pydis_site/apps/api/viewsets/bot/bumped_thread.py @@ -18,12 +18,7 @@ class BumpedThreadViewSet( Returns all BumpedThread items in the database. #### Response format - >>> [ - ... { - ... 'thread_id': "941705627405811793", - ... }, - ... ... - ... ] + >>> list[int] #### Status codes - 200: returned on success -- cgit v1.2.3 From a0180619e77d884aeedb1a89748c0115c8ec8c56 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Wed, 16 Mar 2022 21:47:57 +0000 Subject: Don't return the BumpedThread object when retrieving single We only need to check for existence, so sending the full object isn't needed. --- pydis_site/apps/api/viewsets/bot/bumped_thread.py | 24 ++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/viewsets/bot/bumped_thread.py b/pydis_site/apps/api/viewsets/bot/bumped_thread.py index 0972379b..9d77bb6b 100644 --- a/pydis_site/apps/api/viewsets/bot/bumped_thread.py +++ b/pydis_site/apps/api/viewsets/bot/bumped_thread.py @@ -1,6 +1,8 @@ from rest_framework.mixins import ( - CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin + CreateModelMixin, DestroyModelMixin, ListModelMixin ) +from rest_framework.request import Request +from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet from pydis_site.apps.api.models.bot import BumpedThread @@ -8,7 +10,7 @@ from pydis_site.apps.api.serializers import BumpedThreadSerializer class BumpedThreadViewSet( - GenericViewSet, CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin + GenericViewSet, CreateModelMixin, DestroyModelMixin, ListModelMixin ): """ View providing CRUD (Minus the U) operations on threads to be bumped. @@ -25,15 +27,10 @@ class BumpedThreadViewSet( - 401: returned if unauthenticated ### GET /bot/bumped-threads/ - Returns a specific BumpedThread item from the database. - - #### Response format - >>> { - ... 'thread_id': "941705627405811793", - ... } + Returns whether a specific BumpedThread exists in the database. #### Status codes - - 200: returned on success + - 204: returned on success - 404: returned if a BumpedThread with the given thread_id was not found. ### POST /bot/bumped-threads @@ -58,3 +55,12 @@ class BumpedThreadViewSet( serializer_class = BumpedThreadSerializer queryset = BumpedThread.objects.all() + + def retrieve(self, request: Request, *args, **kwargs) -> Response: + """ + DRF method for checking if the given BumpedThread exists. + + Called by the Django Rest Framework in response to the corresponding HTTP request. + """ + self.get_object() + return Response(status=204) -- cgit v1.2.3 From a4eda4a2da65a1a7f903a8c5b1da37c366baee0b Mon Sep 17 00:00:00 2001 From: D0rs4n <41237606+D0rs4n@users.noreply.github.com> Date: Wed, 13 Apr 2022 23:13:02 +0200 Subject: Adjust filtering settings for the AoC link viewset --- pydis_site/apps/api/viewsets/bot/aoc_link.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pydis_site/apps/api/viewsets/bot') diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py index 9f22c1a1..c7a96629 100644 --- a/pydis_site/apps/api/viewsets/bot/aoc_link.py +++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py @@ -68,4 +68,4 @@ class AocAccountLinkViewSet( serializer_class = AocAccountLinkSerializer queryset = AocAccountLink.objects.all() filter_backends = (DjangoFilterBackend,) - filter_fields = ("user__id",) + filter_fields = ("user__id", "aoc_username") -- cgit v1.2.3