如何在Django ORM中通过get()检索到的单个模型实例上执行update()操作?

109

我有一个函数,目前调用了Models.object.get(),该函数返回0或1个模型对象:

  • 如果它返回0,在函数的except DoesNotExist子句中,我将创建一个新的模型实例。
  • 否则,我想更新预先存在的实例中的字段,而不创建新的实例。

最初我试图在找到的实例上调用.update(),但是.update()似乎只能在QuerySets上调用。如何避免更改一打字段,而不使用.filter()并比较长度以知道是否必须创建或更新预先存在的实例?


首先,您可以使用get_or_create,然后您可以展示一些代码... - Jingo
8个回答

117

随着Django 1.7的到来,现在有一个新的update_or_create QuerySet方法,它应该可以完全满足你的需求。只是要注意,如果不在数据库级别上强制执行唯一性,则可能会存在潜在的竞态条件。

文档中的示例:

obj, created = Person.objects.update_or_create(
    first_name='John', last_name='Lennon',
    defaults={'first_name': 'Bob'},
)

update_or_create 方法基于给定的 kwargs 尝试从数据库获取一个对象。如果找到匹配项,则更新 defaults 字典中传递的字段。


Django 1.7 之前:

根据需要更改模型字段值,然后调用 .save() 以持久化更改:

try:
    obj = Model.objects.get(field=value)
    obj.field = new_value
    obj.save()
except Model.DoesNotExist:
    obj = Model.objects.create(field=new_value)
# do something else with obj if need be

5
假设我有20个字段,是否有更简单的方法来更新它们?使用 .update(),我可以传递 **kwargs 参数吗? - zhuyxn
尝试在这里查看:https://dev59.com/m3I-5IYBdhLWcg3w-96b - Platinum Azure
9
这种方法存在原子性问题——如果两个应用实例都更改了数据库中的同一实例,可能会互相覆盖。 - Nils
1
如果您只是想更新对象,请使用带有相应pk属性的过滤器,这样会更好。这样做的优点是只发出一个查询。如果您使用get进行更新,则会使用两个查询。同时请注意,此方法仅适用于与对象一起工作的情况。 - Chris
1
@zhuyxn 对于kwargs中的每个属性和值,使用setattr函数将其设置到obj对象中。 - DylanYoung
.update_or_create() 在底层也使用了 .save(),因此如果对象已存在,则最终效果相同,这可能是您想要避免的。 - slajma

65

如果你只想在模型已存在的情况下更新它(而不创建新模型):

Model.objects.filter(id = 223).update(field1 = 2)

MySQL 查询:

UPDATE `model` SET `field1` = 2 WHERE `model`.`id` = 223

8
他特别要求使用 .get() 方法进行查询,而不是 .filter() 方法。 - brainLoop
如果你们想在执行数据库查询之前检查模型字段,也可以使用.first(),因为.filter()应该使用唯一的ID作为参数。 - Dwi Mulia Mokoginta
我收到了object has no attribute 'update'的错误信息。 - AlxVallejo

47

从Django 1.5开始,模型保存时有一个update_fields属性。例如:

obj.save(update_fields=['field1', 'field2', ...])

https://docs.djangoproject.com/en/dev/ref/models/instances/

我喜欢这种方法,因为如果您有多个Web应用程序实例更改模型实例的不同部分,则不会创建原子性问题。


1
updated_fields是一个列表而不是字典,那么我们如何传递字段的更新值呢? - brainLoop
4
@brainLoop,您不需要单独传递这些值。您应该在对象本身上设置它们。在调用对象的保存方法之前,使用 obj.field1=value 进行赋值。 - codescribblr
1
update_fields 参数通过强制更新指定字段的模型来防止竞争条件。这样我们就避免了将模型加载到内存中再保存的操作,这意味着即使存在另一个应用程序实例,它也无法在其中间进行任何操作。 - dimyG

29

我不知道这个方法好坏如何,但你可以尝试这样做:

try:
    obj = Model.objects.get(id=some_id)
except Model.DoesNotExist:
    obj = Model.objects.create()
obj.__dict__.update(your_fields_dict) 
obj.save()

2
异常是繁重的操作,不建议使用。 - digitaldavenyc
6
异常是合理且正确的。 注意:此答案中的create()应为create(id=some_id),并且需要有自己的try/except块来处理重复键和/或竞态条件,应在重试机制中运行等等。 - Kevin J. Rice
3
异常是Python的一种方式,它们并不繁重,而且被推荐使用。 - Dustin Wyatt
这个答案就是我想要的。 - bobo pei

12

这是一个 mixin,您可以将其混合到任何模型类中,从而使每个实例都拥有一个 update 方法:

class UpdateMixin(object):
    def update(self, **kwargs):
        if self._state.adding:
            raise self.DoesNotExist
        for field, value in kwargs.items():
            setattr(self, field, value)
        self.save(update_fields=kwargs.keys())

self._state.adding 检查是否将模型保存到数据库中,如果没有,则引发错误。

(注意:此update方法是用于更新已知实例已保存到数据库中的模型时使用,直接回答原始问题。在Platinum Azure的回答中特色的内置update_or_create方法已经涵盖了其他用例。)

您可以像这样使用它(在将其混合到用户模型后):

user = request.user
user.update(favorite_food="ramen")

除了具有更好的 API 之外,这种方法的另一个优点是它调用了 pre_savepost_save 钩子,同时仍然避免了如果另一个进程正在更新相同模型时的原子性问题。


5

正如@Nils所提到的,您可以使用save()方法的update_fields关键字参数来手动指定要更新的字段。

obj_instance = Model.objects.get(field=value)
obj_instance.field = new_value
obj_instance.field2 = new_value2

obj_instance.save(update_fields=['field', 'field2'])
update_fields 值应该是一个字符串列表,表示要更新的字段。
请参见 https://docs.djangoproject.com/en/2.1/ref/models/instances/#specifying-which-fields-to-save

1
更新:
1 - 单个实例: 使用 get() 方法检索单个对象并手动更新。
post = Post.objects.get(id=1)
post.title = "update title"
post.save()

2 - 实例集合: 使用update()方法,该方法仅适用于由filter()方法返回的查询集。

Post.objects.filter(author='ahmed').update(title='updated title for ahmed')

0

我在这种情况下使用以下代码:

obj, created = Model.objects.get_or_create(id=some_id)

if not created:
   resp= "It was created"
else:
   resp= "OK"
   obj.save()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接