Skip to content

Commit 4ab7972

Browse files
authored
Merge pull request #508 from RohanJnr/otn_softdel
offtopicnames active attribute and support for PUT and PATCH request
2 parents b52587d + c4e2eda commit 4ab7972

File tree

8 files changed

+149
-37
lines changed

8 files changed

+149
-37
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 3.0.14 on 2021-05-19 05:45
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0069_documentationlink_validators'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='offtopicchannelname',
15+
name='active',
16+
field=models.BooleanField(default=True, help_text='Whether or not this name should be considered for naming channels.'),
17+
),
18+
migrations.AlterField(
19+
model_name='offtopicchannelname',
20+
name='used',
21+
field=models.BooleanField(default=False, help_text='Whether or not this name has already been used during this rotation.'),
22+
),
23+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Generated by Django 3.0.14 on 2021-07-24 13:54
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0071_increase_message_content_4000'),
10+
('api', '0070_auto_20210519_0545'),
11+
]
12+
13+
operations = [
14+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Generated by Django 3.0.14 on 2021-11-05 05:18
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0072_merge_20210724_1354'),
10+
('api', '0073_otn_allow_GT_and_LT'),
11+
]
12+
13+
operations = [
14+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Generated by Django 3.1.14 on 2021-12-13 05:52
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0077_use_generic_jsonfield'),
10+
('api', '0074_merge_20211105_0518'),
11+
]
12+
13+
operations = [
14+
]

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ class OffTopicChannelName(ModelReprMixin, models.Model):
1818

1919
used = models.BooleanField(
2020
default=False,
21-
help_text="Whether or not this name has already been used during this rotation",
21+
help_text="Whether or not this name has already been used during this rotation.",
22+
)
23+
24+
active = models.BooleanField(
25+
default=True,
26+
help_text="Whether or not this name should be considered for naming channels."
2227
)
2328

2429
def __str__(self):

pydis_site/apps/api/serializers.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -209,25 +209,30 @@ def to_representation(self, instance: Infraction) -> dict:
209209
return ret
210210

211211

212+
class OffTopicChannelNameListSerializer(ListSerializer):
213+
"""Custom ListSerializer to override to_representation() when list views are triggered."""
214+
215+
def to_representation(self, objects: list[OffTopicChannelName]) -> list[str]:
216+
"""
217+
Return a list with all `OffTopicChannelName`s in the database.
218+
219+
This returns the list of off topic channel names. We want to only return
220+
the name attribute, hence it is unnecessary to create a nested dictionary.
221+
Additionally, this allows off topic channel name routes to simply return an
222+
array of names instead of objects, saving on bandwidth.
223+
"""
224+
return [obj.name for obj in objects]
225+
226+
212227
class OffTopicChannelNameSerializer(ModelSerializer):
213228
"""A class providing (de-)serialization of `OffTopicChannelName` instances."""
214229

215230
class Meta:
216231
"""Metadata defined for the Django REST Framework."""
217232

233+
list_serializer_class = OffTopicChannelNameListSerializer
218234
model = OffTopicChannelName
219-
fields = ('name',)
220-
221-
def to_representation(self, obj: OffTopicChannelName) -> str:
222-
"""
223-
Return the representation of this `OffTopicChannelName`.
224-
225-
This only returns the name of the off topic channel name. As the model
226-
only has a single attribute, it is unnecessary to create a nested dictionary.
227-
Additionally, this allows off topic channel name routes to simply return an
228-
array of names instead of objects, saving on bandwidth.
229-
"""
230-
return obj.name
235+
fields = ('name', 'used', 'active')
231236

232237

233238
class ReminderSerializer(ModelSerializer):

pydis_site/apps/api/tests/test_off_topic_channel_names.py

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,15 @@ def test_returns_400_for_negative_random_items_param(self):
6565
class ListTests(AuthenticatedAPITestCase):
6666
@classmethod
6767
def setUpTestData(cls):
68-
cls.test_name = OffTopicChannelName.objects.create(name='lemons-lemonade-stand', used=False)
69-
cls.test_name_2 = OffTopicChannelName.objects.create(name='bbq-with-bisk', used=True)
68+
cls.test_name = OffTopicChannelName.objects.create(
69+
name='lemons-lemonade-stand', used=False, active=True
70+
)
71+
cls.test_name_2 = OffTopicChannelName.objects.create(
72+
name='bbq-with-bisk', used=False, active=True
73+
)
74+
cls.test_name_3 = OffTopicChannelName.objects.create(
75+
name="frozen-with-iceman", used=True, active=False
76+
)
7077

7178
def test_returns_name_in_list(self):
7279
"""Return all off-topic channel names."""
@@ -75,29 +82,55 @@ def test_returns_name_in_list(self):
7582

7683
self.assertEqual(response.status_code, 200)
7784
self.assertEqual(
78-
response.json(),
79-
[
85+
set(response.json()),
86+
{
8087
self.test_name.name,
81-
self.test_name_2.name
82-
]
88+
self.test_name_2.name,
89+
self.test_name_3.name
90+
}
8391
)
8492

85-
def test_returns_single_item_with_random_items_param_set_to_1(self):
93+
def test_returns_two_items_with_random_items_param_set_to_2(self):
8694
"""Return not-used name instead used."""
8795
url = reverse('api:bot:offtopicchannelname-list')
88-
response = self.client.get(f'{url}?random_items=1')
96+
response = self.client.get(f'{url}?random_items=2')
8997

9098
self.assertEqual(response.status_code, 200)
91-
self.assertEqual(len(response.json()), 1)
92-
self.assertEqual(response.json(), [self.test_name.name])
99+
self.assertEqual(len(response.json()), 2)
100+
self.assertEqual(set(response.json()), {self.test_name.name, self.test_name_2.name})
93101

