仅更新models.Model中特定的字段

144

我有一个模型

class Survey(models.Model):
    created_by = models.ForeignKey(User)
    question = models.CharField(max_length=150)
    active = models.NullBooleanField()
    def __unicode__(self):
        return self.question

现在我只想更新active字段。因此我这样做:

survey = get_object_or_404(Survey, created_by=request.user, pk=question_id)
survey.active = True
survey.save(["active"]) 

现在我遇到一个错误:IntegrityError: PRIMARY KEY must be unique

我用这个方法更新是对的吗?

3个回答

275

要更新一部分字段,您可以使用update_fields

survey.save(update_fields=["active"]) 

update_fields 参数是在 Django 1.5 中添加的。在早期版本中,您可以使用 update() 方法来代替:

Survey.objects.filter(pk=survey.pk).update(active=True)

4
虽然这个回答让.update()看起来已经被弃用,但是截至Django 4.0,这两种更新方法仍然可用且未被弃用。非常好的回答,因为提到了两种选项 :-) - ingofreyer
2
这不会更新auto_now字段 modified_at = models.DateTimeField(auto_now=True) 必须像下面这样指定 survey.save(update_fields=["active", "modified_at"]) - Mohamed Jaleel Nazir
这不会更新auto_now字段 modified_at = models.DateTimeField(auto_now=True) 必须像下面这样指定 survey.save(update_fields=["active", "modified_at"]) - Mohamed Jaleel Nazir

21

通常,更新一个或多个模型实例的特定字段的正确方式是在相应的查询集上使用update()方法。然后你可以像这样做:

affected_surveys = Survey.objects.filter(
    # restrict your queryset by whatever fits you
    # ...
    ).update(active=True)

通过这种方式,您不再需要在模型上调用save(),因为它会自动保存。此外,update()方法返回受更新影响的调查实例数量。


2
谢谢。我尝试使用.get而不是.filter,但这样并不起作用。但是使用filter的话可以正常工作。 你知道上述代码有什么问题吗? - Registered User
你的问题可能与 question_id 相关。这个值来自哪里?而且是哪一行引发了 IntegrityError - pemistahl
question_id 来自于 (?P<question_id>\d+) 的 url。我的错误在于工作服务器安装的是 Django 1.4 而我的代码是 1.5 版本。但是使用了你的代码后它可以正常工作。 - Registered User
5
@RegisteredUser,看起来对象上没有“更新(update)”方法,只有查询集(queryset)有。当你使用.filter()时,会返回一个查询集(可能包含零个或多个对象)。而当你使用.get()时,会返回单个对象。 - mgojohn
默认情况下,调用 save()(@Alasdair 解决方案)是更安全的解决方案,因为该方法可能会触发诸如验证或任何自定义代码之类的事情,而 update() 则不会。 - David Dahan
这不会更新auto_now字段 modified_at = models.DateTimeField(auto_now=True) 必须像下面这样指定 survey.save(update_fields=["active", "modified_at"]) - Mohamed Jaleel Nazir

0
您可以使用这个 mixin,它可以用于检测模型实例中哪些字段已经发生了变化,并将这些信息传递给保存方法。以下是该 mixin 的描述:
  • init 方法中,mixin 初始化一个名为 old_instance_data 的字典,其中存储了模型实例中字段的初始值。
  • 在保存方法中,mixin 将当前字段的值与存储在 old_instance_data 中的初始值进行比较,以确定哪些字段已经发生了变化。它创建一个包含已更改字段名称的 update_fields 集合。
  • 如果模型实例具有主键(self.pk 不为 None),且未显式提供 update_fields,则 mixin 会自动确定已更改的字段。
  • mixin 然后调用父类的保存方法,并将 update_fields 和可能提供的其他关键字参数一起传递给它。
  • 如果在保存时传递自己的 update_fields 参数,它将使用该参数。因此,您可以在代码的任何地方部分使用其他 update_fields。
通过在模型中使用这个 mixin,您可以方便地跟踪和保存仅已修改的字段,这对于优化数据库更新和减少不必要的数据库查询非常有帮助。
from django.db import models


class UpdateFieldsMixin(models.Model):
    """ Detects which field changed and passes it to save method """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        fields = (field.name for field in self._meta.fields)
        self.old_instance_data = {name: getattr(self, name, None)
                                  for name in fields}

    def save(self, update_fields=None, **kwargs):
        if self.pk:
            update_fields = update_fields or {
                k for k, v in self.old_instance_data.items()
                if getattr(self, k, None) != v}
        return super().save(update_fields=update_fields, **kwargs)

    class Meta:
        abstract = True

使用方法如下:

class YourModel(UpdateFieldsMixin, models.Model):
    # Any code

    def save(**kwargs):
        # Any code
        return super().save(**kwargs)

那就这样了。如果你有自定义的保存方法,请确保调用UpdateFieldsMixin.save。我建议像上面的例子一样使用super()。

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