Django-mptt: 如何成功地移动节点

5

django-mptt似乎要让我发疯。我试图做一些相对简单的事情:我将删除一个节点,并需要合理处理该节点的子节点。换句话说,我想将它们向上移动一级,使它们成为其当前父节点的父节点。

也就是说,如果树看起来像:

 Root
  |
Grandpa
  |
Father
|    |
C1   C2

我要删除父级,希望C1和C2成为Grandpa的子级。

这是我使用的代码:

class Node(models.Model):
    first_name   = models.CharField(max_length=80, blank=True)
    parent       = models.ForeignKey('self', null=True, blank=True, related_name='children')

    def reparent_children(self, parent):
        print "Reparenting"
        for child in self.get_children():
            print "Working on", child.first_name, "to parent", parent.email
            parent = Node.objects.get(id=parent.id)
            child.move_to(parent, 'last-child')
            child.save()

所以我会这样称呼:

father.reparent_children(grandpa)
father.parent = None
father.save()

这个方法几乎可行。孩子们将他们的父母报告为“爷爷”:

c1.parent == grandpa  # True

Grandpa将C1和C2视为其子节点

c1 in grandpa.children.all()   # True

然而,Root否认与这些孩子有任何关系。
c1.get_root() == father  # c1's root is father, instead of Root

c1 in root.get_descendants()  # False

如何使子元素移动而不破坏其根元素?


1
你确定“father.parent = None”是删除一个节点的正确方式吗? - mawimawi
在这种情况下,我实际上并没有删除节点 - 我正在对其进行归档。我想将其从树中移除。不过你说得对,我实际上并没有在这里将其从树中移除。 - Parand
看起来将父节点设置为None并保存实际上是从树中删除节点的方法(根据mptt测试用例),所以这看起来是正确的。 - Parand
另外一件事:让 Node 继承于 MPTTModel,并让 NodeManager 继承于 TreeManager 是有帮助的。 - Sam Bobel
2个回答

6

内部的lftrght值会在您保存子节点时第一次更改(即reparent_children方法的最后一行)。save()不会更新您可能已经存在的实例。我认为一个安全的方法是每次从数据库重新获取它们,像这样:

def reparent_children(self, parent):
    print "Reparenting"
    for child in self.get_children():
        print "Working on", child.first_name, "to parent", parent.email
        parent = Node.objects.get(id=parent.id)
        current_child = Node.objects.get(id = child.id)
        current_child.move_to(parent, 'last-child')
        current_child.save()

我以前也遇到过类似的问题,那个方法解决了我的问题。


6
多米尼克,我最终使用了这种方法,并且它“似乎”起作用了,但是在使用django-mptt时,我一直在质疑自己的理智。我不知道我是否实际上已经修复了问题或将其隐藏在其他地方。 - Parand
在 Django 应用程序中遇到了类似的问题。使用 parent.refresh_from_db() 解决了该问题。 - jdsantiagojr

1
这个库最近几天让我感到非常困惑--move_to似乎并不能实现我想要的功能,而且我的树结构一直处于不同步状态。我想出了一个解决方案,虽然速度较慢且不太传统,但我更有信心。它围绕着管理方法partial_rebuild在这里展开。
def delete_node(self):
    if not self.parent:
        print("Should not delete root node, confusing behavior follows")
        return
    tree_id = self.tree_id
    parent = self.parent

    for child in self.get_children():
        child.parent = parent
        child.save()

    self.delete()
    Node.objects.partial_rebuild(tree_id)

如果您愿意,可以使用child.move_node(parent)替换child.parent = parent。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接