Does model.related_name.all() perform a new query to DB?

Hello!

I have two models:

# models.py

class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(unique=True)


class Service(models.Model):
    name = models.CharField(max_length=150, unique=True)
    slug = models.SlugField(unique=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    
    def get_absolute_url(self):
        return reverse('service-detail', args = [self.slug])

And two function based views that get Querysets of Categories and Services from the database.
(these querysets are needed for some reusable blocks on the page):

# views.py

def index(request):
    categories = Category.objects.all()
    services = Service.objects.all()
    return render(
        request,
        'app/index.html',
        {'categories': categories,
         'services': services}
    )


def service_detail(request, slug):
    categories = Category.objects.all()
    services = Service.objects.all()
    service = get_object_or_404(Service, slug=slug)
    return render(
        request,
        'app/service-detail.html',
        {'categories': categories,
         'services': services,
         'service': service}
    )

And I have this template of a sidebar.html, where I need to display a list of categories and services related to each category.
This is what I’ve done so far to get this working:

# sidebar.html

    <ul>
      {% for cat in categories %}
      <li>
        {{ cat.name }}
        <ul>
          {% for service in cat.service_set.all %}
          <li>
            <a href="{{ service.get_absolute_url }}">{{ service.name }}</a>
          </li>
          {% endfor %}
        </ul>
      </li>
      {% endfor %}
    </ul>

And the question is this:
what does this line of code do?

{% for service in cat.service_set.all %}

Does it mean that a new database query will be performed to retrieve services related to the category?
Or will Django somehow use querysets fetched by the view function to get services related to the category?

Yes, this has created an N+1 query situation. Django will perform another query for each cat.

You’ll want to use the prefetch_related clause in your Category.objects.all() query. It exists for exactly this purpose.

(Side note: This also means that your Service.objects.all() query isn’t doing you any good - you’re not using that data within the template at all, and you can remove that query.)

Yes, this has created an N+1 query situation. Django will perform another query for each cat.

Thank you! I will keep that in mind.

The prefetch_related() method is a little difficult for me to understand.
So, just to make sure I get it right: is this the right way to use prefetch_related?

# models.py (set related_name on a foreign key field)

class Service(models.Model):
    ...
    category = models.ForeignKey(
        Category,
        on_delete=models.CASCADE,
        related_name='service')

# views.py

def service_detail(request, slug):
    categories = Category.objects.prefetch_related('service')
    services = Service.objects.all()
    service = get_object_or_404(Service, slug=slug)

# sidebar.html

<ul>
      {% for cat in categories %}
      <li>
        {{ cat.name }}
        <ul>
          {% for service in cat.service.all %}
          <li>
            <a href="{{ service.get_absolute_url }}">
              {{ service.name }}
            </a>
          </li>
          {% endfor %}
        </ul>
      </li>
      {% endfor %}
    </ul>

That queryset is here for another purpose. This data is used in the footer block to generate a list of links to service pages.

Using prefetch_related doesn’t affect how you use the related data. Your template doesn’t change. The only change needing to be made is adding the prefetch_related clause to the query.

Note: I notice the change made by adding the related_name clause in the ForeignKey field. I would suggest you make that plural (“services”) to more accurately represent the relationship. I’d also suggest the name being changed to avoid confusion with a lower-case reference to the table name.

Using prefetch_related doesn’t affect how you use the related data. Your template doesn’t change.

Actually you are quite right. I’ve just realized that my template didn’t change at all (except for the related name on this line cat.service.all)

I would suggest you make that plural (“services”) to more accurately represent the relationship.

So should I change related_name='service' to related_name='services'?

It’s an opinion-based recommendation, but yes. There’s no requirement for you to do that, I just think that it logically makes more sense. (cat.services implies more than 1 service for that cat, where cat.service implies 1 service.)

Okay, I will follow this recommendation.
And once again thank you very much. Your comments are realy helpful!!