在迁移中移动 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 关心的每个不变量。所以对于管理命令方式也有话要说。