资讯详情

资讯详情

Wagtail 如何用数据迁移把 RichTextField 转换为 StreamField?

Wagtail 如何用数据迁移把 RichTextField 转换为 StreamField【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail在 Wagtail 项目中如果把一个页面上已经存有内容的RichTextField改成StreamField数据库迁移本身会顺利跑完但旧文本不会被自动变成 StreamField 要求的 JSON 结构内容会暂时无法按新字段读取。Wagtail 官方文档给出的解法是在makemigrations生成的迁移里手写一个数据迁移函数把存量文本包装成 StreamField 的 JSON 表示并且要求新的StreamField定义中必须包含一个RichTextBlock作为可用的块类型。本文按 StreamField migrations 文档 的流程给出从改模型、生成迁移、编辑迁移到迁移后数据形态的完整操作路径。前提为什么必须补一步数据迁移两个关键点来自文档原话RichTextField和StreamField在数据库里都使用 text 列所以把字段类型改掉后schema 迁移会完成且没有错误但 StreamField 用 JSON 表示来存储数据旧的纯文本需要额外的转换步骤才能再次被访问。因此整个任务分两层schema 层面由 Django 自动生成的AlterField完成数据层面要靠你在迁移里添加的RunPython操作完成。如果跳过数据迁移旧内容就只是一段对 StreamField 来说无法解析的字符串。准备条件按文档示例demo应用中的BlogPage模型body字段从RichTextField改为带一个名为rich_text的RichTextBlock的StreamField你需要先确认要转换的StreamField定义里包含一个RichTextBlock文档明确the StreamField needs to include a RichTextBlock as one of the available block types数据库里存在已有内容的页面。注意文档的第一版迁移只对已发布的 Page 对象生效见后文限制与边界草稿与修订需要第二版迁移。第一步修改模型并生成迁移把模型字段从RichTextField改为StreamField后按文档给出的命令照常生成迁移./manage.py makemigrations生成的迁移会包含一条AlterField把字段改为StreamField和一条依赖dependency行。这两处必须原样保留——文档在代码注释中两次强调leave the dependency line from the generated migration intact! 和 leave the generated AlterField intact!。第二步给迁移加入数据迁移函数编辑刚生成的迁移文件加入一对正向/反向转换函数并用migrations.RunPython包起来。文档给出的完整示例如下其中demo是你的应用名、BlogPage是你的页面模型、body是要转换的字段名rich_text是StreamField中RichTextBlock的块名请对应替换成你自己的定义import json from django.core.serializers.json import DjangoJSONEncoder from django.db import migrations import wagtail.blocks import wagtail.fields def convert_to_streamfield(apps, schema_editor): BlogPage apps.get_model(demo, BlogPage) for page in BlogPage.objects.all(): page.body json.dumps( [{type: rich_text, value: page.body}], clsDjangoJSONEncoder ) page.save() def convert_to_richtext(apps, schema_editor): BlogPage apps.get_model(demo, BlogPage) for page in BlogPage.objects.all(): if page.body: stream json.loads(page.body) page.body .join( [child[value] for child in stream if child[type] rich_text] ) page.save() class Migration(migrations.Migration): dependencies [ # leave the dependency line from the generated migration intact! (demo, 0001_initial), ] operations [ migrations.RunPython( convert_to_streamfield, convert_to_richtext, ), # leave the generated AlterField intact! migrations.AlterField( model_nameBlogPage, namebody, fieldwagtail.fields.StreamField( [(rich_text, wagtail.blocks.RichTextBlock())], ), ), ]几个要点正向函数convert_to_streamfield把每一页的body重新写成 JSON 字符串一个数组元素是{type: rich_text, value: 原文本}并用DjangoJSONEncoder编码反向函数convert_to_richtext做逆操作从 JSON 里取出所有type rich_text子块的value并拼接回纯文本保证migrate回退时不丢数据RunPython必须放在AlterField之前先转数据、再改字段定义。编辑完成后按 Django 常规流程应用迁移即可。需要迁移草稿和页面修订时使用第二版迁移文档明确指出上面的迁移will work on published Page objects only。如果你的站点还有草稿页面draft pages和页面修订page revisions需要改用文档给出的第二个示例。它的核心变化是通过ContentType.objects.get_for_model(BlogPage)找到该模型的内容类型再遍历wagtailcore应用的Revision模型用revision.content读写修订里的 JSON 数据转换前先用json.loads试探已经是合法 JSON 的数据直接跳过只有解析失败即仍是旧纯文本才包装成 Stream 结构因此迁移可以重复执行迁移依赖中除原有依赖外还需要一条wagtailcore的迁移依赖文档示例为(wagtailcore, 0076_modellogentry_revision)import json from django.contrib.contenttypes.models import ContentType from django.core.serializers.json import DjangoJSONEncoder from django.db import migrations import wagtail.blocks import wagtail.fields def page_to_streamfield(page): changed False try: json.loads(page.body) except ValueError: page.body json.dumps( [{type: rich_text, value: page.body}], ) changed True else: # Its already valid JSON. Leave it. 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: body, type: rich_text}], clsDjangoJSONEncoder ) changed True else: # Its already valid JSON. Leave it. pass return revision_data, changed def page_to_richtext(page): changed False if page.body: try: body_data json.loads(page.body) except ValueError: # Its not apparently a StreamField. Leave it. pass else: page.body .join( [child[value] for child in body_data if child[type] rich_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: # Its not apparently a StreamField. Leave it. pass else: raw_text .join( [child[value] 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(demo, BlogPage) content_type ContentType.objects.get_for_model(BlogPage) Revision apps.get_model(wagtailcore, Revision) for page in BlogPage.objects.all(): page, changed page_converter(page) if changed: page.save() for revision in Revision.objects.filter( content_type_idcontent_type.pk, object_idpage.pk ): revision_data revision.content revision_data, changed pagerevision_converter(revision_data) if changed: revision.content revision_data 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! (demo, 0001_initial), (wagtailcore, 0076_modellogentry_revision), ] operations [ migrations.RunPython( convert_to_streamfield, convert_to_richtext, ), # leave the generated AlterField intact! migrations.AlterField( model_nameBlogPage, namebody, fieldwagtail.fields.StreamField( [(rich_text, wagtail.blocks.RichTextBlock())], ), ), ]同样注意把demo、BlogPage、body替换为你自己的应用、模型和字段名wagtailcore依赖行的版本号请以你项目中实际存在的 wagtailcore 迁移为准。迁移后的数据形态如何核对结果文档给出的成功状态就是转换后的存储格式本身。以文档示例为例原来存放 HTML 字符串的body列迁移后变成形如下面的 JSON 字符串文档示例[{type: rich_text, value: 原来的 HTML 文本}]核对时可以用文档中反向函数同样的判断逻辑作为依据字段值能被json.loads解析、且其中的子块type为rich_text说明该页数据已按 StreamField 结构存储。对于第二版迁移文档也展示了这一判断方式——json.loads失败才转换成功则跳过因此对已转换过的数据重复执行不会再次包装。模板侧转换后的body就可以按普通 StreamField 用{% include_block %}渲染写法见 StreamField 主题文档。限制与边界第一版迁移只覆盖已发布页面。文档原话Note that the above migration will work on published Page objects only. If you also need to migrate draft pages and page revisions, then edit the migration as in the following example instead. 有草稿或修订的站点必须用第二版StreamField必须包含RichTextBlock。文档没有给出其他转换形态的完整示例如果目标结构不含RichTextBlock此迁移路径不适用两个示例都只处理纯文本 → 单一 rich_text 子块这一种映射多字段、嵌套块的批量转换需要另写逻辑如果你要迁移的是已有 StreamField 的块定义变更例如改块名而不是 RichText 到 StreamField 的字段类型转换那是文档中另一节StreamField data migrations覆盖的任务Wagtail 提供了wagtail.blocks.migrations.migrate_operation、wagtail.blocks.migrations.operations、wagtail.blocks.migrations.utils三个模块实现见 migrate_operation.py 与 operations.py用MigrateStreamData操作代替手写RunPython参数与操作清单的完整说明见 数据迁移参考。下一步数据迁移完成、body已是 StreamField 结构后模板中用{% include_block page.body %}整体渲染或遍历子块逐个渲染块类型的更多定义方式嵌套 StreamBlock、StructBlock、ListBlock 及 block path 规则继续参考 StreamField migrations 文档 与 StreamField 主题文档。【免费下载链接】wagtailA Django content management system focused on flexibility and user experience项目地址: https://gitcode.com/GitHub_Trending/wa/wagtail创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →