You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django(Wagtail CMS)迁移时IntegrityError约束检查失败求助

Wagtail CMS博客项目迁移与后台访问错误排查

基于Wagtail CMS官方教程搭建Django博客项目时,遇到两个核心问题:

  • 执行makemigrations和migrate命令时触发django.db.utils.IntegrityError: CHECK constraint failed: new__blog_blogpage错误
  • 访问后台管理页面时出现OperationalError,提示blog_blogindexpage.introduction列不存在

博客模型代码

from __future__ import unicode_literals

from django import forms
from django.db import models

from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey, ParentalManyToManyField

from wagtail.models import Page, Orderable
from wagtail.fields import RichTextField, StreamField
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel, MultipleChooserPanel
from wagtail.snippets.models import register_snippet

from base.blocks import BaseStreamBlock

# add this:
from wagtail.search import index

# keep the definition of BlogIndexPage model, and add the BlogPage model:

class BlogPersonRelationship(Orderable, models.Model):
    page = ParentalKey(
        "BlogPage", related_name="blog_person_relationship", on_delete=models.CASCADE
    )
    person = models.ForeignKey(
        "base.Person", related_name="person_blog_relationship", on_delete=models.CASCADE
    )
    panels = [FieldPanel("person")]


class BlogPage(Page):
    introduction = models.TextField(help_text="Text to describe the page.", blank=True)
    image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
        help_text="Landscape mode only; horizontal width between 1000px and 3000px.",
    )
    body = StreamField(
        BaseStreamBlock(), verbose_name="Page body", blank=True, use_json_field=True
    )
    subtitle = models.CharField(blank=True, max_length=255)
    # tags = ClusterTaggableManager(through=BlogPageTag, blank=True) CURRENTLY NOT USING TAGS
    date_published = models.DateField("Date article published", blank=True, null=True)

    content_panels = Page.content_panels + [
        FieldPanel("subtitle"),
        FieldPanel("introduction"),
        FieldPanel("image"),
        FieldPanel("body"),
        FieldPanel("date_published"),
        MultipleChooserPanel(
            "blog_person_relationship",
            chooser_field_name="person",
            heading="Authors",
            label="Author",
            panels=None,
            min_num=1,
        ),
        # FieldPanel("tags"),
    ]

    search_fields = Page.search_fields + [
            index.SearchField("body"),
    ]

    def authors(self):
        # only return authors that are not in draft
        return [
            n.person
            for n in self.blog_person_relationship.filter(
                person__live=True
            ).select_related("person")
        ]

    # Specifies parent to BlogPage as being BlogIndexPages
    parent_page_types = ["BlogIndexPage"]

    # Specifies what content types can exist as children of BlogPage
    # Empty list means that no child content types are allowed
    subpage_types = []


class BlogIndexPage(Page):
    # intro = RichTextField(blank=True) # wagtail docs getting started tutorial
    introduction = models.TextField(help_text="Text to describe the page.", blank=True)
    image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
        help_text="Landscape mode only; horizontal width between 1000px and 3000px.",
    )
    content_panels = Page.content_panels + [
        FieldPanel("introduction"),
        FieldPanel("image"),
    ]

    # add the get_context method:
    def get_context(self, request):
        # update context to include only published posts, ordered by reverse-chron
        # blogpages = self.get_children().live().order_by('-first_published_at')
        # context['blogpages'] = blogpages
        context = super(BlogIndexPage, self).get_context(request)
        context["posts"] = (
            BlogPage.objects.descendant_of(self).live().order_by("-date_published")
        )        
        return context
    
    def serve_preview(self, request, mode_name):
        # needed for previews to work
        return self.serve(request)
    
    # returns the child BlogPage objects for this BlogPageIndex
    # if a tag is used then it will filter the posts by tag
    def get_posts(self, tag=None):
        posts = BlogPage.objects.live().descendant_of(self)
        # if tag:
            # posts = posts.filter(tags=tag)
        return posts

错误详情1:后台访问错误

