Django:使用原子事务时,在 catch 块中向数据库写入内容

问题描述 投票:0回答:2

我有一个 Django REST Framework 序列化程序,它使用

select_for_update
结合原子转换,像这样:https://docs.djangoproject.com/en/4.2/ref/models/querysets/#select-for-update/ .这工作正常,除了我想在抛出错误时向数据库写入一些东西......并且这些插入语句正在回滚,永远不会进入数据库。

代码是这样的(非常简化,但可以理解并可重现):

模型.py

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)

views.py

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)

我面临的问题是,当外部 SDK 触发错误并且我试图向数据库写入一些内容时,这永远不会在数据库中结束,事务只是回滚。

如何确保在使用原子事务时仍然可以在 except 块中向数据库写入内容?

django django-orm django-database
2个回答
0
投票

您可以使用上下文管理器代替装饰器。例如:

class MySerializer(Serializer):
    # Some serializer fields here..

    def save(self):
        try:
           with transaction.atomic():
               foo = Foo.objects.all().select_for_update()
               SomeExternalSDK.doStuff(foo)
        except SomeExternalSDK.SomeException as e:
           log = Log(message="Uh oh")
           log.save()
           raise ValidationError("Something went wrong")

0
投票

正如

atomic()
文档中提到的,它将在异常时回滚事务,因此我认为没有办法在错误期间将信息直接存储在数据库中。

但是你总是可以在原子块之外捕获异常,然后保存错误并像这样重新引发错误:

class MySerializer(Serializer):
    # Some serializer fields here..


    def save(self):
       try:
           self.save_data()
       except ValidationError as e:
           Log.objects.create(error = str(e))
           raise e

    @transaction.atomic
    def save_data(self):
        foo = Foo.objects.all().select_for_update()
        self.do_something(foo)
        self.do_something_else(foo)

    def do_something(self, foo):
        try:
           SomeExternalSDK.doStuff(foo)
        except SomeExternalSDK.SomeException as e:
           raise ValidationError(str(e))

    def self.do_something_else(self, foo):
        pass

或者,您可以创建一个对象变量(如列表),您可以在其中放置 SDK 中发生的异常,然后将这些错误存储在数据库中。

通常,日志不存储在数据库中,而是存储在文件中。您可以考虑将错误存储在文件系统中,而不是将错误存储在数据库中,或者使用 Django 的日志记录 来存储错误。

© www.soinside.com 2019 - 2024. All rights reserved.