Django 尝试在 save() 上插入而不是更新

Posted

技术标签:

【中文标题】Django 尝试在 save() 上插入而不是更新【英文标题】:Django tries to INSERT instead of UPDATE on save() 【发布时间】:2019-09-21 01:37:39 【问题描述】:

我有模特

class DeviceAdmin(models.Model):
    dev_id = models.AutoField(db_column='dev_id', primary_key=True)
...

视图定位对象:

device = DeviceAdmin.objects.get(uuid=uuid)

然后它会进行一些更改(或者可能不会)

if (...):
    device.os_type = 'windows'
...

然后它会尝试保存任何更改:

device.save()

这里是 Django 尝试插入另一行而不是更新的地方,导致 DB 错误

django.db.utils.IntegrityError: duplicate key value violates unique constraint 

有一种类似question的解决方法:

将 primary_key 字段设置为 AutoField 后,问题就消失了。

但是,我的主键已经是 AutoField。

所以我用调试器逐步检查了 django 代码,并在文件 ...site-packages\django\models\base.py 中提供了资金:

    # If possible, try an UPDATE. If that doesn't update anything, do an INSERT.
    if pk_set and not force_insert:
        base_qs = cls._base_manager.using(using)
        values = [(f, None, (getattr(self, f.attname) if raw else f.pre_save(self, False)))
                  for f in non_pks]
        forced_update = update_fields or force_update
        updated = self._do_update(base_qs, using, pk_val, values, update_fields,
                                  forced_update)
        if force_update and not updated:
            raise DatabaseError("Forced update did not affect any rows.")
        if update_fields and not updated:
            raise DatabaseError("Save with update_fields did not affect any rows.")
    if not updated:
        if meta.order_with_respect_to:
            # If this is a model with an order_with_respect_to
            # autopopulate the _order field
            field = meta.order_with_respect_to
            filter_args = field.get_filter_kwargs_for_object(self)
            order_value = cls._base_manager.using(using).filter(**filter_args).count()
            self._order = order_value

        fields = meta.local_concrete_fields
        if not pk_set:
            fields = [f for f in fields if f is not meta.auto_field]

        update_pk = meta.auto_field and not pk_set
        result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
        if update_pk:
            setattr(self, meta.pk.attname, result)
    return updated

似乎如果没有更改(因此 update 没有做任何事情),那么它将尝试插入重复记录。

我在get 之后添加了一个保存来测试这个:

device = DeviceAdmin.objects.get(uuid=uuid)
device.save()

它正在尝试插入一个副本。

这是否意味着我的代码需要跟踪一个对象是否必须保存到数据库?

解决这个问题的最简单方法是什么?


更新:

作为测试,我在找到带有get 的对象后立即放了一个save,没有任何更改:

device = DeviceAdmin.objects.get(uuid=uuid)
device.save()

Django 应该知道该对象是现有对象。然而,它会尝试插入一个副本。

同样,创建一个新对象并调用save两次:

device = DeviceAdmin(...) # create a new object (it has null `dev_id`)
device.save()             # insert the new object into DB; get 'dev_id'
                          #   for the newly inserted record
device.save()             # This shouldn't do anything!

第二个save 尝试插入一个重复项 - 我可以看到我是否使用调试器逐步执行 django 代码。

【问题讨论】:

你的序列完全搞砸了吗? Possible duplicate 你试过这种方式吗(虽然我不确定它是否能解决):device.save(update_fields=['field']) 或者你可以使用device = DeviceAdmin.objects.filter(uuid=uuid).update(field=value) 序列很好。我查过了。 您的诊断不正确。 updated 标志只有在没有匹配的行时才会为 False,即 PK 不存在。如果 PK 存在但值设置为与现在相同,则不会为 False。 从你的另一个问题我知道你已经在数据库中有表。如果您使用manage.py inspectdb 为您的模型生成的代码,您能说明会发生什么吗?你可以随时回退到使用.save(force_update=True) 【参考方案1】:

您在字段定义方面肯定有一些问题(也许出于某种原因您应该添加unique=True)。但是,如果您仍然无法确定 UPDATE 失败的原因(如果UPDATE 返回“没有更新的行”,则会发生INSERT),那么您可以使用.save(force_update=True) 作为最后的手段。

你的代码也有一些不一致的地方:

字段被命名为dev_id,但您在过滤中使用uuid (.get(uuid=uuid))。您确定您已正确设置 primary_key 字段的名称吗? AutoField 假设使用了Integer 字段,所以在get_prep_value 期间,它会使用int(self.pk),它不会在uuid.UUID 的实例上抛出任何错误,但是如果内部db 使用varchar 字段,那么它将是用错误的值(例如str(int(uuid.UUID('579aea21-49a4-4819-90ec-a192014b4a44'))) == '116447198070669671892400511866020514372')转换回str。所以这就是可能导致问题的原因。尝试使用 UUIDFieldCharField 代替 AutoField:
import uuid


class SomeModel(models.Model): 

    dev_id = models.UUIDField(primary_key=True, blank=True, default=uuid.uuid4)

# Or if your column is a varchar:

class SomeModel(models.Model): 

    def dev_id_default():
        return uuid.uuid4().hex  # or str(uuid.uuid4()) if you need values with `-`

    dev_id = models.CharField(primary_key=True, blank=True, default=dev_id_default, max_length=33_OR_36)

看起来您正在使用 DB 中预先存在的表:尝试使用 python manage.py inspectdb 生成的模型的代码。

【讨论】:

对于.save(force_update=True),如果DB返回no change,是否还是会覆盖它,还是不会在幕后采取任何行动。【参考方案2】:

在我的例子中,我有一个独特的 CharField 在我班的顶部。在我看来,Django 有时会将其视为主键,有时则不会 - 结果是有时没有意识到它应该更新而不是插入。

添加一个明确的id=AutoField(primary_key=True) 为我解决了这个问题。

【讨论】:

以上是关于Django 尝试在 save() 上插入而不是更新的主要内容,如果未能解决你的问题,请参考以下文章

Django Admin:如何只保存内联模型而不是父模型

Django save request.POST to JSONField 从列表中选择最后一项而不是保存列表

django - 在保存之前比较新旧字段值

Django model.save() 不写入数据库

尝试在 Django 中的表单上使用脆表单过滤器时收到“无效表单:脆”错误,但仅在一个 django 应用程序中而不是另一个?

使用 django 循环插入数据到数据库