【问题标题】:loaddata raises IntegrityError when called within migration, but works if called from the CLIloaddata 在迁移中调用时会引发 IntegrityError,但如果从 CLI 调用则有效
【发布时间】:2016-09-23 17:04:24
【问题描述】:

对于我的 Django 1.8.12 站点,我有一个 example_content 固定装置,用于填充一些初始站点内容。

如果我在运行migrate 后立即加载它,它会很好地导入:

$ ./manage.py migrate --noinput
Operations to perform:
  Synchronize unmigrated apps: ckeditor, staticfiles, zinnia_ckeditor, messages, djangocms_admin_style, webapp_creator, template_debug, sekizai, django_pygments, treebeard
  Apply all migrations: djangocms_inherit, djangocms_snippet, api_docs, cmsplugin_zinnia, sites, menus, contenttypes, store_data, zinnia, django_comments, sessions, rev
ersion, auth, djangocms_picture, tagging, developer_portal, admin, djangocms_link, djangocms_video, django_openid_auth, md_importer, cms, djangocms_text_ckeditor
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  ... (skipped for brevity).
  Applying django_comments.0003_add_submit_date_index... OK
$ ./manage.py loaddata example_content
Installed 139582 object(s) from 1 fixture(s)
$

但是,我希望这些数据在 migrate 上自动导入,而不需要额外的手动 loaddata 步骤,因此我正在尝试创建迁移 my_app.0002_example_content,以调用 loaddata命令。

我通过添加对每个相关模块的依赖项来确保首先加载所有其他数据。

为了确认这次迁移确实是最后一次运行(就像上面成功时一样),我首先注释掉了 RunPython 步骤,使迁移为空:

# my_app/migrations/0002_example_content.py

from django.core.management import call_command
from django.db import models, migrations

class Migration(migrations.Migration):
    from django.core.management import call_command
    from django.db import models, migrations

    dependencies = [
        ('djangocms_inherit', '0002_auto_20150622_1244'),
        ... all other apps (skipped for brevity)
        ('my_app', '0001_initial'),
    ]
    operations = [
#        migrations.RunPython(
#            lambda apps, schema_editor: call_command("loaddata", "example_content")
#        )
    ]

果然它作为最后一次迁移运行:

$ ./manage.py migrate --noinput
Operations to perform:
  Synchronize unmigrated apps: ckeditor, staticfiles, zinnia_ckeditor, messages, djangocms_admin_style, webapp_creator, template_debug, sekizai, django_pygments, treebeard
  Apply all migrations: djangocms_inherit, djangocms_snippet, api_docs, cmsplugin_zinnia, sites, menus, contenttypes, store_data, zinnia, django_comments, sessions, reversion, auth, djangocms_picture, tagging, developer_portal, admin, djangocms_link, djangocms_video, django_openid_auth, md_importer, cms, djangocms_text_ckeditor
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  ... (skipped for brevity)
  Applying django_comments.0003_add_submit_date_index... OK
  Applying my_app.0001_initial... OK
  Applying my_app.0002_example_content... OK
$

但是,当我尝试运行它以实际运行 loaddata 命令(重新注释代码)时,出现错误:

$ ./manage.py migrate --noinput
Operations to perform:
  Synchronize unmigrated apps: ckeditor, staticfiles, zinnia_ckeditor, messages, djangocms_admin_style, webapp_creator, template_debug, sekizai, django_pygments, treebeard
  Apply all migrations: djangocms_inherit, djangocms_snippet, api_docs, cmsplugin_zinnia, sites, menus, contenttypes, store_data, zinnia, django_comments, sessions, reversion, auth, djangocms_picture, tagging, developer_portal, admin, djangocms_link, djangocms_video, django_openid_auth, md_importer, cms, djangocms_text_ckeditor
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  ... (skipped for brevity)
  Applying django_comments.0003_add_submit_date_index... OK
  Applying my_app.0001_initial... OK
  Applying my_app.0002_example_content...Traceback (most recent call last):
...
django.db.utils.IntegrityError: Problem installing fixtures: The row in table 'django_comments' with primary key '1' has an invalid foreign key: django_comments.content
_type_id contains a value '37' that does not have a corresponding value in django_content_type.id.

鉴于我的新迁移肯定是在其他所有操作之后运行,因此顺序应该与我手动调用 loaddata 时的顺序完全相同。所以我不明白为什么它在通过 CLI 调用时会成功,但在通过迁移调用时会失败。

migrate 步骤结束时是否发生了一些我遗漏的事情,这可能解释了这里的差异?

【问题讨论】:

    标签: django django-migrations django-fixtures django-manage.py loaddata


    【解决方案1】:

    Django 使用post_migrate 信号来创建缺失的内容类型,因此如果这是您第一次在当前数据库上迁移,则在迁移到加载数据运行之后,内容类型才会存在。

    您可以手动创建迁移中所需的内容类型。 Django 将检测现有的内容类型,并且不会创建任何重复项。

    请注意,内容类型的特定 id 可能因数据库而异。使用natural foreign keys 转储数据可能会更好。

    【讨论】:

    • 啊,就是这样!你知道是否有任何方法可以像post_migrate 那样手动创建所有内容类型?
    • Django 使用update_contenttypes() 创建它们。
    【解决方案2】:

    knbk's answer的提示下,我设法解决了这个问题。

    最初我尝试手动调用update_contenttypes,但这本身并不起作用。事实证明,我实际上需要调用所有 post_migrate 信号,因为我猜他们需要做更多的整理工作。

    这是我最终得到的my_app.0002_example_content

    # my_app/migrations/0002_example_content.py
    
    from django.core.management import call_command
    from django.db import migrations
    from django.apps import apps
    from django.db.models.signals import post_migrate
    
    
    def finish_previous_migrations(migrate_apps):
        """
        Explicitly run the post_migrate actions
        for all apps
        """
    
        for app_config in apps.get_app_configs():
            post_migrate.send(
                sender=app_config,
                app_config=app_config
            )
    
    
    def load_site_content(migrate_apps, schema_editor):
        """
        Load the website content fixture
        """
    
        finish_previous_migrations(migrate_apps)
    
        call_command("loaddata", "example_content")
    
    
    class Migration(migrations.Migration):
        dependencies = [
            ('djangocms_inherit', '0002_auto_20150622_1244'),
            ... all other apps (skipped for brevity)
            ('my_app', '0001_initial'),
        ]
        operations = [
            migrations.RunPython(load_site_content)
        ]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-10-12
      • 2022-07-22
      • 2014-10-06
      • 2015-05-23
      • 1970-01-01
      • 2021-08-13
      • 1970-01-01
      相关资源
      最近更新 更多