django.db.utils.IntegrityError:重复的键值违反了唯一约束" spirit_category_category_pkey"

时间:2016-01-09 15:27:09

标签: python django postgresql

错误

我正在使用django和spirit建立一个网站。在测试中,当我将新数据插入到名为spirit_category_category的表中时,我收到以下错误:

django.db.utils.IntegrityError: duplicate key value violates unique constraint "spirit_category_category_pkey"
DETAIL:  Key (id)=(1) already exists.

请注意,表格中已有两条其他记录包含12的ID。因此,插入Key(id)=(1)当然不会起作用。但是执行的sql并没有包含id字段。也就是说,Key (id)=(1)是由postgresql自动生成的,为什么它会生成一个已经存在的id?

原因

为了找出原因,我在postgresql中运行了以下命令:

test_spiritdb=# select start_value, last_value, max_value from spirit_category_category_id_seq;
 start_value | last_value |      max_value      
-------------+------------+---------------------
           1 |          1 | 9223372036854775807
(1 row)

基本上,last_value1,所以postgresql每次都会生成Key (id)=(1),我试着把它改成3,一切都很好。

test_spiritdb=# alter sequence spirit_category_category_id_seq restart with 3;

我不知道如何修复测试

测试通过了。但这是一个测试,因此更改测试表是没有意义的,因为测试数据库将被删除并再次为每个测试创建,因此下次测试将再次失败,因为last_value仍将生成{ {1}}。所以我想知道为什么django / postgresql会为1生成这样一个异常值?怎么解决? last_value的模型和迁移如果有帮助,如下所示。

models.py

category

0001_initial.py

# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.conf import settings

from .managers import CategoryQuerySet
from ..core.utils.models import AutoSlugField

class Category(models.Model):

    parent = models.ForeignKey('self', verbose_name=_("category parent"), null=True, blank=True)

    title = models.CharField(_("title"), max_length=75)
    slug = AutoSlugField(populate_from="title", db_index=False, blank=True)
    description = models.CharField(_("description"), max_length=255, blank=True)
    is_global = models.BooleanField(_("global"), default=True,
                                    help_text=_('Designates whether the topics will be'
                                                'displayed in the all-categories list.'))
    is_closed = models.BooleanField(_("closed"), default=False)
    is_removed = models.BooleanField(_("removed"), default=False)
    is_private = models.BooleanField(_("private"), default=False)

    # topic_count = models.PositiveIntegerField(_("topic count"), default=0)

    objects = CategoryQuerySet.as_manager()

    class Meta:
        ordering = ['title', 'pk']
        verbose_name = _("category")
        verbose_name_plural = _("categories")

    def get_absolute_url(self):
        if self.pk == settings.ST_TOPIC_PRIVATE_CATEGORY_PK:
            return reverse('spirit:topic:private:index')
        else:
            return reverse('spirit:category:detail', kwargs={'pk': str(self.id), 'slug': self.slug})

    @property
    def is_subcategory(self):
        if self.parent_id:
            return True
        else:
            return False

0002_auto_20150728_0442.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import spirit.core.utils.models


class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Category',
            fields=[
                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=True, auto_created=True)),
                ('title', models.CharField(verbose_name='title', max_length=75)),
                ('slug', spirit.core.utils.models.AutoSlugField(db_index=False, populate_from='title', blank=True)),
                ('description', models.CharField(verbose_name='description', max_length=255, blank=True)),
                ('is_closed', models.BooleanField(verbose_name='closed', default=False)),
                ('is_removed', models.BooleanField(verbose_name='removed', default=False)),
                ('is_private', models.BooleanField(verbose_name='private', default=False)),
                ('parent', models.ForeignKey(null=True, verbose_name='category parent', to='spirit_category.Category', blank=True)),
            ],
            options={
                'ordering': ['title', 'pk'],
                'verbose_name': 'category',
                'verbose_name_plural': 'categories',
            },
        ),
    ]

0003_category_is_global.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
from django.conf import settings


def default_categories(apps, schema_editor):
    Category = apps.get_model("spirit_category", "Category")

    if not Category.objects.filter(pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK).exists():
        Category.objects.create(
            pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK,
            title="Private",
            slug="private",
            is_private=True
        )

    if not Category.objects.filter(pk=settings.ST_UNCATEGORIZED_CATEGORY_PK).exists():
        Category.objects.create(
            pk=settings.ST_UNCATEGORIZED_CATEGORY_PK,
            title="Uncategorized",
            slug="uncategorized"
        )


class Migration(migrations.Migration):

    dependencies = [
        ('spirit_category', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(default_categories),
    ]

4 个答案:

答案 0 :(得分:1)

经过大量调试后,我终于找到了解决方案。原因是我尝试使用指定的 id s插入另外两个categories,这会导致postgresql停止增加相对last_value的{​​{1}}。如下:

0002_auto_20150728_0442.py

sequence

解决此问题的方法很简单,可以在if not Category.objects.filter(pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK).exists(): Category.objects.create( pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK, title="Private", slug="private", is_private=True ) if not Category.objects.filter(pk=settings.ST_UNCATEGORIZED_CATEGORY_PK).exists(): Category.objects.create( pk=settings.ST_UNCATEGORIZED_CATEGORY_PK, title="Uncategorized", slug="uncategorized" ) 中手动更改last_value,也可以不指定ID,即删除以下行:

django

我想如果你让django承担管理.... pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK, .... pk=settings.ST_UNCATEGORIZED_CATEGORY_PK, .... 的任务,那么在插入新数据时自己指定id可能不是一个好主意。

答案 1 :(得分:0)

我认为问题不在于您的迁移。

您正在尝试添加多个相同的spirit_category_category对象,如果未在Django测试套件中正确配置,则会触发相同的自动递增id。一种选择是识别冲突的测试并将它们移动到单独的TestCase类中(因为setUp将为您刷新数据库)。

另一个选择是使用像Factory Boy这样的库来创建模型实例,这有助于避免这样的冲突。

答案 2 :(得分:0)

我有同样的问题,但我需要保留ID,因为我正在从另一台服务器恢复数据并保持关系等。 我的解决方案是在迁移文件上添加另一个命令,以便在插入项目并重置所涉及的表的数据库序列后运行。

要获取重置表序列的命令,您可以按照https://docs.djangoproject.com/en/1.9/ref/django-admin/#sqlsequencereset

中的说明运行python manage.py sqlsequencereset spirit

然后在您的迁移0002_auto_20150728_0442.py文件中添加:

from django.db connection

def reset_spirit_pk_sequence(apps, schema_editor):
    with connection.cursor() as cursor:
        cursor.execute("RESULT_FROM_SQLRESETSEQUENCE")
    row = cursor.fetchone()

...
...

operations = [
    migrations.RunPython(default_categories),
    migrations.RunPython(reset_spirit_pk_sequence),
]

请注意,将RESULT_FROM_SQLRESETSEQUENCE替换为与您遇到问题的表相关的manage.py sqlresetsequence命令行(使用"转义内部\)。< / p>

答案 3 :(得分:0)

在一个测试中,我的代码试图保存一行而不传递id(主键),但是我得到了:

django.db.utils.IntegrityError: duplicate key value violates unique constraint ...
DETAIL:  Key (id)=(1) already exists. 

我按照以下方法解决了这个问题:

iOneMore = Model.objects.last().id + 1
oNew = Model( id = iOneMore, col1 = string1, col2 = string2 )
oNew.save()

问题已解决,没有更多错误。