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

时间:2019-05-02 13:52:42

标签: django django-models

我有一个模特

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代码。

2 个答案:

答案 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')。这就是可能导致问题的原因。尝试使用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生成的模型使用代码。

答案 1 :(得分:0)

就我而言,我在班级顶部有一个唯一的CharField。在我看来,Django有时会将其视为主键,有时却不将其作为主键,其结果是有时不意识到,应该更新而不是插入。

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