Parent self ForeignKey Childs

Trying to render in html parent categories and under each parent to render it’s childs,
the result i get parents right but it repeats only childs for one parent under all of parents.
i guess the problem in the context because i get it in for loop,
but when i move it out of loop it cannot see childs.
how can i get childs and also parents in same context.

View:

def categories(request, parent_category=None):
    parent_categories_list = Category.objects.filter(parent=None)
    for parent_category in parent_categories_list:
        child_categories_list = Category.objects.filter(parent=parent_category)
        return {
            'parent_categories_list': parent_categories_list,
            'child_categories_list': child_categories_list,
        }

HTML:

{% for parent_category in parent_categories_list %}
<div class="column">
    <h4 class="ui header">
        <a href="{{ parent_category.get_absolute_url }}">
             {{ parent_category.name }}
         </a>
    </h4>
    <div class="ui link list">
    {% for child_category in child_categories_list %}
         <a class="item" href="{{ child_category.get_absolute_url }}">
              {{ child_category.name }}
         </a>
     {% endfor %}
     <div>
</div>
{% endfor %}

This loop is only going to execute 1 time. Your return statement is inside the loop, and so for the first time through the loop, it’s going to return that dict and exit the loop.

It appears from your template that what you want is to render the top level Category objects along with the objects that directly refer to it. You don’t need a separate query.

What you’re really trying to do here is follow the foreign key relationship backwards. You have a Category that has a set of objects related to it by a foreign key. You want to access that set of related objects.

See the docs for Following relationships backward for how to do that.

1 Like

great thanks ken but how about the return if i need to return both of parent_categories_list and child_categories_list how to pass them into one context, Or should i have to make it in two functions.
i tried to indent return statement under the For loop but it there is no parent_categories_list in that case.

is it possible to pass both in same context.
my last function:

def categories(request):
    parent_categories_list = Category.objects.filter(parent=None)

    for parent_category in parent_categories_list:
        child_categories_list = parent_category.category_set.filter(parent=parent_category)

    context = {
        'parent_categories_list': parent_categories_list,
        'child_categories_list': child_categories_list,
    }

    return context

You’re kinda missing the point of my previous post.

You don’t need to write a separate query to retrieve the child categories.

Using the parent category, and taking advantage of the related object manager, you can retrieve the child categories directly from the parent.

Side note: There is no one “child category list” - there is a “child category list” for each parent.

1 Like

thank you my friend, i understand what you mean now and solution is down there for the record, may anyone get use of solution.

HTML:

{% for parent_category in parent_categories_list %}
    <div class="column">
        <h4 class="ui header">
            <a href="{{ parent_category.get_absolute_url }}">
                {{ parent_category.name }}
            </a>
        </h4>
        <div class="ui link list">
            {% for child_category in parent_category.category_set.all %}
            <a class="item" href="{{ child_category.get_absolute_url }}">
                {{ child_category.name }}
            </a>
            {% endfor %}
        </div>
    </div>
{% endfor %}

View:

def categories(request):
    parent_categories_list = Category.objects.filter(parent=None)

    context = {
        'parent_categories_list': parent_categories_list,
    }

    return context

Cool! Now having said that, I will point out a performance enhancement available to you.

If you were to follow this being run, you would see that your view is executing a query for your parent_categories_list plus a query for each parent category to retrieve the related child category list.

Django provides a function named prefetch_related, that retrieves all of the child categories once and then associates each with their respective parent.

So in your case, it would probably look something like this:
parent_categories_list = Category.objects.filter(parent=None).prefetch_related('category')

You would then see that this view only executes two queries for the page instead of “N + 1”.

2 Likes