Django的update_or_create出现"重复键值违反唯一约束"错误提示。

25

也许我误解了Django的update_or_create模型方法的目的。

这是我的模型:

from django.db import models
import datetime
from vc.models import Cluster

class Vmt(models.Model):
    added = models.DateField(default=datetime.date.today, blank=True, null=True)
    creation_time = models.TextField(blank=True, null=True)
    current_pm_active = models.TextField(blank=True, null=True)     
    current_pm_total = models.TextField(blank=True, null=True)
    ... more simple fields ...
    cluster = models.ForeignKey(Cluster, null=True)


    class Meta:
        unique_together = (("cluster", "added"),)

这是我的测试:

from django.test import TestCase
from .models import *
from vc.models import Cluster
from django.db import transaction


# Create your tests here.
class VmtModelTests(TestCase):
    def test_insert_into_VmtModel(self):
        count = Vmt.objects.count()
        self.assertEqual(count, 0)

        # create a Cluster
        c = Cluster.objects.create(name='test-cluster')
        Vmt.objects.create(
            cluster=c,
            creation_time='test creaetion time',
            current_pm_active=5,
            current_pm_total=5,
            ... more simple fields ...
        )
        count = Vmt.objects.count()
        self.assertEqual(count, 1)
        self.assertEqual('5', c.vmt_set.all()[0].current_pm_active)

        # let's test that we cannot add that same record again
        try:
            with transaction.atomic():

                Vmt.objects.create(
                    cluster=c,
                    creation_time='test creaetion time',
                    current_pm_active=5,
                    current_pm_total=5,
                    ... more simple fields ...
                )
                self.fail(msg="Should violated integrity constraint!")
        except Exception as ex:
            template = "An exception of type {0} occurred. Arguments:\n{1!r}"
            message = template.format(type(ex).__name__, ex.args)
            self.assertEqual("An exception of type IntegrityError occurred.", message[:45])

        Vmt.objects.update_or_create(
            cluster=c,
            creation_time='test creaetion time',
            # notice we are updating current_pm_active to 6
            current_pm_active=6,
            current_pm_total=5,
            ... more simple fields ...
        )
        count = Vmt.objects.count()
        self.assertEqual(count, 1)

在最后一次update_or_create调用中,我遇到了这个错误:

IntegrityError: duplicate key value violates unique constraint "vmt_vmt_cluster_id_added_c2052322_uniq"
DETAIL:  Key (cluster_id, added)=(1, 2018-06-18) already exists.
为什么模型没有被更新?为什么Django尝试创建一个违反唯一约束条件的新记录?

1
update_or_create 包含筛选条件,在 defaults={..} 中指定您想要更新的字段。 - Willem Van Onsem
因此,对于我需要更新的每个字段,我都需要在“defaults”中指定该字段。 - Red Cricket
3个回答

64

update_or_create(defaults=None, **kwargs) 有基本上两个部分:

  1. **kwargs 指定用于确定是否已存在此对象的"筛选器"条件;以及
  2. defaults 是一个包含字段映射到值的字典,这些值应在我们创建新行(如果过滤无法找到行)时使用,或者应更新哪些值(如果我们发现这样的行)。

问题在于您使得筛选器太严格:您添加了多个筛选器,结果数据库找不到这样的行。那么会发生什么?数据库将针对这些筛选器值创建行(由于缺少defaults,因此不会添加额外的值)。但是随后发现我们创建了一行,并且clusteradded的组合已经存在。因此,数据库拒绝添加此行。

因此,这行代码:

Model.objects.update_or_create(field1=val1,
                               field2=val2,
                               defaults={
                                   'field3': val3,
                                   'field4': val4
                               })

等价于语义上的近似:

try:
    item = Model.objects.get(field1=val1, field2=val2)
except Model.DoesNotExist:
    Model.objects.create(field1=val1, field2=val2, field3=val3, field4=val4)
else:
    item = Model.objects.filter(
        field1=val1,
        field2=val2,
    ).update(
        field3 = val3
        field4 = val4
    )

(但原始调用通常在一个单一查询中完成)。

因此,您应该编写:

Vmt.objects.update_or_create(
    cluster=c,
    creation_time='test creaetion time',
    defaults = {        
        'current_pm_active': 6,
        'current_pm_total': 5,
    }
)

(或类似内容)


感谢您清晰的解释。我正在尝试在我的单元测试中复现生产环境中的情况。Vmt模型的数据来自我通过URL访问的CSV文件。如果这个CSV文件中有新的行,我想创建一个新的Vmt记录,但如果一行发生了变化,我想更新当天读取CSV文件的Vmt记录。 - Red Cricket
@RedCricket:嗯,也许creation_time是这里的“罪魁祸首”。个人认为将它们一起设为唯一有些问题,因为时间通常是不断增加的。这意味着有时会创建重复项,有时则不会。这相当“不稳定”。 - Willem Van Onsem
create_time只是一个字符串,不是数据库表上任何约束的一部分。 - Red Cricket
@RedCricket:啊,好的,那么这个应该可以工作。所以如果已经存在具有给定“cluster”和“creation_time”的“Vmt”,我们将更新该行,否则我们将创建一个新的。 - Willem Van Onsem
模型中的unique_together指定了clusteradded。我希望这能确保在给定的集群和给定的日期上,我只有一个Vmt数据集合。 - Red Cricket
显示剩余4条评论

7

您应该将字段分开:

  1. 需要搜索的字段
  2. 需要更新的字段

例如: 如果我有以下模型:

class User(models.Model):
    username = models.CharField(max_length=200)
    nickname = models.CharField(max_length=200)

我想搜索用户名为“Nikolas”的用户并将其昵称更新为“Nik”(如果没有名为“Nikolas”的用户,则需要创建),我应该编写以下代码:

User.objects.update_or_create(
    username='Nikolas', 
    defaults={'nickname': 'Nik'},
)

请参见https://docs.djangoproject.com/en/3.1/ref/models/querysets/


1
你的示例中的 defaults 应该是一个字典吧? - shacker

3

以上已经很好地回答了这个问题。

更明确地说,update_or_create()方法应该有**kwargs作为参数,用于检查您想要检查的那些数据是否已通过过滤器存在于数据库中。

从table_name中选择一些列,其中column1=''和column2='';

通过**kwargs进行过滤将给您对象。现在,如果您希望更新这些筛选对象的任何数据/列,则应将它们传递到update_or_create()方法中的defaults参数中。

所以假设您基于筛选器找到了一个对象,现在默认参数值预计会被选中并更新。

如果没有找到基于筛选器的匹配对象,则继续使用筛选器创建条目,并传递默认参数。


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