diff options
| -rw-r--r-- | poetry.lock | 20 | ||||
| -rw-r--r-- | pydis_site/apps/api/tests/test_bumped_threads.py | 2 | ||||
| -rw-r--r-- | pydis_site/apps/api/tests/test_filters.py | 7 | ||||
| -rw-r--r-- | pydis_site/apps/api/tests/test_infractions.py | 2 | ||||
| -rw-r--r-- | pydis_site/apps/api/tests/test_nominations.py | 4 | ||||
| -rw-r--r-- | pydis_site/apps/api/tests/test_roles.py | 2 | ||||
| -rw-r--r-- | pydis_site/apps/api/urls.py | 3 | ||||
| -rw-r--r-- | pydis_site/apps/api/viewsets/bot/infraction.py | 26 | ||||
| -rw-r--r-- | pyproject.toml | 2 | 
9 files changed, 50 insertions, 18 deletions
diff --git a/poetry.lock b/poetry.lock index 86970757..8239793e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -462,17 +462,18 @@ dev = ["PyGithub (>=1.43,<2.0)", "flake8 (>=3.8,<4.0)", "flake8-annotations (>=2  [[package]]  name = "djangorestframework" -version = "3.15.1" +version = "3.14.0"  description = "Web APIs for Django, made easy."  optional = false  python-versions = ">=3.6"  files = [ -    {file = "djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6"}, -    {file = "djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1"}, +    {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, +    {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"},  ]  [package.dependencies]  django = ">=3.0" +pytz = "*"  [[package]]  name = "filelock" @@ -912,6 +913,17 @@ docs = ["sphinx"]  test = ["mypy", "pyaml", "pytest", "toml", "types-PyYAML", "types-toml"]  [[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ +    {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, +    {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[package]]  name = "pyyaml"  version = "6.0.1"  description = "YAML parser and emitter for Python" @@ -1212,4 +1224,4 @@ brotli = ["Brotli"]  [metadata]  lock-version = "2.0"  python-versions = "3.11.*" -content-hash = "4f8439d34f38d81f62fb06e19a913cb8358ddf968aa382378aebc07724dd2dd1" +content-hash = "4028778c4e713fb935bcc463c3ed209f6251f09c62db700b95524469022b7895" diff --git a/pydis_site/apps/api/tests/test_bumped_threads.py b/pydis_site/apps/api/tests/test_bumped_threads.py index 72f475c6..2e3892c7 100644 --- a/pydis_site/apps/api/tests/test_bumped_threads.py +++ b/pydis_site/apps/api/tests/test_bumped_threads.py @@ -60,4 +60,4 @@ class BumpedThreadAPITests(AuthenticatedAPITestCase):          response = self.client.get(url)          self.assertEqual(response.status_code, 404) -        self.assertEqual(response.json(), {"detail": "No BumpedThread matches the given query."}) +        self.assertEqual(response.json(), {"detail": "Not found."}) diff --git a/pydis_site/apps/api/tests/test_filters.py b/pydis_site/apps/api/tests/test_filters.py index 9771bacc..4cef1c8f 100644 --- a/pydis_site/apps/api/tests/test_filters.py +++ b/pydis_site/apps/api/tests/test_filters.py @@ -211,12 +211,7 @@ class GenericFilterTests(AuthenticatedAPITestCase):                  response = self.client.get(f"{sequence.url()}/42")                  self.assertEqual(response.status_code, 404) -                parsed = response.json() -                self.assertIn('detail', parsed) -                self.assertIn(parsed['detail'], ( -                    "No Filter matches the given query.", -                    "No FilterList matches the given query." -                )) +                self.assertDictEqual(response.json(), {'detail': 'Not found.'})      def test_creation(self) -> None:          for name, sequence in get_test_sequences().items(): diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py index b82fb66c..f1e54b1e 100644 --- a/pydis_site/apps/api/tests/test_infractions.py +++ b/pydis_site/apps/api/tests/test_infractions.py @@ -559,7 +559,7 @@ class CreationTests(AuthenticatedAPITestCase):                      second_response.json(),                      {                          'non_field_errors': [ -                            'The fields user, type must make a unique set.' +                            'This user already has an active infraction of this type.'                          ]                      }                  ) diff --git a/pydis_site/apps/api/tests/test_nominations.py b/pydis_site/apps/api/tests/test_nominations.py index 7c6f1bbb..e4dfe36a 100644 --- a/pydis_site/apps/api/tests/test_nominations.py +++ b/pydis_site/apps/api/tests/test_nominations.py @@ -379,7 +379,7 @@ class NominationTests(AuthenticatedAPITestCase):          response = self.client.get(url, data={})          self.assertEqual(response.status_code, 404)          self.assertEqual(response.json(), { -            "detail": "No Nomination matches the given query." +            "detail": "Not found."          })      def test_returns_404_on_patch_unknown_nomination(self): @@ -391,7 +391,7 @@ class NominationTests(AuthenticatedAPITestCase):          response = self.client.patch(url, data={})          self.assertEqual(response.status_code, 404)          self.assertEqual(response.json(), { -            "detail": "No Nomination matches the given query." +            "detail": "Not found."          })      def test_returns_405_on_list_put(self): diff --git a/pydis_site/apps/api/tests/test_roles.py b/pydis_site/apps/api/tests/test_roles.py index 8ca6e7c1..d3031990 100644 --- a/pydis_site/apps/api/tests/test_roles.py +++ b/pydis_site/apps/api/tests/test_roles.py @@ -208,4 +208,4 @@ class CreationTests(AuthenticatedAPITestCase):          for method in ('get', 'put', 'patch', 'delete'):              response = getattr(self.client, method)(url)              self.assertEqual(response.status_code, 404) -            self.assertJSONEqual(response.content, '{"detail": "No Role matches the given query."}') +            self.assertJSONEqual(response.content, '{"detail": "Not found."}') diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py index 58e4878b..5cda033a 100644 --- a/pydis_site/apps/api/urls.py +++ b/pydis_site/apps/api/urls.py @@ -30,8 +30,7 @@ from .viewsets import (  bot_router = DefaultRouter(trailing_slash=False)  bot_router.register(      'filter/filter_lists', -    FilterListViewSet, -    basename='filter-filter-lists', +    FilterListViewSet  )  bot_router.register(      "aoc-account-links", diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py index 254a588d..8da82822 100644 --- a/pydis_site/apps/api/viewsets/bot/infraction.py +++ b/pydis_site/apps/api/viewsets/bot/infraction.py @@ -1,5 +1,6 @@  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 @@ -274,3 +275,28 @@ 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 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 diff --git a/pyproject.toml b/pyproject.toml index 3b21aa08..760ec819 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ django-environ = "0.11.2"  django-filter = "24.2"  django-prometheus = "2.3.1"  django-simple-bulma = "2.6.0" -djangorestframework = "3.15.1" +djangorestframework = "3.14.0"  gunicorn = "21.2.0"  httpx = "0.27.0"  markdown = "3.6"  |