我正在使用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(...)”语句,以便如果该对象不再存在,则可以忽略引发的任何错误?
答案 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')