diff options
author | 2022-02-20 17:43:54 +0100 | |
---|---|---|
committer | 2022-02-21 22:24:00 +0100 | |
commit | 26e4f518c874cafdee594c08c01d610e88528dc7 (patch) | |
tree | 637a3d87c4e73c364b101654dca505449990b11d /pydis_site/apps/api/viewsets | |
parent | Merge pull request #624 from python-discord/content/update-help-channel-timing (diff) |
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.
Diffstat (limited to 'pydis_site/apps/api/viewsets')
-rw-r--r-- | pydis_site/apps/api/viewsets/bot/infraction.py | 18 |
1 files changed, 18 insertions, 0 deletions
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.', + ] + } + ) |