首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何使用django-树胡子的MP_Node预取后代?

如何使用django-树胡子的MP_Node预取后代?
EN

Stack Overflow用户
提问于 2016-04-12 09:08:38
回答 2查看 2.4K关注 0票数 3

我正在django-rest-framework中使用django-treebeard开发一个具有分层数据结构的应用程序。我的(简化的)主要模型如下

代码语言:javascript
复制
class Task(MP_Node):
    name = models.CharField(_('name'), max_length=64)
    started = models.BooleanField(default=True)

我目前试图实现的是所有根节点的列表视图,其中显示额外的字段(例如是否所有子节点都已启动)。为此,我指定了一个视图:

代码语言:javascript
复制
class TaskViewSet(viewsets.ViewSet):

    def retrieve(self, request, pk=None):
        queryset = Task.get_tree().filter(depth=1, job__isnull=True)
        operation = get_object_or_404(queryset, pk=pk)
        serializer = TaskSerializer(operation)
        return Response(serializer.data)

串行化

代码语言:javascript
复制
class TaskSerializer(serializers.ModelSerializer):
    are_children_started = serializers.SerializerMethodField()

    def get_are_children_started(self, obj):
        return all(task.started for task in Task.get_tree(obj))

这一切都成功了,我得到了预期的结果。但是,我遇到了一个N+1查询问题,对于每个根任务,我需要分别获取所有的子任务。通常情况下,使用prefetch_related可以解决这个问题,但是由于我使用了来自django-treebeard的物化路径结构,任务模型之间没有Django关系,因此prefetch_related不知道该做什么。我尝试过使用自定义Prefetch对象,但由于这仍然需要Django关系路径,所以无法使其工作。

我目前的想法是使用指向根节点的外键扩展Task模型,如下所示:

代码语言:javascript
复制
root_node = models.ForeignKey('self', null=True,
                              related_name='descendant_tasks',
                              verbose_name=_('root task')
                              )

为了使MP关系变得明确,所以可以查询它。然而,这确实让人觉得这是一种不干的方法,所以我想知道是否有人对如何解决这个问题有另外的建议。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-04-19 11:42:07

最后,我向每个任务添加了一个外键,指向其根节点,如下所示:

代码语言:javascript
复制
root_node = models.ForeignKey('self', null=True,
                          related_name='descendant_tasks',
                          verbose_name=_('root task')
                          )

我更新了任务模型上的保存方法,以确保始终指向正确的根节点

代码语言:javascript
复制
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
    try:
        self.root_task = self.get_root()
    except ObjectDoesNotExist:
        self.root_task = None

    return super(Task, self).save(force_insert=False, force_update=False, using=None,
                                  update_fields=None
                                  )

这允许我使用prefetch_related('descendants')预取所有的后代。

每当需要以嵌套方式嵌套后代时,我都会使用以下函数再次嵌套扁平的后代列表

代码语言:javascript
复制
def build_nested(tasks):

    def get_basepath(path, depth):
        return path[0:depth * Task.steplen]

    container, link = [], {}
    for task in sorted(tasks, key=attrgetter('depth')):
        depth = int(len(task.path) / Task.steplen)
        try:
            parent_path = get_basepath(task.path, depth - 1)
            parent_obj = link[parent_path]
            if not hasattr(parent_obj, 'sub_tasks'):
                parent_obj.sub_tasks = []
            parent_obj.sub_tasks.append(task)
        except KeyError:  # Append it as root task if no parent exists
            container.append(task)

        link[task.path] = task

    return container
票数 3
EN

Stack Overflow用户

发布于 2021-12-03 12:33:05

如果要避免使用外键,可以遍历查询集并在内存中重新创建树结构。

在我的例子中,我希望有一个模板标记(很像 templatetag)来显示多个层次的嵌套页面,其中只有一个数据库查询。基本上,复制mptt.utils.get_cached_trees --我最终得到的结果是:

代码语言:javascript
复制
def get_cached_trees(queryset: QuerySet) -> list:
    """Return top-most pages / roots.

    Each page will have its children stored in `_cached_children` attribute
    and its parent in `_cached_parent`. This avoids having to query the database.
    """
    top_nodes: list = []
    path: list = []
    for obj in queryset:
        obj._cached_children = []
        if obj.depth == queryset[0].depth:
            add_top_node(obj, top_nodes, path)
        else:
            while not is_child_of(obj, parent := path[-1]):
                path.pop()
            add_child(parent, obj)

        if obj.numchild:
            path.append(obj)

    return top_nodes

def add_top_node(obj: MP_Node, top_nodes: list, path: list) -> None:
    top_nodes.append(obj)
    path.clear()

def add_child(parent: MP_Node, obj: MP_Node) -> None:
    obj._cached_parent = parent
    parent._cached_children.append(obj)

def is_child_of(child: MP_Node, parent: MP_Node) -> bool:
    """Return whether `child` is a sub page of `parent` without database query.

    `_get_children_path_interval` is an internal method of MP_Node.
    """
    start, end = parent._get_children_path_interval(parent.path)
    return start < child.path < end

可以像这样使用它来避免可怕的N+1查询问题:

代码语言:javascript
复制
for page in get_cached_trees(queryset):
    for child in page._cached_children:
        ... 
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/36568588

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档