Skip to content

Commit 96122b7

Browse files
authored
Merge #663 - bumped threads endpoint
2 parents bdc37c9 + 429d98a commit 96122b7

File tree

10 files changed

+220
-11
lines changed

10 files changed

+220
-11
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 3.1.14 on 2022-02-19 16:26
2+
3+
import django.core.validators
4+
from django.db import migrations, models
5+
import pydis_site.apps.api.models.mixins
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('api', '0080_add_aoc_tables'),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='BumpedThread',
17+
fields=[
18+
('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')),
19+
],
20+
bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
21+
),
22+
]

pydis_site/apps/api/models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# flake8: noqa
22
from .bot import (
3-
FilterList,
43
BotSetting,
4+
BumpedThread,
55
DocumentationLink,
66
DeletedMessage,
7+
FilterList,
78
Infraction,
89
Message,
910
MessageDeletionContext,

pydis_site/apps/api/models/bot/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# flake8: noqa
2-
from .filter_list import FilterList
32
from .bot_setting import BotSetting
3+
from .bumped_thread import BumpedThread
44
from .deleted_message import DeletedMessage
55
from .documentation_link import DocumentationLink
6+
from .filter_list import FilterList
67
from .infraction import Infraction
78
from .message import Message
89
from .aoc_completionist_block import AocCompletionistBlock
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from django.core.validators import MinValueValidator
2+
from django.db import models
3+
4+
from pydis_site.apps.api.models.mixins import ModelReprMixin
5+
6+
7+
class BumpedThread(ModelReprMixin, models.Model):
8+
"""A list of thread IDs to be bumped."""
9+
10+
thread_id = models.BigIntegerField(
11+
primary_key=True,
12+
help_text=(
13+
"The thread ID that should be bumped."
14+
),
15+
validators=(
16+
MinValueValidator(
17+
limit_value=0,
18+
message="Thread IDs cannot be negative."
19+
),
20+
),
21+
verbose_name="Thread ID",
22+
)

pydis_site/apps/api/serializers.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
AocAccountLink,
1717
AocCompletionistBlock,
1818
BotSetting,
19+
BumpedThread,
1920
DeletedMessage,
2021
DocumentationLink,
2122
FilterList,
@@ -41,6 +42,32 @@ class Meta:
4142
fields = ('name', 'data')
4243

4344

45+
class ListBumpedThreadSerializer(ListSerializer):
46+
"""Custom ListSerializer to override to_representation() when list views are triggered."""
47+
48+
def to_representation(self, objects: list[BumpedThread]) -> int:
49+
"""
50+
Used by the `ListModelMixin` to return just the list of bumped thread ids.
51+
52+
Only the thread_id field is useful, hence it is unnecessary to create a nested dictionary.
53+
54+
Additionally, this allows bumped thread routes to simply return an
55+
array of thread_id ints instead of objects, saving on bandwidth.
56+
"""
57+
return [obj.thread_id for obj in objects]
58+
59+
60+
class BumpedThreadSerializer(ModelSerializer):
61+
"""A class providing (de-)serialization of `BumpedThread` instances."""
62+
63+
class Meta:
64+
"""Metadata defined for the Django REST Framework."""
65+
66+
list_serializer_class = ListBumpedThreadSerializer
67+
model = BumpedThread
68+
fields = ('thread_id',)
69+
70+
4471
class DeletedMessageSerializer(ModelSerializer):
4572
"""
4673
A class providing (de-)serialization of `DeletedMessage` instances.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from django.urls import reverse
2+
3+
from .base import AuthenticatedAPITestCase
4+
from ..models import BumpedThread
5+
6+
7+
class UnauthedBumpedThreadAPITests(AuthenticatedAPITestCase):
8+
def setUp(self):
9+
super().setUp()
10+
self.client.force_authenticate(user=None)
11+
12+
def test_detail_lookup_returns_401(self):
13+
url = reverse('api:bot:bumpedthread-detail', args=(1,))
14+
response = self.client.get(url)
15+
16+
self.assertEqual(response.status_code, 401)
17+
18+
def test_list_returns_401(self):
19+
url = reverse('api:bot:bumpedthread-list')
20+
response = self.client.get(url)
21+
22+
self.assertEqual(response.status_code, 401)
23+
24+
def test_create_returns_401(self):
25+
url = reverse('api:bot:bumpedthread-list')
26+
response = self.client.post(url, {"thread_id": 3})
27+
28+
self.assertEqual(response.status_code, 401)
29+
30+
def test_delete_returns_401(self):
31+
url = reverse('api:bot:bumpedthread-detail', args=(1,))
32+
response = self.client.delete(url)
33+
34+
self.assertEqual(response.status_code, 401)
35+
36+
37+
class BumpedThreadAPITests(AuthenticatedAPITestCase):
38+
@classmethod
39+
def setUpTestData(cls):
40+
cls.thread1 = BumpedThread.objects.create(
41+
thread_id=1234,
42+
)
43+
44+
def test_returns_bumped_threads_as_flat_list(self):
45+
url = reverse('api:bot:bumpedthread-list')
46+
47+
response = self.client.get(url)
48+
self.assertEqual(response.status_code, 200)
49+
self.assertEqual(response.json(), [1234])
50+
51+
def test_returns_204_for_existing_data(self):
52+
url = reverse('api:bot:bumpedthread-detail', args=(1234,))
53+
54+
response = self.client.get(url)
55+
self.assertEqual(response.status_code, 204)
56+
self.assertEqual(response.content, b"")
57+
58+
def test_returns_404_for_non_existing_data(self):
59+
url = reverse('api:bot:bumpedthread-detail', args=(42,))
60+
61+
response = self.client.get(url)
62+
self.assertEqual(response.status_code, 404)
63+
self.assertEqual(response.json(), {"detail": "Not found."})

pydis_site/apps/api/urls.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
AocAccountLinkViewSet,
77
AocCompletionistBlockViewSet,
88
BotSettingViewSet,
9+
BumpedThreadViewSet,
910
DeletedMessageViewSet,
1011
DocumentationLinkViewSet,
1112
FilterListViewSet,
@@ -21,13 +22,21 @@
2122
# https://www.django-rest-framework.org/api-guide/routers/#defaultrouter
2223
bot_router = DefaultRouter(trailing_slash=False)
2324
bot_router.register(
24-
'filter-lists',
25-
FilterListViewSet
25+
"aoc-account-links",
26+
AocAccountLinkViewSet
27+
)
28+
bot_router.register(
29+
"aoc-completionist-blocks",
30+
AocCompletionistBlockViewSet
2631
)
2732
bot_router.register(
2833
'bot-settings',
2934
BotSettingViewSet
3035
)
36+
bot_router.register(
37+
'bumped-threads',
38+
BumpedThreadViewSet
39+
)
3140
bot_router.register(
3241
'deleted-messages',
3342
DeletedMessageViewSet
@@ -37,12 +46,8 @@
3746
DocumentationLinkViewSet
3847
)
3948
bot_router.register(
40-
"aoc-account-links",
41-
AocAccountLinkViewSet
42-
)
43-
bot_router.register(
44-
"aoc-completionist-blocks",
45-
AocCompletionistBlockViewSet
49+
'filter-lists',
50+
FilterListViewSet
4651
)
4752
bot_router.register(
4853
'infractions',

pydis_site/apps/api/viewsets/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# flake8: noqa
22
from .bot import (
3-
FilterListViewSet,
43
BotSettingViewSet,
4+
BumpedThreadViewSet,
55
DeletedMessageViewSet,
66
DocumentationLinkViewSet,
7+
FilterListViewSet,
78
InfractionViewSet,
89
NominationViewSet,
910
OffensiveMessageViewSet,

pydis_site/apps/api/viewsets/bot/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# flake8: noqa
22
from .filter_list import FilterListViewSet
33
from .bot_setting import BotSettingViewSet
4+
from .bumped_thread import BumpedThreadViewSet
45
from .deleted_message import DeletedMessageViewSet
56
from .documentation_link import DocumentationLinkViewSet
67
from .infraction import InfractionViewSet
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from rest_framework.mixins import (
2+
CreateModelMixin, DestroyModelMixin, ListModelMixin
3+
)
4+
from rest_framework.request import Request
5+
from rest_framework.response import Response
6+
from rest_framework.viewsets import GenericViewSet
7+
8+
from pydis_site.apps.api.models.bot import BumpedThread
9+
from pydis_site.apps.api.serializers import BumpedThreadSerializer
10+
11+
12+
class BumpedThreadViewSet(
13+
GenericViewSet, CreateModelMixin, DestroyModelMixin, ListModelMixin
14+
):
15+
"""
16+
View providing CRUD (Minus the U) operations on threads to be bumped.
17+
18+
## Routes
19+
### GET /bot/bumped-threads
20+
Returns all BumpedThread items in the database.
21+
22+
#### Response format
23+
>>> list[int]
24+
25+
#### Status codes
26+
- 200: returned on success
27+
- 401: returned if unauthenticated
28+
29+
### GET /bot/bumped-threads/<thread_id:int>
30+
Returns whether a specific BumpedThread exists in the database.
31+
32+
#### Status codes
33+
- 204: returned on success
34+
- 404: returned if a BumpedThread with the given thread_id was not found.
35+
36+
### POST /bot/bumped-threads
37+
Adds a single BumpedThread item to the database.
38+
39+
#### Request body
40+
>>> {
41+
... 'thread_id': int,
42+
... }
43+
44+
#### Status codes
45+
- 201: returned on success
46+
- 400: if one of the given fields is invalid
47+
48+
### DELETE /bot/bumped-threads/<thread_id:int>
49+
Deletes the BumpedThread item with the given `thread_id`.
50+
51+
#### Status codes
52+
- 204: returned on success
53+
- 404: if a BumpedThread with the given `thread_id` does not exist
54+
"""
55+
56+
serializer_class = BumpedThreadSerializer
57+
queryset = BumpedThread.objects.all()
58+
59+
def retrieve(self, request: Request, *args, **kwargs) -> Response:
60+
"""
61+
DRF method for checking if the given BumpedThread exists.
62+
63+
Called by the Django Rest Framework in response to the corresponding HTTP request.
64+
"""
65+
self.get_object()
66+
return Response(status=204)

0 commit comments

Comments
 (0)