问题 从SQLalchemy中的自引用表创建树


我正在为一个面向iPhone的网站建立一个基本的CMS瓶,我遇到了一些问题。我有一个非常小的数据库,只有1个表(页面)。这是模型:

class Page(db.Model):
    __tablename__ = 'pages'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    parent_id = db.Column(db.Integer, db.ForeignKey("pages.id"), nullable=True)

如您所见,对于子页面,它们只是引用了另一个页面对象 parent_id 领域。我在管理面板中尝试做的是有一个嵌套的无序列表,其中所有页面都嵌套在其父页面中。我对如何做到这一点知之甚少。所有我能想到的是以下(这只会起作用(可能 - 我没有测试过)2级下来):

pages = Page.query.filter_by(parent_id=None)
for page in pages:
    if Page.query.filter_by(parent_id=page.id):
        page.sub_pages = Page.query.filter_by(parent_id=page.id)

然后我会将其格式化为模板中的列表。如何使用可能超过10个嵌套页面来完成这项工作?

谢谢你提前!


编辑: 我环顾四周,发现了 http://www.sqlalchemy.org/docs/orm/relationships.html#adjacency-list-relationships所以我补充道

children = db.relationship("Page", backref=db.backref("parent", remote_side=id))

在我的底部 Page 模型。而且我正在寻找递归遍历所有内容并将其添加到对象树中。我可能没有任何意义,但这是我描述它的最佳方式


编辑2: 我做了一个递归函数来运行所有页面并生成一个包含所有页面及其子项的大型嵌套字典,但它不断崩溃python所以我认为它只是一个无限循环...这里是函数

def get_tree(base_page, dest_dict):
    dest_dict = { 'title': base_page.title, 'content': base_page.content }
    children = base_page.children
    if children:
        dest_dict['children'] = {}
        for child in children:
            get_tree(base_page, dest_dict)
    else:
        return

和我正在测试它的页面:

@app.route('/test/')
def test():
    pages = Page.query.filter_by(parent_id=None)
    pages_dict = {}
    for page in pages:
        get_tree(page, pages_dict)
    return str(pages_dict)

有人有任何想法吗?


3095
2018-02-04 08:51


起源



答案:


看着 http://sqlamp.angri.ru/index.html

要么 http://www.sqlalchemy.org/trac/browser/examples/adjacency_list/adjacency_list.py

UPD: 对于adjacency_list.py声明性示例

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base(metadata=metadata)

class TreeNode(Base):

    __tablename__ = 'tree'

    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('tree.id'))
    name = Column(String(50), nullable=False)

    children = relationship('TreeNode',

                        # cascade deletions
                        cascade="all",

                        # many to one + adjacency list - remote_side
                        # is required to reference the 'remote' 
                        # column in the join condition.
                        backref=backref("parent", remote_side='TreeNode.id'),

                        # children will be represented as a dictionary
                        # on the "name" attribute.
                        collection_class=attribute_mapped_collection('name'),
                    ) 

    def __init__(self, name, parent=None):
        self.name = name
        self.parent = parent

    def append(self, nodename):
        self.children[nodename] = TreeNode(nodename, parent=self)

    def __repr__(self):
        return "TreeNode(name=%r, id=%r, parent_id=%r)" % (
                    self.name,
                    self.id,
                    self.parent_id
                )    

修复递归

def get_tree(base_page, dest_dict):
    dest_dict = { 'title': base_page.title, 'content': base_page.content }
    children = base_page.children
    if children:
        dest_dict['children'] = {}
        for child in children:
            get_tree(child, dest_dict)
    else:
        return

在示例中使用查询来从db中递归获取数据:

 # 4 level deep
 node = session.query(TreeNode).\
                        options(joinedload_all("children", "children", 
                                                "children", "children")).\
                        filter(TreeNode.name=="rootnode").\
                        first()

15
2018-02-04 09:15



