在Django中,如何防止“使用update_fields保存不会影响任何行”。错误?

时间:2019-05-22 14:22:03

标签: django python-3.x django-models sql-update

我正在使用Django和Python 3.7。我有这个代码

article = get_article(id)
...
article.label = label
article.save(update_fields=["label"])

有时我在“保存”行上遇到以下错误...

    raise DatabaseError("Save with update_fields did not affect any rows.")
django.db.utils.DatabaseError: Save with update_fields did not affect any rows.

很明显,在“ ...”中,另一个线程可能正在删除我的文章。还有另一种方法可以重写我的“ article.save(...)”语句,以便如果该对象不再存在,则可以忽略引发的任何错误?

3 个答案:

答案 0 :(得分:5)

gachdavit的comment建议使用select_for_update。您可以在提取文章之前修改get_article函数以调用select_for_update。这样,只要当前事务不提交或不回滚,保存该文章的数据库行将被锁定。如果另一个线程试图同时删除该文章,则该线程将阻塞直到释放锁。实际上,只有调用save函数之后,该文章才会被删除。

除非您有特殊要求,否则这是我要采取的方法。

答案 1 :(得分:2)

除了检查值是否已更改外,我不知道有什么特殊的处理方法。

article = update_model(article, {'label': label})


def update_model(instance, updates):
    update_fields = {
        field: value
        for field, value in updates.items()
        if getattr(instance, field) != value
    }
    if update_fields:
        for field, value in update_fields.items():
            setattr(instance, field, value)
        instance.save(update_fields=update_fields.keys())
    return instance

编辑: 另一种选择是捕获并处理异常。

答案 2 :(得分:2)

这很容易,但是您可以在模型中覆盖_do_update并仅返回True。当_do_update包含模型中未出现的列名时,Django本身对update_fields的{​​{3}}进行了某种修改,以抑制相同的异常。

_do_update的返回值触发您从line 893看到的异常

我测试了下面的覆盖,它似乎可以工作。我为重写私用方法感到有些肮脏,但我想我会克服它。

def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_update):
    updated = super(Article, self)._do_update(base_qs, using, pk_val, values, update_fields, forced_update)
    if not updated and Article.objects.filter(id=pk_val).count() == 0:
        return True
    return updated

如果您需要为多个模型处理此解决方案,则可以通用此解决方案并将其移至mixin基类。

我用这个this block进行测试

from django.core.management.base import BaseCommand
from foo.models import Article

class Command(BaseCommand):
    def handle(self, *args, **kwargs):
        Article.objects.update_or_create(id=1, defaults=dict(label='zulu'))

        print('Testing _do_update hack')
        article1 = Article.objects.get(id=1)
        article1.label = 'yankee'
        article2 = Article.objects.get(id=1)
        article2.delete()

        article1.save(update_fields=['label'])
        print('Done. No exception raised')