【问题标题】:How To Migrate RichTextField to StreamField?如何将 RichTextField 迁移到 StreamField?
【发布时间】:2019-05-01 19:38:32
【问题描述】:

在我的博客文章模型中,我试图将 RichTextField 迁移到 StreamField。我遵循了 Wagtail 文档“将 RichTextFields 迁移到 StreamField”,包括迁移博客文章的部分。他们没有效果。如何将 RichTextField 变成 StreamField?

这是一个使用 Django 1.11.13、Wagtail 2.1 和 PostgreSQL 的博客。我有超过 200 篇博客文章,其中许多都处于 Live+Draft 状态,这意味着它们有未发布的修订版。我检查了数据库中的博客文章,看起来它们的正文字段存储为 HTML。

我复制了文档中的代码并更改了所有引用以与我自己的项目相关。运行 migrate 时,我得到了一个找不到“raw_text”的 AttributeError。所以我创建了一个异常来传递它。我应用了迁移并成功完成。

然后在 models.py 中,我将类的 body 属性从 RichTextField 更改为带有 RichFieldBlock 的 StreamField。我还将其内容面板从 FieldPanel 更改为 StreamFieldPanel。我应用了此迁移,并成功完成。

当我在 Wagtail admin 中查看一些帖子时,所有具有 Live+Draft 状态的帖子都在 StreamFields 中转换为 RichTextBlocks,但是,它们的内容被包装在一个名为 {'rich_text': ''} 的 JSON 对象中。 JSON 对象的样式与编辑器中的其他文本不同。当我实时查看这些帖子时,没有显示任何数据,我认为是因为模板无法读取 JSON。所有具有 Live 状态的博客文章也将 RichTextField 转换为 StreamField,但它们的内容是空的。他们的数据已从编辑器中删除。当我现场观看它们时,它们是空白的。但是,当我在数据库中检查它们时,它们的正文字段仍然包含我之前看到的 HTML。

这是管理员中的 Live+Draft 帖子:

这是管理员中的实时帖子:

在运行两次迁移后,我尝试安装数据库的新副本,但看到奇怪的数据,但这并没有改善情况。

模板.html:

<section>
    {{ page.body }}
</section>

在我运行转换迁移之前的models.py:

class BlogPost(Page):

    body = RichTextField(blank=True)

    content_panels = Page.content_panels + [
            FieldPanel('body'),
        ]

migration.py,我在page_to_streamfield() 函数中为AttributeError 添加了一个异常,因为未找到raw_text:

# -*- coding: utf-8 -*-
# Generated by Django 1.11.13 on 2019-05-01 13:46
from __future__ import unicode_literals

import json

from django.core.serializers.json import DjangoJSONEncoder
from django.db import migrations, models

from wagtail.core.rich_text import RichText


def page_to_streamfield(page):
    changed = False
    try:
        if page.body.raw_text and not page.body:
            page.body = [('rich_text', {'rich_text': RichText(page.body.raw_text)})]
            changed = True
    except AttributeError:
        pass
    return page, changed


def pagerevision_to_streamfield(revision_data):
    changed = False
    body = revision_data.get('body')
    if body:
        try:
            json.loads(body)
        except ValueError:
            revision_data['body'] = json.dumps(
                [{
                    "value": {"rich_text": body},
                    "type": "rich_text"
                }],
                cls=DjangoJSONEncoder)
            changed = True
        else:
            # It's already valid JSON. Leave it.
            pass
    return revision_data, changed


def page_to_richtext(page):
    changed = False
    if page.body.raw_text is None:
        raw_text = ''.join([
            child.value['rich_text'].source for child in page.body
            if child.block_type == 'rich_text'
        ])
        page.body = raw_text
        changed = True
    return page, changed


def pagerevision_to_richtext(revision_data):
    changed = False
    body = revision_data.get('body', 'definitely non-JSON string')
    if body:
        try:
            body_data = json.loads(body)
        except ValueError:
            # It's not apparently a StreamField. Leave it.
            pass
        else:
            raw_text = ''.join([
                child['value']['rich_text'] for child in body_data
                if child['type'] == 'rich_text'
            ])
            revision_data['body'] = raw_text
            changed = True
    return revision_data, changed


def convert(apps, schema_editor, page_converter, pagerevision_converter):
    BlogPage = apps.get_model("blog", "BlogPost")
    for page in BlogPage.objects.all():

        page, changed = page_converter(page)
        if changed:
            page.save()

        for revision in page.revisions.all():
            revision_data = json.loads(revision.content_json)
            revision_data, changed = pagerevision_converter(revision_data)
            if changed:
                revision.content_json = json.dumps(revision_data, cls=DjangoJSONEncoder)
                revision.save()


def convert_to_streamfield(apps, schema_editor):
    return convert(apps, schema_editor, page_to_streamfield, pagerevision_to_streamfield)


