Как откатить транзакции в цикле django

Я пытаюсь откатить группу транзакций, если в цикле возникают исключения. Но я не хочу выходить из цикла или генерировать исключение, не перехватив его.

Я не хочу, чтобы бизнес-логика сохранялась, если какой-либо из дочерних элементов в цикле выдает исключение. Это означает, что я не могу поместить транзакцию в цикл, потому что, если какая-либо из них завершится неудачей, транзакция будет отменена только для конкретного дочернего элемента.

parent = Parent.objects.get(pk='something')
exceptions = []
with transaction.atomic():
    for child in parent.children.all():
        try:
            # business logic which also saves other models
            # I don't want this saved if there is an exception for any object in the loop
        except Exception as e:
            exceptions.append({
                'id': child.id,
                'error': str(e),
            })
if len(exceptions) > 0:
    transaction.set_rollback(True)
    for exception in exceptions:
        Child.objects.filter(pk=exception['id']) \
            .update(error=exception['error']
    # more business logic and raise exception
    parent.is_blocked = True
    parent.save()
    # I don't want this exception to rollback all transactions
    raise Exception('Parent {} is blocked'.format(parent.id))

Я получаю сообщение об ошибке с приведенным выше кодом. Сообщение довольно прямое. Я пытаюсь откатить транзакцию вне блока.

django.db.transaction.TransactionManagementError: The rollback flag doesn't work outside of an 'atomic' block.

Кто-нибудь нашел способ справиться с чем-то вроде этого. Я надеюсь, что я просто пропустил что-то простое. Пожалуйста, дайте мне знать, если вам нужна дополнительная информация.


person brandonbanks    schedule 27.02.2018    source источник


Ответы (2)


Avoid catching exceptions inside atomic!

Следуйте документу, и в вашем конкретном случае ваш код должен быть таким:

parent = Parent.objects.get(pk='something')
exceptions = []
try:
    with transaction.atomic():
        for child in parent.children.all():
            try:
                # business logic which also saves other models
                # I don't want this saved if there is an exception for any object in the loop
            except Exception as e:
                exceptions.append({
                    'id': child.id,
                    'error': str(e),
                })
        # raise exception handly to trigger rollback
        if len(exceptions) > 0:
            raise("raise for rollback")
except Exception as e:
    pass

if len(exceptions) > 0:
    for exception in exceptions:
        Child.objects.filter(pk=exception['id']) \
            .update(error=exception['error']
    # more business logic and raise exception
    parent.is_blocked = True
    parent.save()
    # I don't want this exception to rollback all transactions
    raise Exception('Parent {} is blocked'.format(parent.id))
person Fogmoon    schedule 14.03.2019

Вы можете попробовать функцию генератора:

def function():
    for child in parent.children.all():
        try:
            yield result
        except Exception as e:
            yield exception

Вы можете проверить этот ответ для ясности: Как обрабатывать ошибку, возникающую в функции генератора

person Samuel Omole    schedule 27.02.2018
comment
Не могли бы вы дать еще некоторые пояснения? Как это решает проблему отката транзакции, если возникли исключения? - person brandonbanks; 27.02.2018
comment
Функция генератора в основном возвращает исключение при возникновении ошибки, поэтому, если вы проверите ссылку, которую я вам показал (это вопрос с ответом), вы увидите лучшее объяснение того, как ее реализовать. - person Samuel Omole; 27.02.2018
comment
Я ценю эту идею. Я не думаю, что это решает проблему. Я мог что-то упустить, но я уже ловлю исключение и добавляю его в массив. - person brandonbanks; 27.02.2018