english django model recursive-query

english - Django consulta de filtro de clave extranjera auto-recursiva para todos los hijos



django override save (7)

Debes leer acerca del recorrido del árbol de preorden modificado. Aquí está la implementación de django. https://github.com/django-mptt/django-mptt/

Tengo este modelo con una relación de clave externa auto referenciada:

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

Ahora quiero conseguir todos los niños de niveles múltiples para una persona. ¿Cómo escribo una consulta de Django para ello? Necesita comportarse como función recursiva.


La sugerencia de sunn0 es una gran idea, pero get_all_children () devuelve resultados extraños. Devuelve algo como [Person1, [Person3, Person4], []]. Debería ser cambiado para gustar a continuación.

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


Sé que esto es viejo, pero alguien podría ser ayudado.

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

y luego simplemente haga de esto una property (O una property cached_property si eso funciona para usted) en el modelo para que pueda cached_property en cualquier instancia.


Si conoces la profundidad máxima de tu árbol, puedes probar algo como esto (sin probar):

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


Siempre puedes agregar una función recursiva a tu modelo:

EDITAR: Corregido de acuerdo a 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

(No uses esto si tienes mucha recursión o datos ...)

Sigo recomendando mptt como lo sugiere errx.


También voy a escribir en QuerySet ya que esto te permitirá encadenarlos. Y proporcionaré respuesta para la recuperación de todos los niños y todos los padres.

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)

Ahora necesitamos configurar PersonQuerySet como el administrador.

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

Así que aquí está la consulta final.

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: Las siguientes soluciones son tan lentas como el resto de las otras respuestas anteriores. He inspeccionado el acierto de la base de datos cada vez que responde a su hijo / padre. (Si alguien puede mejorar aún más con un poco de optimización y almacenamiento en caché, esto sería mejor, tal vez obtener previamente los datos relevantes antes de consultar). Mientras tanto, mptt es más práctico.


Tenía un problema de negocios muy similar, en el que, dado un miembro del equipo, se suponía que debía averiguar el equipo completo debajo de él. Pero tener una gran cantidad de empleados hizo que la solución recursiva fuera muy ineficiente y también mi API estaba recibiendo errores de tiempo de espera del servidor.

La solución aceptada toma el nodo, va a su primer hijo y va hasta el fondo de la jerarquía. Luego vuelve a subir al segundo hijo (si existe) y luego vuelve a bajar hasta el final. En resumen, explora todos los nodos uno por uno y agrega todos los miembros en una matriz. Esto conduce a una gran cantidad de llamadas de db y debe evitarse si hay una gran cantidad de nodos para explorar. La solución que se me ocurrió, obtiene los nodos por capas. El número de llamadas db es igual al número de capas. Eche un vistazo a este enlace SO para la solución.