跳转到主要内容

快速且简单的树形结构。

项目描述

⚠ 开放融资以支持MySQL & SQLite兼容性 ⚠

快速且简单的树形结构。

http://img.shields.io/pypi/v/django-tree.svg?style=flat-square http://img.shields.io/travis/BertrandBordage/django-tree/master.svg?style=flat-square http://img.shields.io/coveralls/BertrandBordage/django-tree/master.svg?style=flat-square

处于测试阶段,目前还不能用于生产。

这个工具的工作方式与 django-mpttdjango-treebeard 非常相似,然而由于它在构思上有所不同,从头开始比重写现有解决方案更好更快。

与这些解决方案相比,django-tree旨在具有以下优势(其中一些已经实现)

  • 更少侵入性(没有由于模型、管理器和查询集子类引起的继承问题)

  • 更容易安装

  • 更容易使用

  • 更完整

  • 极简主义(代码更少,数据库字段更少)

  • 无错误

  • 安全(大部分逻辑直接写入数据库)

  • 所有操作更快

然而,这里没有突破性的东西:这仅仅是最新Django改进的正确使用和良好SQL知识的结果。

基准测试

详细的基准测试 可以给出django-tree与其他Django解决方案相比的性能的良好概念。同时它更易于使用,更健壮,并且完全通用到原始SQL,批量等。

基准测试的一些值得注意的摘录(越少越好)

benchmark/results/postgresql_-_Create_all_objects.svg benchmark/results/postgresql_-_Rebuild_paths.svg benchmark/results/postgresql_-_Create_[root].svg

安装

Django-tree需要Django 1.8、1.11或2.0以及Python 2或3。目前,django-tree仅适用于PostgreSQL。将来将适应其他数据库。

安装模块后,您需要将'tree'添加到您的INSTALLED_APPS中,然后在具有ForeignKey('self')(通常命名为parent)的模型中添加一个PathField(如果该字段有其他名称,请使用CreateTreeTriggerparent_field参数)。PathField存储Path对象,这些对象有执行查询的方法,例如获取当前对象的全部子代、其兄弟等。为了更方便地调用这些方法,您可以将TreeModelMixin添加到您的模型中。混合继承顺序并不重要,因为混合方法不会与Django冲突。如果您在同一个模型上有多个PathField,您必须在调用方法时指定字段名称,使用path_field

这将为您提供如下所示的模型

from django.db.models import Model, CharField, ForeignKey, BooleanField
from tree.fields import PathField
from tree.models import TreeModelMixin

class YourModel(Model, TreeModelMixin):
    name = CharField(max_length=30)
    parent = ForeignKey('self', null=True, blank=True)
    path = PathField()
    public = BooleanField(default=False)

    class Meta:
        ordering = ['path']

然后您需要创建一个SQL触发器,该触发器将自动更新path。为此,创建一个依赖最新django-tree迁移的迁移,并添加一个CreateTreeTrigger操作

from django.db import migrations
from tree.operations import CreateTreeTrigger

class Migration(migrations.Migration):
    dependencies = [
        ('tree', '0001_initial'),
    ]

    operations = [
        CreateTreeTrigger('your_app.YourModel'),
    ]

如果您已经在YourModel中有了数据,您将需要添加一个操作来允许SQLNULL值,然后在创建触发器之前重新构建路径,并撤销对NULL值的允许

from django.db import migrations
from tree.fields import PathField
from tree.operations import CreateTreeTrigger, RebuildPaths

class Migration(migrations.Migration):
    dependencies = [
        ('tree', '0001_initial'),
    ]

    operations = [
        migrations.AlterField('YourModel', 'path', PathField(null=True)),
        CreateTreeTrigger('YourModel'),
        RebuildPaths('YourModel', 'path'),
        migrations.AlterField('YourModel', 'path', PathField()),
    ]

