我正在使用 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(...)”语句,以便如果该对象不再存在我可以忽略抛出的任何错误?
get_article
函数以在获取文章之前调用
select_for_update
。通过这样做,只要当前事务不提交或回滚,保存该文章的数据库行就会被锁定。如果另一个线程尝试同时删除该文章,该线程将阻塞,直到锁被释放。实际上,只有在您调用
save
函数之后,该文章才会被删除。除非您有特殊要求,否则这就是我采取的方法。
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
编辑: 另一种选择是捕获并处理异常。
True
。 Django 本身在
_do_update
的 line 893上做了一些 hacky 操作,以在
update_fields
包含未出现在模型中的列名称时抑制相同的异常。
_do_update
的返回值会触发您在此块中看到的异常 我测试了下面的覆盖,它似乎有效。我觉得重写私有方法有点肮脏,但我想我会克服它。
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 基类。
我用这个
django管理命令来测试
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')
from django.conf import settings
class PrimaryReplicaRouter:
def db_for_read(self, model, **hints):
return 'read_replica' if 'read_replica' in settings.DATABASES else 'default'
def db_for_write(self, model, **hints):
return 'default'
def allow_relation(self, obj1, obj2, **hints):
return True
def allow_migrate(self, db, app_label, model_name=None, **hints):
return db == 'default'