Domanda

Ho questo modello con un auto riferimento relazione chiave esterna:

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

Ora voglio ottenere tutti i bambini a più livelli per una persona. Come faccio a scrivere una query Django per esso? Ha bisogno di comportarsi come funzione ricorsiva.

È stato utile?

Soluzione

È sempre possibile aggiungere una funzione ricorsiva per il tuo modello:

EDIT: corretto in base alle 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

(non usare questo se avete un sacco di ricorsione o dati ...)

Ancora raccomandando MPTT come suggerito da ERRx.

Altri suggerimenti

Si dovrebbe leggere su Modified albero preordine attraversamento. Ecco django implementazione. https://github.com/django-mptt/django-mptt/

suggerimento

di sunn0 è grande idea, ma get_all_children () restituisce risultati strani. Esso restituisce qualcosa di simile [Person1, [persona3, Person4], []]. Esso dovrebbe essere modificato a piacere in basso.

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

Se si conosce la profondità massima del vostro albero, si potrebbe provare qualcosa di simile (non testata):

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

So che questo è vecchio, ma qualcuno potrebbe anche ottenere aiuto.

     def get_all_children(self, container=None):
         if container is None:
             container = []
         result = container
         for child in self.children.all():
             result.append(child)
             if child.children.count() > 0:
                 child.get_all_children(result)
         return result

e poi semplicemente rendere questo un property (OR un cached_property se che funziona per voi) sul modello in modo che possa essere chiamato su qualsiasi istanza.

Ho avuto un molto simile problema di business , in cui dato un membro del team, avrei dovuto scoprire la squadra completa sotto sotto di lui. Ma avere un gran numero di dipendenti ha reso la soluzione ricorsiva molto inefficiente e anche il mio API stava diventando errori di timeout da server.

La soluzione accettata prende il nodo, va ad esso del primo figlio e va in profondità verso il basso fino basso della gerarchia. Poi torna di nuovo al secondo figlio (se esiste), e poi di nuovo scende fino in fondo. In breve, esplora tutti i nodi uno per uno e aggiunge tutti i membri di un array. Questo porta a un sacco di chiamate db e dovrebbero essere evitati se c'è un enorme numero di nodi da esplorare. La soluzione mi è venuta, recupera i nodi strato-saggio. Il numero di chiamate db è uguale al numero di strati. Date un'occhiata a questo SO link per la soluzione.

Ho anche intenzione di scrivere in QuerySet dal momento che questo vi permetterà di catena di loro. E mi fornirà risposta sia per il recupero di tutti i bambini e tutti i genitori.

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)

Ora abbiamo bisogno di PersonQuerySet insieme come gestore.

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

    people = PersonQuerySet.as_manager()

Quindi, ecco la domanda finale.

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)

Nota: Le seguenti soluzioni è lento come come il resto delle altre risposte precedenti. Ho ispezionato il colpo ogni database è ricorsione al suo figlio / genitore. (Se qualcuno in grado di migliorare ulteriormente con qualche ottimizzazione e la memorizzazione nella cache, questo sarebbe meglio, forse, prefetch i dati pertinenti prima interrogazione). Nel frattempo, MPTT è più pratica.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top