Skip to content

Commit

Permalink
Merge branch 'main' into set-elidable-on-run-python-operations-in-mig…
Browse files Browse the repository at this point in the history
…rations
  • Loading branch information
wookie184 committed Apr 2, 2024
2 parents 9717773 + 6b128e1 commit a70ad81
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 55 deletions.
38 changes: 19 additions & 19 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions pydis_site/apps/api/migrations/0095_user_display_name.py
@@ -0,0 +1,18 @@
# Generated by Django 5.0.3 on 2024-04-01 12:21

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("api", "0094_migrate_mailing_listdata"),
]

operations = [
migrations.AddField(
model_name="user",
name="display_name",
field=models.CharField(blank=True, help_text="The display name, taken from Discord.", max_length=32),
),
migrations.RunSQL("UPDATE api_user SET display_name = name;", elidable=True),
]
5 changes: 5 additions & 0 deletions pydis_site/apps/api/models/bot/user.py
Expand Up @@ -33,6 +33,11 @@ class User(ModelReprMixin, models.Model):
max_length=32,
help_text="The username, taken from Discord.",
)
display_name = models.CharField(
max_length=32,
blank=True,
help_text="The display name, taken from Discord.",
)
discriminator = models.PositiveSmallIntegerField(
validators=(
MaxValueValidator(
Expand Down
4 changes: 2 additions & 2 deletions pydis_site/apps/api/serializers.py
Expand Up @@ -434,7 +434,7 @@ def to_representation(self, instance: FilterList) -> dict:
schema = {name: getattr(instance, name) for name in BASE_FILTERLIST_FIELDS}
schema['filters'] = [
FilterSerializer(many=False).to_representation(instance=item)
for item in Filter.objects.filter(filter_list=instance.id)
for item in Filter.objects.filter(filter_list=instance.id).prefetch_related('filter_list')
]

settings = {name: getattr(instance, name) for name in BASE_SETTINGS_FIELDS}
Expand Down Expand Up @@ -673,7 +673,7 @@ class Meta:
"""Metadata defined for the Django REST Framework."""

model = User
fields = ('id', 'name', 'discriminator', 'roles', 'in_guild')
fields = ('id', 'name', 'display_name', 'discriminator', 'roles', 'in_guild')
depth = 1
list_serializer_class = UserListSerializer

Expand Down
47 changes: 46 additions & 1 deletion pydis_site/apps/api/tests/test_filters.py
Expand Up @@ -6,7 +6,7 @@
from django.db.models import Model
from django.urls import reverse

from pydis_site.apps.api.models.bot.filters import Filter, FilterList
from pydis_site.apps.api.models.bot.filters import Filter, FilterList, FilterListType
from pydis_site.apps.api.tests.base import AuthenticatedAPITestCase


Expand Down Expand Up @@ -350,3 +350,48 @@ def test_filter_unique_constraint(self) -> None:

response = self.client.post(test_filter.url(), data=clean_test_json(test_filter.object))
self.assertEqual(response.status_code, 400)


class FilterCreationMissingOptionalFieldsTestCase(AuthenticatedAPITestCase):
@classmethod
def setUpTestData(cls):
cls.filter_list = FilterList.objects.create(
name="Ingsoc",
list_type=FilterListType.ALLOW,
dm_content="But if thought corrupts language, language can also corrupt thought.",
dm_embed="",
infraction_type="timeout",
infraction_duration=timedelta(days=80 * 365),
infraction_reason="Thoughtcrime",
infraction_channel=1,
guild_pings=["@BigBrother"],
filter_dm=False,
dm_pings=["@BigBrother"],
remove_context=True,
bypass_roles=[],
enabled=True,
send_alert=True,
enabled_channels=[],
disabled_channels=[],
enabled_categories=[],
disabled_categories=[],
)

def test_creation_missing_optional_fields(self) -> None:
data = {
"filter_list": self.filter_list.id,
"content": "1234567",
"description": "Guild \"Python\" - Phishing",
"additional_settings": {},
"guild_pings": [],
"infraction_type": "BAN",
"infraction_channel": 1,
"infraction_duration": 345600.0,
"infraction_reason": (
"The creatures outside looked from pig to man, and from man to pig, "
"and from pig to man again; but already it was impossible to say which was which"
)
}
endpoint = reverse('api:bot:filter-list')
response = self.client.post(endpoint, data=data)
self.assertEqual(response.status_code, 201)
23 changes: 21 additions & 2 deletions pydis_site/apps/api/tests/test_github_webhook_filter.py
@@ -1,3 +1,4 @@
import io
from unittest import mock
from urllib.error import HTTPError

Expand Down Expand Up @@ -44,8 +45,10 @@ def test_accepts_interesting_events(self):
context_mock.read.return_value = b'{"status": "ok"}'

response = self.client.post(url, data=payload, headers=headers)
self.assertEqual(response.status_code, context_mock.status)
self.assertEqual(response.headers.get('X-Clacks-Overhead'), 'Joe Armstrong')
response_body = response.json()
self.assertEqual(response.status_code, 200)
self.assertEqual(response_body.get("headers", {}).get("X-Clacks-Overhead"), 'Joe Armstrong')
self.assertEqual(response_body.get("original_status"), 299)

def test_rate_limit_is_logged_to_sentry(self):
url = reverse('api:github-webhook-filter', args=('id', 'token'))
Expand All @@ -56,6 +59,22 @@ def test_rate_limit_is_logged_to_sentry(self):
mock.patch.object(GitHubWebhookFilterView, "logger") as logger,
):
urlopen.side_effect = HTTPError(None, 429, 'Too Many Requests', {}, None)
urlopen.side_effect.fp = io.BytesIO()
logger.warning = mock.PropertyMock()
self.client.post(url, data=payload, headers=headers)

logger.warning.assert_called_once()

def test_other_error_is_logged(self):
url = reverse('api:github-webhook-filter', args=('id', 'token'))
payload = {}
headers = {'X-GitHub-Event': 'pull_request_review'}
with (
mock.patch('urllib.request.urlopen') as urlopen,
mock.patch.object(GitHubWebhookFilterView, "logger") as logger,
):
urlopen.side_effect = HTTPError(None, 451, 'Unavailable For Legal Reasons', {}, None)
urlopen.side_effect.fp = io.BytesIO()
logger.warning = mock.PropertyMock()
self.client.post(url, data=payload, headers=headers)

Expand Down
13 changes: 9 additions & 4 deletions pydis_site/apps/api/tests/test_users.py
Expand Up @@ -61,7 +61,8 @@ def test_accepts_valid_data(self):
url = reverse('api:bot:user-list')
data = {
'id': 42,
'name': "Test",
'name': "test",
'display_name': "Test Display",
'discriminator': 42,
'roles': [
self.role.id
Expand All @@ -75,6 +76,7 @@ def test_accepts_valid_data(self):

user = User.objects.get(id=42)
self.assertEqual(user.name, data['name'])
self.assertEqual(user.display_name, data['display_name'])
self.assertEqual(user.discriminator, data['discriminator'])
self.assertEqual(user.in_guild, data['in_guild'])

Expand All @@ -83,7 +85,8 @@ def test_supports_multi_creation(self):
data = [
{
'id': 5,
'name': "test man",
'name': "testman",
'display_name': "Test Display 1",
'discriminator': 42,
'roles': [
self.role.id
Expand All @@ -92,7 +95,8 @@ def test_supports_multi_creation(self):
},
{
'id': 8,
'name': "another test man",
'name': "anothertestman",
'display_name': "Test Display 2",
'discriminator': 555,
'roles': [],
'in_guild': False
Expand Down Expand Up @@ -200,7 +204,8 @@ def test_multiple_users_patch(self):
data = [
{
"id": 1,
"name": "User 1 patched!",
"name": "user1patched",
"display_name": "User 1 Patched",
"discriminator": 1010,
"roles": [self.role_developer.id],
"in_guild": False
Expand Down
23 changes: 20 additions & 3 deletions pydis_site/apps/api/views.py
Expand Up @@ -303,9 +303,26 @@ def post(self, request: Request, *, webhook_id: str, webhook_token: str) -> Resp
(response_status, headers, body) = self.send_webhook(
webhook_id, webhook_token, request.data, dict(request.headers),
)
headers.pop('Connection', None)
headers.pop('Content-Length', None)
return Response(data=body, headers=headers, status=response_status)

body_decoded = body.decode("utf-8")

if (
not (status.HTTP_200_OK <= response_status < status.HTTP_300_MULTIPLE_CHOICES)
and response_status != status.HTTP_429_TOO_MANY_REQUESTS
):
self.logger.warning(
"Failed to send GitHub webhook to Discord. Response code %d, body: %s",
response_status,
body_decoded,
)

response_body = {
"original_status": response_status,
"data": body_decoded,
"headers": headers,
}

return Response(response_body)

def send_webhook(
self,
Expand Down
14 changes: 11 additions & 3 deletions pydis_site/apps/api/viewsets/bot/user.py
Expand Up @@ -64,7 +64,8 @@ class UserViewSet(ModelViewSet):
... 'results': [
... {
... 'id': 409107086526644234,
... 'name': "Python",
... 'name': "python",
... 'display_name': "Python",
... 'discriminator': 4329,
... 'roles': [
... 352427296948486144,
Expand All @@ -79,6 +80,7 @@ class UserViewSet(ModelViewSet):
#### Optional Query Parameters
- username: username to search for
- display_name: display name to search for
- discriminator: discriminator to search for
- page_size: number of Users in one page, defaults to 10,000
- page: page number
Expand All @@ -92,7 +94,8 @@ class UserViewSet(ModelViewSet):
#### Response format
>>> {
... 'id': 409107086526644234,
... 'name': "Python",
... 'name': "python",
... 'display_name': "Python",
... 'discriminator': 4329,
... 'roles': [
... 352427296948486144,
Expand Down Expand Up @@ -170,6 +173,7 @@ class UserViewSet(ModelViewSet):
>>> {
... 'id': int,
... 'name': str,
... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
Expand All @@ -192,6 +196,7 @@ class UserViewSet(ModelViewSet):
>>> {
... 'id': int,
... 'name': str,
... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
Expand All @@ -210,6 +215,7 @@ class UserViewSet(ModelViewSet):
>>> {
... 'id': int,
... 'name': str,
... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
Expand All @@ -229,13 +235,15 @@ class UserViewSet(ModelViewSet):
... {
... 'id': int,
... 'name': str,
... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
... },
... {
... 'id': int,
... 'name': str,
... 'display_name': str,
... 'discriminator': int,
... 'roles': List[int],
... 'in_guild': bool
Expand All @@ -260,7 +268,7 @@ class UserViewSet(ModelViewSet):
queryset = User.objects.all().order_by("id")
pagination_class = UserListPagination
filter_backends = (DjangoFilterBackend,)
filterset_fields = ('name', 'discriminator')
filterset_fields = ('name', 'discriminator', 'display_name')

def get_serializer(self, *args, **kwargs) -> ModelSerializer:
"""Set Serializer many attribute to True if request body contains a list."""
Expand Down
Expand Up @@ -15,7 +15,7 @@ This page will focus on the quickest steps one can take, with mentions of altern
### Setup Project Dependencies
Below are the dependencies you **must** have installed to get started with the bot.

1. Make sure you have [Python 3.11](https://www.python.org/downloads/) installed. It helps if it is your system's default Python version.
1. Make sure you have [Python 3.12](https://www.python.org/downloads/) installed. It helps if it is your system's default Python version.
1. [Install Poetry](https://github.com/python-poetry/poetry#installation).
1. [Install the project's dependencies](../installing-project-dependencies).
1. Docker.
Expand Down
Expand Up @@ -10,7 +10,7 @@ You should have already forked the [`sir-lancebot`](https://github.com/python-di
Remember to ensure that you have read the [contributing guidelines](../contributing-guidelines) in full before you start contributing.

### Requirements
- [Python 3.10.*](https://www.python.org/downloads/)
- [Python 3.12.*](https://www.python.org/downloads/)
- [Poetry](https://github.com/python-poetry/poetry#installation)
- [Git](https://git-scm.com/downloads)
- [Windows Installer](https://git-scm.com/download/win)
Expand Down

0 comments on commit a70ad81

Please sign in to comment.