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。所以这就是可能导致问题的原因。尝试使用 UUIDField
或 CharField
代替 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 save request.POST to JSONField 从列表中选择最后一项而不是保存列表
尝试在 Django 中的表单上使用脆表单过滤器时收到“无效表单:脆”错误,但仅在一个 django 应用程序中而不是另一个?