94102
def test_running_out_of_names_with_random_parameter(self):
95103
"""Reset names `used` parameter to `False` when running out of names."""
96104
url = reverse('api:bot:offtopicchannelname-list')
97-
response = self.client.get(f'{url}?random_items=2')
105+
response = self.client.get(f'{url}?random_items=3')
98106

99107
self.assertEqual(response.status_code, 200)
100-
self.assertEqual(response.json(), [self.test_name.name, self.test_name_2.name])
108+
self.assertEqual(
109+
set(response.json()),
110+
{self.test_name.name, self.test_name_2.name, self.test_name_3.name}
111+
)
112+
113+
def test_returns_inactive_ot_names(self):
114+
"""Return inactive off topic names."""
115+
url = reverse('api:bot:offtopicchannelname-list')
116+
response = self.client.get(f"{url}?active=false")
117+
118+
self.assertEqual(response.status_code, 200)
119+
self.assertEqual(
120+
response.json(),
121+
[self.test_name_3.name]
122+
)
123+
124+
def test_returns_active_ot_names(self):
125+
"""Return active off topic names."""
126+
url = reverse('api:bot:offtopicchannelname-list')
127+
response = self.client.get(f"{url}?active=true")
128+
129+
self.assertEqual(response.status_code, 200)
130+
self.assertEqual(
131+
set(response.json()),
132+
{self.test_name.name, self.test_name_2.name}
133+
)
101134

102135

103136
class CreationTests(AuthenticatedAPITestCase):

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
from django.db.models import Case, Value, When
22
from django.db.models.query import QuerySet
3-
from django.http.request import HttpRequest
43
from django.shortcuts import get_object_or_404
54
from rest_framework.exceptions import ParseError
6-
from rest_framework.mixins import DestroyModelMixin
5+
from rest_framework.request import Request
76
from rest_framework.response import Response
87
from rest_framework.status import HTTP_201_CREATED
9-
from rest_framework.viewsets import ViewSet
8+
from rest_framework.viewsets import ModelViewSet
109

1110
from pydis_site.apps.api.models.bot.off_topic_channel_name import OffTopicChannelName
1211
from pydis_site.apps.api.serializers import OffTopicChannelNameSerializer
1312

1413

15-
class OffTopicChannelNameViewSet(DestroyModelMixin, ViewSet):
14+
class OffTopicChannelNameViewSet(ModelViewSet):
1615
"""
1716
View of off-topic channel names used by the bot to rotate our off-topic names on a daily basis.
1817
@@ -58,22 +57,22 @@ class OffTopicChannelNameViewSet(DestroyModelMixin, ViewSet):
5857

5958
lookup_field = 'name'
6059
serializer_class = OffTopicChannelNameSerializer
60+
queryset = OffTopicChannelName.objects.all()
6161

6262
def get_object(self) -> OffTopicChannelName:
6363
"""
6464
Returns the OffTopicChannelName entry for this request, if it exists.
6565
6666
If it doesn't, a HTTP 404 is returned by way of throwing an exception.
6767
"""
68-
queryset = self.get_queryset()
6968
name = self.kwargs[self.lookup_field]
70-
return get_object_or_404(queryset, name=name)
69+
return get_object_or_404(self.queryset, name=name)
7170

7271
def get_queryset(self) -> QuerySet:
7372
"""Returns a queryset that covers the entire OffTopicChannelName table."""
7473
return OffTopicChannelName.objects.all()
7574

76-
def create(self, request: HttpRequest) -> Response:
75+
def create(self, request: Request, *args, **kwargs) -> Response:
7776
"""
7877
DRF method for creating a new OffTopicChannelName.
7978
@@ -91,7 +90,7 @@ def create(self, request: HttpRequest) -> Response:
9190
'name': ["This query parameter is required."]
9291
})
9392

94-
def list(self, request: HttpRequest) -> Response:
93+
def list(self, request: Request, *args, **kwargs) -> Response:
9594
"""
9695
DRF method for listing OffTopicChannelName entries.
9796
@@ -109,13 +108,13 @@ def list(self, request: HttpRequest) -> Response:
109108
'random_items': ["Must be a positive integer."]
110109
})
111110

112-
queryset = self.get_queryset().order_by('used', '?')[:random_count]
111+
queryset = self.queryset.order_by('used', '?')[:random_count]
113112

114113
# When any name is used in our listing then this means we reached end of round
115114
# and we need to reset all other names `used` to False
116115
if any(offtopic_name.used for offtopic_name in queryset):
117116
# These names that we just got have to be excluded from updating used to False
118-
self.get_queryset().update(
117+
self.queryset.update(
119118
used=Case(
120119
When(
121120
name__in=(offtopic_name.name for offtopic_name in queryset),
@@ -126,13 +125,18 @@ def list(self, request: HttpRequest) -> Response:
126125
)
127126
else:
128127
# Otherwise mark selected names `used` to True
129-
self.get_queryset().filter(
128+
self.queryset.filter(
130129
name__in=(offtopic_name.name for offtopic_name in queryset)
131130
).update(used=True)
132131

133132
serialized = self.serializer_class(queryset, many=True)
134133
return Response(serialized.data)
135134

136-
queryset = self.get_queryset()
135+
params = {}
136+
137+
if active_param := request.query_params.get("active"):
138+
params["active"] = active_param.lower() == "true"
139+
140+
queryset = self.queryset.filter(**params)
137141
serialized = self.serializer_class(queryset, many=True)
138142
return Response(serialized.data)

0 commit comments

Comments
 (0)