【问题标题】:Replacing django decorator commit_manually with non_atomic_requests用 non_atomic_requests 替换 django 装饰器 commit_manually
【发布时间】:2019-10-21 10:13:34
【问题描述】:

我有一个导入 Excel 文件的 Django 视图,如果发生异常,我想捕获它们并全部报告,并回滚所有保存。即使我使用了 non_atomic_requests 装饰器,我也会收到 TransactionManagementError。

  1. 由于我也在使用 login_required 装饰器,我认为它们可能会相互干扰。首先我颠倒了顺序,然后我删除了所需的登录。没有变化。

  2. 我已尝试在全局范围内禁用自动事务。也许我没有做对,但这不是我想要的解决方案。

  3. 我删除了有问题的代码行(见下文),但是当我尝试回滚时发生了同样的错误

它在 Python 3.7.3 上运行,带有最新的 Django 并使用 SQLlite。我现在将它作为单元测试运行,尽管我可能在滥用这个术语。可以说它作为 Django TestCase 运行。

@transaction.non_atomic_requests
@login_required(login_url='/accounts/login/?next=/finance/gl_upload/')
def gl_upload(request):
    transaction.set_autocommit(False)
    if upriv(request.user, ['admin', 'finance']) == 'admin':
        if request.method == 'POST':

... file processing here ...

                except Exception as e:
                    errs.append(format('Exception "{1}" at row {0}\n'.format(p['rownum'], e)))
                if errs:
                    transaction.rollback()
                    rows_deleted = 0
                    rows_inserted = 0
                    print(''.join('Error: {0}\n'.format(e) for e in errs))
                else:
                    transaction.commit()
                    rows_deleted = Gldata.objects.filter(item='Actual', period_gte=older, period_lte=newest).delete()
                    rows_inserted = Gldata.objects.filter(item=temp_item).update(item='Actual')
                transaction.set_autocommit(True)
                print('Deleted: {0}, inserted: {1}'.format(rows_delete, rows_inserted))
                return render(request, 'gl_upload.html', {'inserted': rows_inserted, 'removed': rows_deleted, 'errors': errs})
            else:
                return render(request, 'gl_upload.html', {'form': form})
        else:
            form = uploadForm()
            return render(request, 'gl_upload.html', {'form': form})

我在 set_autocommit 上收到一个 TransactionManagementError 指示原子块处于活动状态,即使我知道装饰器会禁用它。几年前,我使用了旧的 commit_manually 装饰器,效果很好。

文件“C:\Users\csullivan\responsive\finance\views.py”,第 25 行,在 gl_upload 中 transaction.set_autocommit(False) 文件“C:\Users\csullivan\responsive\env\lib\site-packages\django\db\transaction.py”,第 30 行,在 set_autocommit 返回 get_connection(using).set_autocommit(autocommit) 文件“C:\Users\csullivan\responsive\env\lib\site-packages\django\db\backends\base\base.py”,第 394 行,在 set_autocommit self.validate_no_atomic_block() 文件“C:\Users\csullivan\responsive\env\lib\site-packages\django\db\backends\base\base.py”,第 433 行,在 validate_no_atomic_block “当‘原子’块处于活动状态时,这是禁止的。”) django.db.transaction.TransactionManagementError:当“原子”块处于活动状态时,这是禁止的。

【问题讨论】:

    标签: python django django-views


    【解决方案1】:

    我认为atomic() 装饰器/上下文管理器提供您所需要的 --如果底层代码成功则提交事务并回滚 当出现异常时。

    所以在你的情况下,我会这样做:

    with atomic():
      process_xls_files()
    

    您不需要手动调用transaction.set_autocommit() 和类似的低级方法, 除非你有非常特殊的需求并且“正常”的事务处理是不够的。

    【讨论】:

    • 谢谢托马斯。原子是视图的默认值,不是吗?我希望视图读取整个文件并跟踪它发现的错误(如果有)。如果没有错误,那么我想提交 INSERT(s),如果有错误,我想回滚并在响应中包含错误列表。我不想将整个文件读入内存并做一个巨大的 bulk_create,这将是另一种方法。因为这个文件是 50K 行,另一个类似的练习是 120K 行。
    • @Deepstop Autocommit 是默认设置,除非您使用 ATOMIC_REQUESTS=True 设置。根据您所说的,with atomic(): 方式将完全满足您的需求——数据库事务将“累积”您所有的INSERTs,如果没有错误,它们将在原子块退出后立即提交。如果出现错误,整个事务将被回滚。
    • 顺便说一句 - 即使你设置了ATOMIC_REQUESTS=Trueatomic() 也会有所帮助,因为它会创建一个以类似方式工作的保存点(主要区别在于提交的保存点可以仍然被回滚,例如当整个事务被回滚时)。
    • 这有点令人费解,但它确实有效。整个处理循环在 with 块中,如果填充了 errs 列表,则在循环结束时,我只需引发一个通用异常并将其捕获到 with 块之外。虽然我不得不说我更喜欢显式提交和回滚,但感谢您为我指出一个可行的解决方案!
    • @Deepstop 我很高兴它现在可以工作了!我想说的是,与atomic() 的“魔力”相比,手动管理事务要复杂得多。 atomic() 的全部意义在于它总是 做正确的事情,因此很难意外引入错误。而手动方式容易出错,因为您需要涵盖每个可能的场景。此外,多个嵌套的atomic() 块可以正常交互,最外层处理事务,内部处理保存点。
    猜你喜欢
    • 2018-02-08
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    • 2021-11-20
    • 2015-11-18
    • 1970-01-01
    • 1970-01-01
    • 2015-01-18
    相关资源
    最近更新 更多