Representar un árbol de objetos en la plantilla de Django.

Tengo un modelo Django que tiene una ForeignKey para la misma clase, haciendo un árbol efectivamente:

class Tag(models.Model): name = models.CharField(max_length=50) parent = models.ForeignKey('self', blank=True, null=True) 

Jugando con un recursivo en el shell de Django ( ./manage.py shell ), puedo representar fácilmente el árbol como texto simple:

 def nodes(parent, level): children = Tag.objects.filter(parent=parent) for c in children: spaces = "" for i in xrange(0,level): spaces+=" " print "%s%s" % (spaces,c.name) nodes(c.pk,level+1) nodes(None,0) 

De lo que no estoy seguro es de cómo obtener todo el árbol en una plantilla de Django. He creado una etiqueta de plantilla personalizada para hacer esto más fácil, pero no puedo averiguar cómo pasar los datos a la plantilla para iterar fácilmente sobre el árbol para mostrarlos en una plantilla. Aquí está la etiqueta de la plantilla básica.

 @register.inclusion_tag("core/tags.html") def render_tags(**kwargs): tags = Tag.objects.all() return {"tags":tags} 

Sé que lo anterior es muy básico, pero no estoy seguro de a dónde ir desde aquí. Pensé que podría ser más fácil si la clase Tag tuviera una función para obtener sus hijos, así que también tengo en la clase:

  def children(self): return Tag.objects.filter(parent=self.pk) 

Yo uso self.pk allí, entonces la raíz del árbol es simplemente rootTag=Tag() , ya que no tiene pk ya que no está guardado, rootTag.children() encontrará cualquier etiqueta que no tenga una etiqueta principal, y cualquiera de estas tags puede continuar con la función de children() . Pero como dije, no sé cómo convertir esto en una estructura de datos única de algún tipo para pasar a mi plantilla.

¿Pensamientos? Creo que probablemente quiera construir una especie de dictado, simplemente no puedo seguir adelante aquí.

Como lo mencionó Jproffitt, Django MPTT es una buena manera de lograr lo que quieres.

Usándolo, puede acceder a la instancia de los niños, recursivamente, en su plantilla, como esto:

 {% load mptt_tags %} 

Tags

    {% recursetree tags %}
  • {{ node.name }} {% if not node.is_leaf_node %}
      {{ children }}
    {% endif %}
  • {% endrecursetree %}

Lo tengo para uno de mis proyectos y es fácil de configurar y usar.

Solución alternativa sin una aplicación de terceros

Si no desea utilizar una aplicación dedicada, creo que esta solución podría funcionar (sin probar):

 # yourapp/tags_list.html 
    {% for tag in tags %}
  • {{ tag.name }}
  • {% if tag.children.exists %} {% with tag.children.all as tags %} {% include "yourapp/tags_list.html" %} {% endwith %} {% endif %} {% endfor %}

De esta manera, la plantilla debería llamarse a sí mismo recursivamente hasta que no haya más tags de niños. Esta solución necesita que especifique un nombre relacionado en su modelo de etiqueta:

 parent = models.ForeignKey('self', blank=True, null=True, related_name="children") 

Sin embargo, tenga cuidado, esta solución implicará más consultas de base de datos que el uso de MPTT.

Cuanto más busqué, más me di cuenta de que tratar de hacer esto con la recursión de la plantilla no es el camino a seguir, pero todavía me gustaría evitar el django-mptt por ahora porque parece demasiado para lo que siento que es un Un problema simple, y no planeo tener árboles muy grandes.

Una respuesta a esta pregunta relacionada señaló que la recursión de la plantilla realmente no debería usarse y, en cambio, manejar la serialización en la vista. Pensé que esto sería mejor como mencioné en la pregunta original, asumiendo que lo más probable es que fuera un dictado.

Esta respuesta es básicamente lo que estaba buscando. El objeto serializable_object es algo que puedo pasar a una plantilla, y si decido que quiero seguir con json más tarde, es bastante fácil de hacer como se indica en esa respuesta.

Voy a seleccionar la respuesta de Eliot como correcta ya que django-mptt es probablemente la cosa “más correcta” que se debe hacer aquí, pero esta es una solución alternativa para cualquiera que busque evitar otra aplicación de terceros.

 class Tag(models.Model): name = models.CharField(max_length=50) description = models.CharField(max_length=100, blank=True) parent = models.ForeignKey('self', blank=True, null=True) # Not necessary, can use Tag.tag_set.all() # But this function uses pk which allows # rootTag=Tag() # rootTag.children() # Because rootTag has no pk def children(self): return Tag.objects.filter(parent=self.pk) def serializable_object(self): obj = {'name': self.name, 'children': []} for child in self.children(): obj['children'].append(child.serializable_object()) return obj 

Con ese modelo, puedes hacer:

 rootTag=Tag() rootTag.serializable_object() 

O:

 tags = [] for t in Tag.objects.filter(parent=None): tags.append(t.serializable_object()) # And if you need JSON, from django.utils import simplejson simplejson.dumps(tags)