我正在使用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.
请注意,表格中已有两条其他记录包含1
和2
的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_value
是1
,所以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
的模型和迁移如果有帮助,如下所示。
category
# -*- 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
# -*- 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',
},
),
]
# -*- 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),
]
答案 0 :(得分:1)
经过大量调试后,我终于找到了解决方案。原因是我尝试使用指定的 id s插入另外两个categories
,这会导致postgresql停止增加相对last_value
的{{1}}。如下:
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()
问题已解决,没有更多错误。