2011-01-18 16 views
15

ho questo modello con un auto riferimento relazione chiave esterna:Django query di filtro ForeignKey auto-ricorsiva per tutti Childs

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 posso scrivere una query su Django? Deve comportarsi come una funzione ricorsiva.

risposta

24

si può sempre aggiungere una funzione ricorsiva al 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 consigliare mptt come suggerito da errx.

+0

Dovresti essere get_all_children() come chiamata di funzione e devi usare r = r + c.get_all_children() o finirai con elenchi annidati. Non mi sembra di avere il diritto di modificarlo da solo – alan

+0

Codice aggiornato in base al commento. Se tutti avessero il permesso di modificare tutti i post avremmo un grosso problema :) – sunn0

+1

Penso che questo dovrebbe avere anche include_self = True nella chiamata della funzione interna. Altrimenti, ad ogni ricorsione scendiamo di un altro livello, senza mai aggiungere nulla a 'r'. Ha funzionato correttamente solo per me dopo aver apportato questa modifica. – andy

12

Si consiglia di leggere l'attraversamento dell'albero preordinato modificato. Ecco l'implementazione di django. https://github.com/django-mptt/django-mptt/

+1

è necessario utilizzare una soluzione di terze parti? c'è qualche altro modo ?? – Ahsan

+2

@Ashan: puoi sempre leggerlo ad es. qui: http://dev.mysql.com/tech-resources/articles/hierarchical-data.html e scrivi tu stesso il codice –

5

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

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

di sunn0 è grande idea, ma get_all_children() restituisce risultati strani. Restituisce qualcosa come [Person1, [Person3, Person4], []]. Dovrebbe essere cambiato per piacere qui sotto.

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 
0

ho avuto una molto simile business problem, in cui dato un membro del team, dovevo scoprire il team completo sotto sotto di lui. Ma avere un grande numero di dipendenti ha reso la soluzione ricorsiva molto inefficiente e anche la mia API stava ricevendo errori di time-out dal server.

La soluzione accettata prende il nodo, passa al primo figlio e va in profondità fino al fondo della gerarchia. Quindi 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 molte chiamate db e dovrebbe essere evitato se c'è un numero enorme di nodi da esplorare. La soluzione che ho trovato, recupera i nodi a livello di layer. Il numero di chiamate db è uguale al numero di livelli. Dai un'occhiata a questo SO link per la soluzione.

0

So che questo è vecchio, ma qualcuno potrebbe semplicemente essere aiutato.

Dato un istanza del modello Person, dire person, si può semplicemente fare:

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