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

问题描述 投票:0回答:4

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

django python-3.x django-models sql-update
4个回答
18
投票
gachdavit 的

评论建议使用select_for_update

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

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


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
投票
这很 hacky,但您可以在模型中覆盖

_do_update

 并简单地返回 True
。 Django 本身在 
_do_updateline 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')
    

0
投票
就我而言,我使用 Postgres 的主实例和只读副本。当我删除只读副本时,我能够成功登录。

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'
    
© www.soinside.com 2019 - 2024. All rights reserved.