Django自遞歸的foreignkey過濾器查詢用於所有的childs

[英]Django self-recursive foreignkey filter query for all childs


I have this model with a self referencing Foreign Key relation:

我有一個自引用外鍵關系的模型:

class Person(TimeStampedModel):
    name = models.CharField(max_length=32)
    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

Now I want to get all the multi level children for a person. How do I write a Django query for it? It needs to behave like recursive function.

現在我想要為一個人獲得所有的多層次的孩子。如何為它編寫Django查詢?它需要像遞歸函數一樣運行。

7 个解决方案

#1


24  

You can always add a recursive function to your model:

您可以在模型中添加一個遞歸函數:

EDIT: Corrected according to SeomGi Han

編輯:根據SeomGi Han修正。

def get_all_children(self, include_self=True):
    r = []
    if include_self:
        r.append(self)
    for c in Person.objects.filter(parent=self):
        _r = c.get_all_children(include_self=True)
        if 0 < len(_r):
            r.extend(_r)
    return r

(Don't use this if you have a lot of recursion or data ...)

(如果你有很多遞歸或數據,不要使用這個。)

Still recommending mptt as suggested by errx.

仍然推薦errx建議的mptt。

#2


13  

You should read about Modified preorder tree traversal. Here is django implementation. https://github.com/django-mptt/django-mptt/

您應該閱讀有關修改后的前序樹遍歷的內容。這是django的實現。https://github.com/django-mptt/django-mptt/

#3


7  

sunn0's suggestion is great idea, but get_all_children() returns weird results. It returns something like [Person1, [Person3, Person4], []]. It should be changed to like below.

sunn0的建議很好,但是get_all_children()返回奇怪的結果。它返回類似於[Person1, [Person3, Person4],[]的東西。應該更改為如下所示。

def get_all_children(self, include_self=True):
    r = []
    if include_self:
        r.append(self)
    for c in Person.objects.filter(parent=self):
        _r = c.get_all_children(include_self=True)
        if 0 < len(_r):
            r.extend(_r)
    return r

#4


5  

If you know the max depth of your tree, you could try something like this (untested):

如果你知道你的樹的最大深度,你可以試試這樣的東西(未經測試):

Person.objects.filter(Q(parent=my_person)|Q(parent__parent=my_person)| Q(parent__parent__parent=my_person))

#5


0  

I had a very similar business problem, wherein given a team member, I was supposed to find out the complete team under under him. But having a large number of employees made the recursive solution very inefficient and also my API was getting time-out errors from server.

我有一個非常相似的業務問題,當我有一個團隊成員時,我應該找出他手下的完整團隊。但是有大量的員工使得遞歸解決方案非常低效,而且我的API也從服務器獲得超時錯誤。

The accepted solution takes the node, goes to it's first child and goes deep down till bottom of the hierarchy. Then comes back up again to the second child (if exists), and then again goes down till the bottom. In short, it explores all the nodes one by one and appends all the members in an array. This leads to a lot of db calls and should be avoided if there is a huge number of nodes to be explored. The solution I came up with, fetches the nodes layer-wise. The number of db calls is equal to the number of layers. Have a look at this SO link for the solution.

接受的解決方案接受節點,進入它的第一個子節點,深入到層次結構的底部。然后再回到第二個孩子(如果存在),然后再繼續下去直到底部。簡而言之,它逐個地探索所有節點,並將數組中的所有成員附加在一起。這將導致大量的db調用,如果要探索大量的節點,則應該避免這種調用。我提出的解決方案,讓節點分層。db調用的數量等於層的數量。看看這個,鏈接到解決方案。

#6


0  

I know this is old, but someone might just get helped.

我知道這是舊的,但有人可能會得到幫助。

Given an instance of the Person model, say person, you can simply do:

給定一個Person模型的實例,例如Person,您可以簡單地做:

 children = person.children.all() # children being the related_name of the recursive field

#7


0  

I'm also going to write in QuerySet since this will allow you to chain them. And I will provide answer for both retrieval of all children and all parents.

我也要用QuerySet來寫,因為這會讓你把它們鏈起來。我將為所有孩子和所有父母的恢復提供答案。

class PersonQuerySet(QuerySet):
    def descendants(self, person):
        q = Q(pk=person.pk)
        for child in person.children.all():
            q |= Q(pk__in=self.descendants(child))
        return self.filter(q)

    def ancestors(self, person):
        q = Q(pk=person.pk)
        if person.parent:
            q |= Q(pk__in=self.ancestors(person.parent))
        return self.filter(q)

Now we need to set PersonQuerySet as the manager.

現在我們需要將PersonQuerySet設置為manager。

class Person(TimeStampedModel):
    name = models.CharField(max_length=32)
    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')

    people = PersonQuerySet.as_manager()

So here's the final query.

這是最后一個查詢。

albert_einstein = Person.people.get(name='Albert Einstein')
bernhard_einstein = Person.peole.get(name='Bernhard Caesar Einstein')
einstein_folks = Person.people.descendants(albert_einstein).ancestors(bernhard_einstein)

Note: The following solutions is as slow as as the rest of the other answers earlier. I have inspected the Database hit everytime it recurse to its child/parent. (If anyone can improve further with some optimization & caching, this would be better, perhaps prefetch relevant data before querying). In the meanwhile, mptt is more practical.

注意:下面的解決方案和前面其他答案一樣慢。每次數據庫遞歸到它的子/父時,我都會檢查它的命中率。(如果有人可以通過一些優化和緩存進一步改進,這將會更好,也許在查詢之前預取相關數據)。同時,mptt更加實用。


注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:https://www.itdaan.com/blog/2011/01/18/7212e29ac341610c6ec5800fd350382e.html



 
粤ICP备14056181号  © 2014-2021 ITdaan.com