django.db.utils.ProgrammingError: 关系已经存在

124

我正在为一个新的Django项目设置表格(即,这些表格在数据库中还不存在);Django版本为1.7,数据库后端为PostgreSQL。项目名称为crud。迁移尝试的结果如下:

python manage.py makemigrations crud


我正在尝试为一个新的 Django 项目设置数据表(也就是说,这些表在数据库中还不存在);该项目使用的是 Django 版本 1.7,数据库后端是 PostgreSQL。项目名称为 crud。执行迁移命令的结果如下:

python manage.py makemigrations crud

Migrations for 'crud':
  0001_initial.py:
    - Create model AddressPoint
    - Create model CrudPermission
    - Create model CrudUser
    - Create model LDAPGroup
    - Create model LogEntry
    - Add field ldap_groups to cruduser
    - Alter unique_together for crudpermission (1 constraint(s))

python manage.py migrate crud

Operations to perform:
  Apply all migrations: crud
Running migrations:
  Applying crud.0001_initial...Traceback (most recent call last):
  File "manage.py", line 18, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python2.7/dist-packages/django/core/management/commands/migrate.py", line 161, in handle
    executor.migrate(targets, plan, fake=options.get("fake", False))
  File "/usr/local/lib/python2.7/dist-packages/django/db/migrations/executor.py", line 68, in migrate
    self.apply_migration(migration, fake=fake)
  File "/usr/local/lib/python2.7/dist-packages/django/db/migrations/executor.py", line 102, in apply_migration
    migration.apply(project_state, schema_editor)
  File "/usr/local/lib/python2.7/dist-packages/django/db/migrations/migration.py", line 108, in apply
    operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
  File "/usr/local/lib/python2.7/dist-packages/django/db/migrations/operations/models.py", line 36, in database_forwards
    schema_editor.create_model(model)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/schema.py", line 262, in create_model
    self.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/schema.py", line 103, in execute
    cursor.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 82, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 66, in execute
    return self.cursor.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 66, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: relation "crud_crudpermission" already exists

迁移文件的一些亮点:

dependencies = [
    ('auth', '0001_initial'),
    ('contenttypes', '0001_initial'),
]
    migrations.CreateModel(
        name='CrudPermission',
        fields=[
            ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ('_created_by', models.CharField(default=b'', max_length=64, null=True, editable=False, blank=True)),
            ('_last_updated_by', models.CharField(default=b'', max_length=64, null=True, editable=False, blank=True)),
            ('_created', models.DateTimeField(null=True, editable=False, blank=True)),
            ('_last_updated', models.DateTimeField(null=True, editable=False, blank=True)),
            ('domain', models.CharField(max_length=32, choices=[(b'town', b'Town'), (b'boe', b'BOE'), (b'police', b'Police')])),
            ('ldap_group', models.CharField(max_length=128, verbose_name=b'LDAP group')),
            ('can_add', models.BooleanField(default=False, verbose_name=b'add')),
            ('can_change', models.BooleanField(default=False, verbose_name=b'change')),
            ('restrict_change_to_own', models.BooleanField(default=False)),
            ('can_delete', models.BooleanField(default=False, verbose_name=b'delete')),
            ('restrict_delete_to_own', models.BooleanField(default=False)),
            ('models', models.ManyToManyField(to='contenttypes.ContentType', null=True, blank=True)),
        ],
        options={
            'verbose_name': 'CRUD permission',
        },
        bases=(models.Model,),
    ),
    migrations.AlterUniqueTogether(
        name='crudpermission',
        unique_together=set([('ldap_group', 'can_add', 'can_change', 'can_delete', 'domain')]),
    )

这个 CRUD 应用程序本身没有实际用途,但我在另一个应用程序中使用它,所以当我尝试从该应用程序迁移时,会触发上述问题。

我在网上找到了其他类似问题的例子,但他们的情况似乎都不适用于我的情况,因为:

  1. 这个问题影响整个关系,而不仅仅是一个列。
  2. 我没有使用多重继承。

接下来我应该在哪里查找潜在问题?


8
你是否运行了 syncdb 命令?如果是的话,这已经在数据库中创建了表格,所以这个迁移正在尝试重新创建。要跳过它,请运行 python manage.py migrate --fake 命令。 - Timmy O'Mahony
5
syncdb仅是迁移操作加上如无管理员则提示创建管理员。如果已运行该命令,则迁移不会尝试重新应用该迁移。 - shangxiao
20个回答

