Reputation: 58
I'm having trouble rendering related objects in a template. I have a Page model, that has a ForeignKey relationship to itself to define parent-child relationships.
class Page(BaseModel):
title = models.CharField(
max_length=280,
blank=False,
null=False,
)
description = models.CharField(
max_length=280,
blank=False,
null=False,
)
slug = models.SlugField(
blank=False,
null=False,
)
is_home = models.BooleanField(
default=False,
)
is_parent = models.BooleanField(
default=False,
)
parent = models.ForeignKey(
'self',
on_delete=models.PROTECT,
default='Home',
blank=True,
null=True,
related_name='children',
)
content = RichTextField(
blank=False,
null=False,
)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('page_detail', args=[str(self.slug)])
My views.py filters out non-parent pages:
class PageListView(ListView):
queryset = Page.objects.filter(is_parent=True, is_home=False)
template_name = 'pages/page_list.html'
context_object_name = 'page'
But when it comes to rendering 'child' objects in my template, I'm stuck. I've figured that I need a loop within a loop (first for parent pages, second for child pages per parent page), but can't get the second loop to work. Here was my latest attempt, which got me a "'Page' object is not iterable" error.
{% extends '_base.html' %}
{% block content %}
<div class="container">
{% for page in page %}
<p>{{ page.title }}</p>
{% for children in page %}
<p>{{ page.title }}</p>
{% endfor %}
{% endfor %}
</div>
{% endblock content %}
Upvotes: 1
Views: 259
Reputation: 477338
You can iterate over the manager constructed by the related_name:
{% block content %}
<div class="container">
{% for page in page %}
<p>{{ page.title }}</p>
{% for child in page.children.all %}
<p>{{ child.title }}</p>
{% endfor %}
{% endfor %}
</div>
{% endblock content %}
You can boost efficiency with a .prefetch_related(…)
[Django-doc] clause:
class PageListView(ListView):
queryset = Page.objects.filter(
is_parent=True, is_home=False
).prefetch_related('children')
template_name = 'pages/page_list.html'
context_object_name = 'page'
I would furthermore advise not to create an extra field is_parent
, since this is a form of data duplication. It turns out that keeping fields in sync, even on the same database, might be harder than what it appears to be at first sight. You can check if the object is a parent with:
class PageListView(ListView):
queryset = Page.objects.filter(
children__isnull=False, is_home=False
).distinct().prefetch_related('children')
template_name = 'pages/page_list.html'
context_object_name = 'page'
Upvotes: 2