在迁移中移动 Wagtail 页面
Moving Wagtail pages in a migration
我正在重组我的 Wagtail 应用程序以删除其中只有一个项目的 IndexPage,并将该项目移动为当前 IndexPage 父项的子项。
基本上从这个开始:
Page--|
|--IndexPage--|
|--ChildPages
(其中只有 1 个)
对此:
Page--|
|--ChildPage
我对模型进行了更改,以便使用此结构来创建新内容并修复相关视图以直接指向 ChildPage。但现在我想将当前数据迁移到新结构,但我不确定如何着手......理想情况下,这将在迁移中完成,这样我们就不必手动进行任何操作。
有没有办法在迁移过程中以编程方式将这些 ChildPage 向上移动到树中?
不幸的是,有一个硬限制(可能)排除了在迁移中进行页面树调整的可能性:插入、移动和删除页面等树操作是作为 Page
模型上的方法实现的,并且在迁移中,您只能访问该模型的 'dummy' 版本,它只允许您访问数据库字段和基本 ORM 方法,而不是那些自定义方法。
(您可以通过将 from wagtail.wagtailcore.models import Page
放入迁移中并使用它而不是标准的 Page = apps.get_model("wagtailcore", "Page")
方法来解决此问题,但我不建议这样做 - 它容易损坏如果迁移 运行 在迁移序列中 Page
模型仍在构建并且与模型的 'real' 状态不匹配的点。)
相反,我建议编写 Django management command to do the tree manipulation - within a management command it is safe to import the Page
model from wagtailcore, as well as your specific page models. Page
provides a method move(target, pos)
which works as per the Treebeard API - 移动子页面的代码可能类似于:
from myapp.models import IndexPage
# ...
for index_page in IndexPage.objects.all():
for child_page in index_page.get_children():
child_page.move(index_page, 'right')
index_page.delete()
理论上,应该可以使用 Daniele Miele 在 Django-treebeard and Wagtail page creation 中演示的相同类型的操作来构建 move()。它看起来像这样 Python 伪代码:
def move(page, target):
# assuming pos='last_child' but other cases follow similarly,
# just with more bookkeeping
# first, cut it out of its old tree
page.parent.numchild -= 1
for sib in page.right_siblings: # i.e. those with a greater path
old = sib.path
new = sib.path[:-4] + (int(sib.path[-4:])-1):04
sib.path = new
for nib in sib.descendants:
nib.path = nib.path.replace_prefix(old, new)
# now, update itself
old_path = page.path
new_path = target.path + (target.numchild+1):04
page.path = new_path
old_url_path = page.url_path
new_url_path = target.url_path + page.url_path.last
page.url_path = new_url_path
old_depth = page.depth
new_depth = target.depth + 1
page.depth = new_depth
# and its descendants
depth_change = new_depth - old_depth
for descendant in page.descendants:
descendant.path = descendant.path.replace_prefix(old_path, new_path)
descendant.url_path = descendant.url_path.replace_prefix(old_path, new_path)
descendant.depth += depth_change
# finally, update its new parent
target.numchild += 1
使这个操作比看起来更简单的核心概念是:当一个节点被重新排序或移动时,它的所有后代都需要更新,但是 only 他们需要的更新与他们的祖先获得的更新完全相同。它被用作前缀替换(如果是 str)或差异(如果是 int),这两者都不需要知道后代的确切值。
也就是说,我还没有测试过;它足够复杂,很容易搞砸;并且无法知道我是否更新了 Wagtail 关心的每个不变量。所以对于管理命令方式也有话要说。
我正在重组我的 Wagtail 应用程序以删除其中只有一个项目的 IndexPage,并将该项目移动为当前 IndexPage 父项的子项。
基本上从这个开始:
Page--|
|--IndexPage--|
|--ChildPages
(其中只有 1 个)
对此:
Page--|
|--ChildPage
我对模型进行了更改,以便使用此结构来创建新内容并修复相关视图以直接指向 ChildPage。但现在我想将当前数据迁移到新结构,但我不确定如何着手......理想情况下,这将在迁移中完成,这样我们就不必手动进行任何操作。
有没有办法在迁移过程中以编程方式将这些 ChildPage 向上移动到树中?
不幸的是,有一个硬限制(可能)排除了在迁移中进行页面树调整的可能性:插入、移动和删除页面等树操作是作为 Page
模型上的方法实现的,并且在迁移中,您只能访问该模型的 'dummy' 版本,它只允许您访问数据库字段和基本 ORM 方法,而不是那些自定义方法。
(您可以通过将 from wagtail.wagtailcore.models import Page
放入迁移中并使用它而不是标准的 Page = apps.get_model("wagtailcore", "Page")
方法来解决此问题,但我不建议这样做 - 它容易损坏如果迁移 运行 在迁移序列中 Page
模型仍在构建并且与模型的 'real' 状态不匹配的点。)
相反,我建议编写 Django management command to do the tree manipulation - within a management command it is safe to import the Page
model from wagtailcore, as well as your specific page models. Page
provides a method move(target, pos)
which works as per the Treebeard API - 移动子页面的代码可能类似于:
from myapp.models import IndexPage
# ...
for index_page in IndexPage.objects.all():
for child_page in index_page.get_children():
child_page.move(index_page, 'right')
index_page.delete()
理论上,应该可以使用 Daniele Miele 在 Django-treebeard and Wagtail page creation 中演示的相同类型的操作来构建 move()。它看起来像这样 Python 伪代码:
def move(page, target):
# assuming pos='last_child' but other cases follow similarly,
# just with more bookkeeping
# first, cut it out of its old tree
page.parent.numchild -= 1
for sib in page.right_siblings: # i.e. those with a greater path
old = sib.path
new = sib.path[:-4] + (int(sib.path[-4:])-1):04
sib.path = new
for nib in sib.descendants:
nib.path = nib.path.replace_prefix(old, new)
# now, update itself
old_path = page.path
new_path = target.path + (target.numchild+1):04
page.path = new_path
old_url_path = page.url_path
new_url_path = target.url_path + page.url_path.last
page.url_path = new_url_path
old_depth = page.depth
new_depth = target.depth + 1
page.depth = new_depth
# and its descendants
depth_change = new_depth - old_depth
for descendant in page.descendants:
descendant.path = descendant.path.replace_prefix(old_path, new_path)
descendant.url_path = descendant.url_path.replace_prefix(old_path, new_path)
descendant.depth += depth_change
# finally, update its new parent
target.numchild += 1
使这个操作比看起来更简单的核心概念是:当一个节点被重新排序或移动时,它的所有后代都需要更新,但是 only 他们需要的更新与他们的祖先获得的更新完全相同。它被用作前缀替换(如果是 str)或差异(如果是 int),这两者都不需要知道后代的确切值。
也就是说,我还没有测试过;它足够复杂,很容易搞砸;并且无法知道我是否更新了 Wagtail 关心的每个不变量。所以对于管理命令方式也有话要说。