119

12
我常常忘记什么是 "default",所以我想提一下这是 Django 设置中 DB 配置文件的名称 - https://docs.djangoproject.com/en/1.9/ref/settings/#databases - TitanFighter
34
在使用假选项时请务必小心。它只是通过使存储在数据库中的迁移与本地文件夹匹配来消除问题,但在进行下一次迁移时,情况会变得非常糟糕,因此我建议不要使用“fake”,除非你真正理解正在发生的事情。 - max
@max - 你说的“事情会变得非常糟糕”是什么意思?当我遇到相同的编程错误时,我已经使用了这个虚假选项几次了。直到现在,我都没有遇到过问题。但现在我无法迁移,因为我的应用程序中已经存在一个表,在我进行makemigrations之后,突然--fake不能解决这个问题。 - Xeberdee
3
@Xeberdee,使用“fake”选项将使Django认为您已经执行了迁移。如果有任何未迁移的内容,哪怕是一点点小的东西,由于您使用了“fake”选项,它也不会进行迁移。只有在我确定唯一的更改是Django所抱怨的表格,并且我进行了健全性检查以确保该表格实际上存在并具有相同的名称时,我才会使用“fake”选项。我的意思是,如果您有2个更改,其中一个是创建已经存在的表格,则如果您使用“fake”,则第二个更改将无法生效。 - max
2
Showmigrations 命令会告诉你数据库的迁移状态,而 migrations 文件夹中的文件应该与其同名。这两者应该是匹配的,对吧?你是说数据库实际上可能处于不同的状态,而它认为自己处于另一种状态?这就是 fake 命令可以实现的吗?文档中没有太多相关信息。链接 - Xeberdee
显示剩余5条评论

80

链接已经失效。 - theSekyi
请尝试使用此链接:https://docs.djangoproject.com/en/3.1/ref/django-admin/#django-admin-migrate - Farid Chowdhury

16

当我向现有模型添加了一些新字段时,我遇到了类似的问题。我使用的是Django 1.9,它introduced--run-syncdb选项。运行manage.py migrate --run-syncdb可以修复我的表格。


这对我的情况有所帮助,因为需要为我自定义定义的AUTH_USER_MODEL创建一个新表,所以选项--run-syncdb实现了期望的结果。 - Iulian Pinzaru
1
运行 python manage.py migrate --fake,然后运行 manage.py migrate --run-syncdb 对我起了作用。 - Zack Turner
文档对 --run-syncdb 的解释如下:因此请谨慎操作... - StephenGodderidge

11

现在(我使用的是Django 1.9)你可以做到:

./manage.py migrate [--database DATABASE] --fake [app_label] [migration_name]

这样您就可以更准确地解决问题,只能在特定的数据库上模拟出有问题的迁移。

因此,针对这个问题,您可以:

./manage.py migrate --database default --fake crud crud.0001_initial

您缺少manage.py的实际命令。应该是:./manage.py migrate --database default --fake crud crud.0001_initial - slackmart
我遇到了一些git合并冲突的问题,导致一个迁移必须使用不同的文件名重新应用。而我只需运行这个命令 python manage.py migrate --database default --fake <appname> 就解决了这个问题。 - Ragav Y

8
在我的情况下,一个迁移文件被删除并自动生成了一个新的文件,但这个新文件的名称不同。由于名称不同,Django尝试应用新的迁移文件,这与先前应用过的相同,而现在已经删除了。两者都需要创建一个新的模型,导致出现django.db.utils.ProgrammingError: relation "app_space" already exists错误。我尝试回滚迁移,但缺失的迁移文件阻止了Django实际回滚它。教训是:应该将迁移文件提交到git。
以下是一些帮助我找到问题根源的步骤:--fake可以暂时解决,但是在下一次迁移中会遇到相同的问题。除非您确信这是正确使用案例,否则不建议使用--fake
这篇答案对我非常关键。
  1. 检查以前的迁移 python manage.py showmigrations

  2. 检查Django数据库中应用的迁移 select * from django_migrations;(使用psql访问postgres数据库控制台:psql -h 0.0.0.0 -U <your-db-user>,然后使用目标数据库\c <your-db-name>)。

  3. 我看到了一个已应用的迁移,但它不再在我的迁移文件夹中。

 20 | app             | 0001_initial                             | 2021-03-05 07:40:20.14602+00
 21 | app             | 0002_some_auto_name                      | 07:40:20.269775+00
 22 | app             | 0003_auto_20210318_2350 <---here         | 2021-03-18 23:50:09.064971+00

在我的迁移文件夹中有一个名为0003_auto_20210318_2355的文件,它在5分钟后生成了相同的文件。我将迁移文件重命名为上述名称以便执行反向操作。
要执行反向操作,需要通过传递想要返回的迁移来反向迁移。 python manage.py migrate <app-name> <latest-migration-to-which-to-return> 例如:python manage.py migrate app 0002_some_auto_name 接下来应该做正确的事情,并将迁移提交到git。然后你就可以执行makemigrationsmigrate操作,享受更加宁静的生活。

8

我曾经遇到过类似的问题,最终删除了migration文件夹中的所有.py文件(django1.7会自动创建该文件夹),之后一切正常。


4
这个有任何副作用吗? - Vardan
4
在生产环境迁移时可能会出现问题。 尝试在初始迁移或开发中仅使用此方法或仅转储数据库。 - Arjunsingh

6

我曾经遇到过类似的问题,当时我更改了列名。我遇到的错误与提问中给出的堆栈跟踪相同。

这是我所做的。

首先我运行了虚拟迁移。然后我从django_migrations表中删除了要运行的迁移的条目,并再次运行了迁移(这次没有虚拟迁移)。

对我来说,更改如预期一样出现了。

希望这有所帮助。


1
这对我来说毫无意义,但它确实起作用了。我在一个列上添加了null=True(django==1.11)。有人能解释一下为什么它起作用吗? - thetanuj
在Django==2.2中,这对我有效,涉及更改列名。 - KenBuckley

6

对于我来说,当我遇到这个异常时,我使用Django dbshell工具或任何类型的MY_DATABASE查看器/ 交互式命令行 来解决它。

DBShell:

  1. python manage.py dbshell
  2. ALTER TABLE [已经存在的字段名称] DROP column [表中字段];

5
Django提供了一个--fake-initial选项,我发现它对我的使用非常有效。从Django迁移文档中可以看到:

--fake-initial

如果所有CreateModel操作创建的所有模型的名称在该迁移中所有数据库表中已经存在,则允许Django跳过应用程序的初始迁移。此选项旨在用于针对已存在迁移之前存在的数据库运行迁移的情况。但是,此选项不检查匹配的数据库模式除了匹配表名称外,因此仅当您确信现有模式与记录在初始迁移中的模式匹配时才可以安全使用此选项。

对于我的用途,我刚刚从版本控制中拉出了一个项目,并准备添加一些新的模型字段。我添加了这些字段,运行了./manage.py makemigrations,然后尝试运行./manage.py migrate,但是由于许多字段已经存在于现有的数据库中,所以产生了错误。
我应该立即在从版本控制中拉出项目后运行makemigrations来创建现有模型状态的快照。然后,运行./manage.py migrate --fake-initial将是下一步。
之后,您可以像往常一样添加并运行makemigrations> migrate
注意:我不知道--fake-initial是否会跳过现有字段并添加新字段。我选择注释掉到那个时候为止已创建的新字段,将--fake-initial视为从版本控制中提取后的第一件事运行,然后在下一个迁移中添加更新后的字段。
其他相关文档:https://docs.djangoproject.com/en/dev/topics/migrations/#initial-migrations

4

我已经处理这个问题好几年了。 可能会有不同的情况:

情况1: 与原帖相同,您没有起始表。 在这种情况下,我会

  1. 在models.py中注释掉关系
  2. 运行python manage.py migrate,假设现在可以成功迁移
  3. 取消注释,运行python manage.py migrate --fake取消步骤1的注释

情况2: 多个应用程序: 一种可能性是您可能有不同的应用程序,并且一个应用程序的数据模型正在使用另一个应用程序的某些表格。在这种情况下,如果数据模型被正确地设计,您应该能够仅为一个应用程序创建表(通过在setting.py中仅指定一个应用程序),然后添加其他应用程序并进行迁移。如果它没有得到精心设计,并且存在递归依赖关系,则我建议更改设计,而不是进行临时修复。

情况3: 您拥有一些表格,但是迁移出了问题,那么我会

  1. 将models.py还原为原来的状态,仅引入似乎已经存在于models.py中的新关系。
  2. 删除迁移文件夹
  3. 运行python manage.py makemigrations
  4. 如果有任何新更改,请继续使用makemigrations和migrate命令进行介绍,并继续进行。

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