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

0qx6xfy6  于 2023-04-13  发布在  Go
关注(0)|答案(2)|浏览(156)

bounty还有6天到期。回答此问题可获得+200声望奖励。Kevin Renskers希望奖励现有答案

我有一个Django REST Framework序列化器,它使用select_for_update和原子转换,如下所示:这https://docs.djangoproject.com/en/4.2/ref/models/querysets/#select-for-update/很好,除了我想在抛出错误时向数据库写入一些东西......这些insert语句被回滚,永远不会到达数据库。
代码是这样的(非常简化,但得到了点,是可复制的):

models.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块中仍然可以向数据库写入内容?

gwo2fgha

gwo2fgha1#

正如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中发生的异常放入其中,然后将这些错误存储在DB中。
通常,日志不存储在数据库中,而是存储在文件中。您可以考虑将错误存储在文件系统中或使用Django's logging来存储错误,而不是将错误存储在数据库中。

更新

你可以删除允许回滚发生的嵌套异常引发。相反,你可以使用一个标志来在以后引发404。

@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)
        )

    error = self.activate_licenses(activations)  # using flag to see if there is any error
    return error

def activate_licenses(self, activations):
    has_error = False
    try:
        raise Exception("Something went wrong!")

    except Exception as e:
        for activation in activations:
            activation.activation_log = str(e)
            activation.save()
        # flag
        has_error = True
    return has_error


 #view
   def view(request):
    # Let's make sure we have a license code to work with
    LicenseCode.objects.get_or_create(code="A")

    serialier = LicenseRedeemSerializer()
    error = serialier.save()
    if error:
       raise Http404('error activation')

或者,您可以查看保存点,但我不确定它如何适用于您的代码。

knpiaxh1

knpiaxh12#

你可以使用context manager来代替decorator。例如:

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)

相关问题