OperationalError at /admin/pages/4/
no such column: blog_blogindexpage.introduction
Request Method: GET
Request URL:    http://127.0.0.1:8000/admin/pages/4/
Django Version: 4.2.11
Exception Type: OperationalError
Exception Value:    
no such column: blog_blogindexpage.introduction
Exception Location: /home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/sqlite3/base.py, line 328, in execute
Raised during:  wagtail.admin.views.pages.listing.IndexView
Python Executable:  /home/david/projects/someproject/about/venv-about/bin/python3
Python Version: 3.8.10
Python Path:    
['/home/david/projects/someproject/about/about',
 '/usr/lib/python38.zip',
 '/usr/lib/python3.8',
 '/usr/lib/python3.8/lib-dynload',
 '/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages']
Server time:    Tue, 16 Apr 2024 18:18:24 +0000

错误详情2:迁移命令错误

Running migrations:
  Applying blog.0006_remove_blogindexpage_intro_remove_blogpage_authors_and_more...Traceback (most recent call last):
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/sqlite3/base.py", line 328, in execute
    return super().execute(query, params)
sqlite3.IntegrityError: CHECK constraint failed: new__blog_blogpage

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/core/management/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/core/management/base.py", line 412, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/core/management/base.py", line 458, in execute
    output = self.handle(*args, **options)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/core/management/base.py", line 106, in wrapper
    res = handle_func(*args, **kwargs)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/core/management/commands/migrate.py", line 356, in handle
    post_migrate_state = executor.migrate(
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/migrations/executor.py", line 135, in migrate
    state = self._migrate_all_forwards(
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/migrations/executor.py", line 167, in _migrate_all_forwards
    state = self.apply_migration(
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/migrations/executor.py", line 252, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/migrations/migration.py", line 132, in apply
    operation.database_forwards(
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/migrations/operations/fields.py", line 235, in database_forwards
    schema_editor.alter_field(from_model, from_field, to_field)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/sqlite3/schema.py", line 175, in alter_field
    super().alter_field(model, old_field, new_field, strict=strict)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/base/schema.py", line 831, in alter_field
    self._alter_field(
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/sqlite3/schema.py", line 463, in _alter_field
    self._remake_table(model, alter_fields=[(old_field, new_field)])
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/sqlite3/schema.py", line 336, in _remake_table
    self.execute(
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/base/schema.py", line 201, in execute
    cursor.execute(sql, params)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/utils.py", line 102, in execute
    return super().execute(sql, params)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/utils.py", line 91, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "/home/david/projects/someproject/about/venv-about/lib/python3.8/site-packages/django/db/backends/sqlite3/base.py", line 328, in execute
    return super().execute(query, params)
django.db.utils.IntegrityError: CHECK constraint failed: new__blog_blogpage

问题分析与解决步骤

1. 错误根源

  • 后台列不存在:修改BlogIndexPage模型时,将原intro字段替换为introduction,但迁移未正确执行,导致数据库无对应字段。
  • 迁移约束失败:SQLite修改表结构时会重建表,CHECK constraint failed是因为现有数据违反新约束——BlogPage的MultipleChooserPanel设置了min_num=1,但已有博客页面未关联作者,触发非空约束。

2. 具体解决步骤

步骤1:临时移除严格约束

修改BlogPage中的MultipleChooserPanel,注释min_num=1:

MultipleChooserPanel(
    "blog_person_relationship",
    chooser_field_name="person",
    heading="Authors",
    label="Author",
    panels=None,
    # min_num=1, 临时注释此行
),

步骤2:重置迁移文件

  • 删除未执行的迁移文件(blog/migrations/0006_*.py)
  • 重新生成迁移:
python manage.py makemigrations
  • 打开新生成的迁移文件,确保逻辑为:先给BlogIndexPage添加introduction字段,再移除旧的intro字段。

步骤3:执行迁移并补全数据

python manage.py migrate

迁移完成后,进入后台给所有已存在的BlogPage添加作者,再恢复min_num=1配置,重新生成并执行迁移。

步骤4:验证后台访问

重启服务后访问后台管理页面,确认OperationalError消失。

额外注意事项

  • SQLite对表结构修改支持有限,复杂变更易出错,生产环境建议使用PostgreSQL。
  • Wagtail页面模型修改后,必须确保迁移正确执行,避免模型与数据库结构不一致。

内容的提问来源于stack exchange,提问作者user3125823

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 16:30:57