def convert_to_richtext(apps, schema_editor):
    return convert(apps, schema_editor, page_to_richtext, pagerevision_to_richtext)


class Migration(migrations.Migration):

    dependencies = [
        # leave the dependency line from the generated migration intact!
        ('blog', 'previous_migration'),
    ]

    operations = [
        migrations.RunPython(
            convert_to_streamfield,
            convert_to_richtext,
        ),
    ]

models.py 运行上一次迁移后,我手动将其更改为 StreamField 并针对此更改运行第二次迁移:

class BlogPost(Page):

    body = StreamField([
            ('rich_text', blocks.RichTextBlock())
        ], blank=True)

    content_panels = Page.content_panels + [
            StreamFieldPanel('body'),
        ]

我希望在 Wagtail 管理员的 StreamField 中看到博客文章的数据,但它是空白的或包装在 JSON 对象中。

【问题讨论】:

    标签: wagtail


    【解决方案1】:

    我能够使用此脚本将 RichTextField 迁移到带有 RichTextBlock 的 StreamField(假设架构类似于 Wagtail 入门教程的前 3 章)。我发现通过将其分解为不同的步骤来考虑这个过程更容易:来自备份/制作备份的新数据库、模式迁移、数据迁移和管理/模板更改。我发现我需要遍历每个 BlogPost 及其所有相关的 PageRevision。编辑实时发布的数据很简单,但草稿以序列化 JSON 的形式存储了两层,很难弄清楚如何与之交互。希望这个脚本可以帮助其他人。注意:此脚本不会反向迁移。

    0004_convert_data.py

    import json
    from django.db import migrations
    import wagtail.core.fields
    from wagtail.core.rich_text import RichText
    
    
    def convert_data(apps, schema_editor):
        blog_page = apps.get_model('blog', 'BlogPage')
        for post in blog_page.objects.all():
            print('\n', post.title)
            # edit the live post
            if post.body.raw_text and not post.body:
                post.body = [('paragraph', RichText(post.body.raw_text))]
                print('Updated ' + post.title)
                post.save()
    
            # edit drafts associated with post
            if post.has_unpublished_changes:
                print(post.title + ' has drafts...')
                for rev in post.revisions.all():
                    data = json.loads(rev.content_json)
                    body = data['body']
                    print(body)
    
                    print('This is current JSON:', data, '\n')
                    data['body'] = json.dumps([{
                        "type": "paragraph",
                        "value": body
                    }])
                    rev.content_json = json.dumps(data)
                    print('This is updated JSON:', rev.content_json, '\n')
    
                    rev.save()
    
            print('Completed ' + post.title + '.' + '\n')
    
    
    class Migration(migrations.Migration):
    
        dependencies = [
            ('blog', '0003_blogpage_stream'),
        ]
    
        operations = [
            migrations.AlterField(
                model_name='blogpage',
                name='body',
                field=wagtail.core.fields.StreamField([('paragraph', wagtail.core.blocks.RichTextBlock())], blank=True),
            ),
    
            migrations.RunPython(convert_data),
        ]
    

    models.py

    from django.db import models
    
    from wagtail.core.models import Page
    from wagtail.core import blocks
    from wagtail.core.fields import RichTextField, StreamField
    from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
    from wagtail.images.blocks import ImageChooserBlock
    from wagtail.search import index
    
    
    class BlogIndexPage(Page):
        intro = RichTextField(blank=True)
    
        content_panels = Page.content_panels + [
            FieldPanel('intro', classname="full")
        ]
    
    
    class BlogPage(Page):
        date = models.DateField("Post date")
        intro = models.CharField(max_length=250)
        # body = RichTextField(blank=True)
        body = StreamField([
            ('paragraph', blocks.RichTextBlock()),
        ], blank=True)
        stream = StreamField([
            ('heading', blocks.CharBlock(classname="full title")),
            ('paragraph', blocks.RichTextBlock()),
            ('image', ImageChooserBlock()),
        ], blank=True)
    
        search_fields = Page.search_fields + [
            index.SearchField('intro'),
            index.SearchField('body'),
        ]
    
        content_panels = Page.content_panels + [
            FieldPanel('date'),
            FieldPanel('intro'),
            StreamFieldPanel('body'),
            StreamFieldPanel('stream'),
        ]
    
    
    

    模板/博客/blog_page.html

    {% extends "base.html" %}
    
    {% load wagtailcore_tags %}
    
    {% block body_class %}template-blogpage{% endblock %}
    
    {% block content %}
        <h1>{{ page.title }}</h1>
        <p class="meta">{{ page.date }}</p>
    
        <div class="intro">{{ page.intro }}</div>
    
        {{ page.body }}
    
        <p><a href="{{ page.get_parent.url }}">Return to blog</a></p>
    
    {% endblock %}
    

    【讨论】:

      猜你喜欢
      • 2021-07-19
      • 2019-05-15
      • 2021-09-30
      • 2017-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多