Spaces:
Runtime error
Runtime error
| """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, | |
| ) | |