我有一个模特
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尝试在其中插入花药行而不是进行更新的地方,从而导致数据库错误
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
如果没有更改(因此更新不执行任何操作),则它将尝试插入重复的记录。
我通过在get
之后添加保存来测试了这一点:
device = DeviceAdmin.objects.get(uuid=uuid)
device.save()
,它正在尝试插入副本。
这是否意味着我的代码需要跟踪是否必须将对象保存到DB?
解决此问题的最简单方法是什么?
更新:
作为测试,我在使用save
找到对象后立即放置了get
,没有任何更改:
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代码。
答案 0 :(得分:0)
您当然对字段定义存在一些问题(也许出于某些原因,您应该添加unique=True
)。但是,如果仍然无法确定为什么更新失败(如果INSERT
返回“没有更新的行”,则会发生UPDATE
,那么您可以使用.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(例如str(int(uuid.UUID('579aea21-49a4-4819-90ec-a192014b4a44'))) == '116447198070669671892400511866020514372'
)。这就是可能导致问题的原因。尝试使用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
生成的模型使用代码。
答案 1 :(得分:0)
就我而言,我在班级顶部有一个唯一的CharField。在我看来,Django有时会将其视为主键,有时却不将其作为主键,其结果是有时不意识到,应该更新而不是插入。
添加一个明确的id=AutoField(primary_key=True)
对我来说解决了这个问题。