【问题标题】:Creating a tree from self referential tables in SQLalchemy从 SQLalchemy 中的自引用表创建树
【发布时间】:2011-06-21 05:35:38
【问题描述】:

我正在为一个面向 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)

有人有什么想法吗?

【问题讨论】:

    标签: python sqlalchemy flask


    【解决方案1】:

    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()
    

    【讨论】:

    • 谢谢你,但我还是有点想不通。对于第二个链接,是否可以使用定义模型的声明性基本方式来做到这一点(这就是 sqlalchemy 的烧瓶扩展所使用的)?
    • @Estin 加入的load_all 调用指定了“孩子”N 次。在这种情况下为 4,因此树只会递归 4 次。有没有办法让它递归任意次数?除非有一种编程方式可以轻松确定它吗?
    • @Zoran,如果您计划使用树递归,这不是最佳选择,更强大的方法是使用 MPTT 解决方案,如sqlalchemy-mptt.readthedocs.org/en/latest > 除非有一种编程方式可以轻松确定它? - 您可以在根节点上存储他的“深层”。
    • @ZoranPavlovic - 你问你的问题已经一年多了,但我想我会给你另一个提示。您可以简单地在一个查询中获取所有行(即没有options(joinedload_all(...)),然后根据 parent_id 构建您的树。我猜您不会有数百万个元素(即您会有多少页?100?甚至1000?)所以从数据库性能的角度来看,这样的查询不应该造成任何问题。而且遍历这样的数组来构建你的树不应该太耗费资源。
    • relationship 中的cascade 参数与foreignkey 函数设置中的ONDELETE 参数设置为CASCADE 之间有什么区别?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-09
    • 2017-07-10
    • 1970-01-01
    • 2021-10-01
    • 1970-01-01
    相关资源
    最近更新 更多