user1272534
user1272534

Reputation: 991

Django queryset efficiency

I have an Item object which has a ForeignKey to a Receipt object.

class Receipt(models.Model):
    ...

class Item(models.Model):
    receipt = models.ForeignKey(Receipt)
    name = models.CharField(max_length=255)
    cost = models.DecimalField(default=0, max_digits=6, decimal_places=3)

    ...

I display the receipts using a generic view.

class ReceiptView(DetailView):
    model = Receipt

And then on the template, I want to show all items on the receipt.

<ul>
{% for item in object.item_set.all %}
    <li>{{ item.name }}</li>
{% endfor %}
</ul>

This works fine for smaller data sets. One of the receipt objects has 1600 items related to it. Loading the page for that receipt is incredibly slow. Using Django Debug Toolbar I notice that Django is executing 1 query per item.

If I alter the list item in the template so that rather than displaying a property of the item, I just display the item itself, Django just executes a single query.

<ul>
{% for item in object.item_set.all %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

Unfortunately I need to display about 10 of the item's properties in the template. Is there someway I can tell Django to execute a single query to get all items and all of their properties?

Upvotes: 0

Views: 1167

Answers (1)

dursk
dursk

Reputation: 4445

Use prefetch_related. Since you are doing a reverse foreign key lookup, you're essentially working with a ManyToMany relationship. prefetch_related will do a separate query under the hood, to retrieve and then cache all of the Receipt objects for you.

Since you're using generic views, rather than specifying a model, you can specify a queryset.

class ReceiptView(DetailView):
    queryset = Receipt.objects.prefetch_related('related_items')

In order to do this, you'll have to specify a related_name in your Item model:

class Item(models.Model):
    receipt = models.ForeignKey(Receipt, related_name='related_items')

Upvotes: 1

Related Questions