谢谢你,但它仍然有点过头了。对于第二个链接是否有一个wak用定义模型的声明性基本方式(这是sqlalchemy使用的烧瓶扩展)是什么? - Tom Brunoli
@Estin joinload_all调用指定“children”N次。在这种情况下4,所以树只会递归4次。有没有办法让它递归任意次数?除非有一种程序化的方法来轻松确定它? - Zoran Pavlovic
@Zoran,如果你计划使用树递归这不是最佳选择,更强大的方法是使用MPTT解决方案,如 sqlalchemy-mptt.readthedocs.org/en/latest  >除非有一种编程方式来轻松确定它? - 您可以在根节点上存储他的“深层次”。 - estin
@ZoranPavlovic - 自问你问题以来已经过去了一年多,但我想我会再给你一个提示。您可以简单地获取一个查询中的所有行(即没有 options(joinedload_all(...)) 然后基于parent_id构建您的树。我猜你不会有数百万个元素(即你有多少页?100?甚至1000?)所以这样的查询不应该从数据库性能的角度出现任何问题。并且遍历这样的数组来构建你的树也不应该太耗资源。 - Greg0ry


答案:


看着 http://sqlamp.angri.ru/index.html

要么 http://www.sqlalchemy.org/trac/browser/examples/adjacency_list/adjacency_list.py

UPD: 对于adjacency_list.py声明性示例

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base(metadata=metadata)

class TreeNode(Base):

    __tablename__ = 'tree'

    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('tree.id'))
    name = Column(String(50), nullable=False)

    children = relationship('TreeNode',

                        # cascade deletions
                        cascade="all",

                        # many to one + adjacency list - remote_side
                        # is required to reference the 'remote' 
                        # column in the join condition.
                        backref=backref("parent", remote_side='TreeNode.id'),

                        # children will be represented as a dictionary
                        # on the "name" attribute.
                        collection_class=attribute_mapped_collection('name'),
                    ) 

    def __init__(self, name, parent=None):
        self.name = name
        self.parent = parent

    def append(self, nodename):
        self.children[nodename] = TreeNode(nodename, parent=self)

    def __repr__(self):
        return "TreeNode(name=%r, id=%r, parent_id=%r)" % (
                    self.name,
                    self.id,
                    self.parent_id
                )    

修复递归

def get_tree(base_page, dest_dict):
    dest_dict = { 'title': base_page.title, 'content': base_page.content }
    children = base_page.children
    if children:
        dest_dict['children'] = {}
        for child in children:
            get_tree(child, dest_dict)
    else:
        return

在示例中使用查询来从db中递归获取数据:

 # 4 level deep
 node = session.query(TreeNode).\
                        options(joinedload_all("children", "children", 
                                                "children", "children")).\
                        filter(TreeNode.name=="rootnode").\
                        first()

15
2018-02-04 09:15



谢谢你,但它仍然有点过头了。对于第二个链接是否有一个wak用定义模型的声明性基本方式(这是sqlalchemy使用的烧瓶扩展)是什么? - Tom Brunoli
@Estin joinload_all调用指定“children”N次。在这种情况下4,所以树只会递归4次。有没有办法让它递归任意次数?除非有一种程序化的方法来轻松确定它? - Zoran Pavlovic
@Zoran,如果你计划使用树递归这不是最佳选择,更强大的方法是使用MPTT解决方案,如 sqlalchemy-mptt.readthedocs.org/en/latest  >除非有一种编程方式来轻松确定它? - 您可以在根节点上存储他的“深层次”。 - estin
@ZoranPavlovic - 自问你问题以来已经过去了一年多,但我想我会再给你一个提示。您可以简单地获取一个查询中的所有行(即没有 options(joinedload_all(...)) 然后基于parent_id构建您的树。我猜你不会有数百万个元素(即你有多少页?100?甚至1000?)所以这样的查询不应该从数据库性能的角度出现任何问题。并且遍历这样的数组来构建你的树也不应该太耗资源。 - Greg0ry