I have a Django REST Framework serializer which uses select_for_update
in combination with atomic transitions, like this: https://docs.djangoproject.com/en/4.2/ref/models/querysets/#select-for-update/. That works fine, except that I want to write something to the database when an error is thrown... and these insert statements are getting rolled back, never making it to the database.
The code is something like this (very much simplified but gets the point across and is reproducible):
from django.db import models
class LicenseCode(models.Model):
code = models.CharField(max_length=200, unique=True)
class Activation(models.Model):
license_code = models.TextField(max_length=200)
activation_log = models.TextField(blank=True, null=True)
success = models.BooleanField(default=False)
from django.http import HttpResponse, Http404
from django.db import transaction
from .models import Activation, LicenseCode
class LicenseRedeemSerializer:
@transaction.atomic
def save(self):
license_codes = LicenseCode.objects.all().select_for_update()
activations = []
for license_code in license_codes:
activations.append(
Activation(license_code=license_code.code, success=False)
)
self.activate_licenses(activations)
def activate_licenses(self, activations):
try:
# In our real app we'd try to activate the licenses with an external SDK. This can fail.
raise Exception("Something went wrong!")
except Exception as e:
for activation in activations:
activation.activation_log = str(e)
activation.save()
# With our real DRF serializer we'd raise ValidationError
raise Http404("Could not activate the license!")
def view(request):
# Let's make sure we have a license code to work with
LicenseCode.objects.get_or_create(code="A")
serialier = LicenseRedeemSerializer()
serialier.save()
html = "Hello there"
return HttpResponse(html)
The problem I am facing is that when the external SDK triggers an error and I'm trying to write something to the database, that this never ends up in the database, the transaction is just rolled back.
How can I make sure that I can still write something to the database when using atomic transactions, in the except block?
Instead of decorator you can use context manager. For example:
from django.http import HttpResponse, Http404
from django.db import transaction
from .models import Activation, LicenseCode
class LicenseRedeemSerializer:
def save(self):
license_codes = LicenseCode.objects.all().select_for_update()
activations = []
with transaction.atomic():
for license_code in license_codes:
activations.append(
Activation(license_code=license_code.code, success=False)
)
self.activate_licenses(activations)
def activate_licenses(self, activations):
try:
# In our real app we'd try to activate the licenses with an external SDK. This can fail.
raise Exception("Something went wrong!")
except Exception as e:
for activation in activations:
activation.activation_log = str(e)
activation.save()
# With our real DRF serializer we'd raise ValidationError
raise Http404("Could not activate the license!")
def view(request):
# Let's make sure we have a license code to work with
LicenseCode.objects.get_or_create(code="A")
serialier = LicenseRedeemSerializer()
serialier.save()
html = "Hello there"
return HttpResponse(html)