gapguide-api / config /mixins.py
arifRB's picture
Deploy GapGuide backend (Docker)
ffd36e0 verified
Raw
History Blame Contribute Delete
1.54 kB
"""Reusable DRF viewset mixins.
`IntegrityConflictMixin` converts a DB-level uniqueness *race* into a clean
409. It is not the first line of defense: DRF's auto-generated
`UniqueValidator` / `UniqueTogetherValidator` already reject the common
(non-concurrent) duplicate with a 400 during `is_valid()`, before the DB is
touched. This mixin only catches the genuine concurrent race that slips past
validation (two requests both pass `is_valid`, both reach the INSERT) — which
would otherwise surface as an uncaught 500.
"""
from django.db import IntegrityError, transaction
from rest_framework import status
from rest_framework.response import Response
class IntegrityConflictMixin:
"""Mix into a ModelViewSet to turn an INSERT-time IntegrityError into 409.
The create is wrapped in its own `atomic()` block so the catch is
savepoint-safe regardless of `ATOMIC_REQUESTS`: the failed INSERT rolls
back cleanly and the `except` runs outside the (now-closed) block, so no
later query hits a poisoned transaction.
Set `conflict_detail` per viewset for a user-facing message.
"""
conflict_detail = 'This record already exists.'
def create(self, request, *args, **kwargs):
try:
with transaction.atomic():
return super().create(request, *args, **kwargs)
except IntegrityError:
return Response(
{'detail': self.conflict_detail},
status=status.HTTP_409_CONFLICT,
)