然而,上述模型是无序的。相同父级的子代将按主键排序。您可以使用PathFieldorder_by参数来指定子代的排序方式。如果需要,您可以为用户添加一个字段来明确排序这些对象,通常是一个位置字段。示例模型

from django.db.models import (
    Model, CharField, ForeignKey, IntegerField, BooleanField)
from tree.fields import PathField
from tree.models import TreeModelMixin

class YourModel(Model, TreeModelMixin):
    name = CharField(max_length=30)
    parent = ForeignKey('self', null=True, blank=True)
    position = IntegerField(default=1)
    path = PathField(order_by=['position', 'name'])
    public = BooleanField(default=False)

    class Meta:
        ordering = ['path']

相应的迁移如下

from django.db import models, migrations
from tree.operations import CreateTreeTrigger

class Migration(migrations.Migration):
    dependencies = [
        ('tree', '0001_initial'),
    ]

    operations = [
        migrations.AddField('YourModel', 'position',
                            models.IntegerField(default=1))
        CreateTreeTrigger('YourModel'),
    ]

在这里,相同父级的子代将按位置排序,如果位置相同,则按名称排序。

用法

由于有CreateTreeTriggerPathField将自动填充,一旦安装,您就无需设置、修改或查看其值。但您可以使用它存储的Path对象或更方便的TreeModelMixin来获取当前实例的树信息,或在整个树结构上执行复杂的查询。以下示例展示了大部分可能性

obj = YourModel.objects.all()[0]
obj.path.get_level()
obj.get_level()  # Shortcut for the previous method, if you use
                 # `TreeModelMixin`. Same for other object methods below.
obj.is_root()
obj.is_leaf()
obj.get_children()
obj.get_children().filter(public=True)
obj.get_ancestors()
obj.get_ancestors(include_self=True)
obj.get_descendants(include_self=True)
obj.get_siblings()
obj.get_prev_sibling()  # Fetches the previous sibling.
obj.get_next_sibling()
# Same as `get_prev_sibling`, except that we get the first public one.
obj.get_prev_siblings().filter(public=True).first()
other = YourModel.objects.all()[1]
obj.is_ancestor_of(other)
obj.is_descendant_of(other, include_self=True)
YourModel.objects.filter_roots()

#
# Advanced usage
# Use the following methods only if you understand exactly what they mean.
#

YourModel.rebuild_paths()  # Rebuilds all paths of this field, useful only
                           # if something is broken, which shouldn’t happen.
YourModel.disable_tree_trigger()  # Disables the SQL trigger.
YourModel.enable_tree_trigger()   # Restores the SQL trigger.
with YourModel.disabled_tree_trigger():
    # What happens inside this context manager is ignored
    # by the SQL trigger.
    # The trigger is restored after that, even if there an error occurred.
    pass

还有一些不太有用的查找和转换可用。它们将在将来通过示例进行说明。

MPTT和treebeard的差异

级别 vs 深度

django-mptt和django-treebeard使用两个不同的名称来表示几乎相同的东西:MPTT使用级别,treebeard使用深度。它们都是整数,用于表示节点与树顶部的距离。唯一的区别是,根据惯例,级别应该从1开始,而深度应该从0开始。

遗憾的是,MPTT和treebeard在索引方面都是错误的:MPTT从级别0开始,而treebeard从深度1开始。

Django-tree最终通过从1级开始实现级别来解决这个问题,并且没有深度以避免混淆。必须选择一个名称,我发现“级别”更能准确地表达我们处理的是一个抽象树,其中同一级别的所有节点都在同一行。相比之下,“深度”听起来像我们实际上正在挖掘一个真正的根,给人一种根的子节点可以比另一个根的子节点深的感觉,就像在现实生活中一样。

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源代码分发

django-tree-0.5.6.tar.gz (36.2 kB 查看哈希值)

上传时间 源代码

构建版本

django_tree-0.5.6-py3-none-any.whl (39.8 kB 查看哈希值)

上传时间 Python 3

由以下组织支持