Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
564 views
in Technique[技术] by (71.8m points)

Django: show the sum of related Items in a template

I want to get the sum of all values from items related to a certain object. In concrete, I have invoices with one or many items. Each item has a price and I want to calculate the sum of the prices for all the items of one invoice.

These are the essential parts of my models.py:

from django.db import models
from django.db.models import Sum

class Invoice(models.Model):

    @property
    def price(self): 
        return self.item_set.aggregate(Sum('price'))

class Item(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=8, decimal_places=2)

The view.py is simple:

class InvoiceDetailView(DetailView):
    model = Invoice

In my template invoice_detail.html, {{ invoice.price }} delivers something like

{'price__sum': Decimal('282.400000000000')}

So I added get('price__sum') to the price property of the Invoice Model like that:

   return self.item_set.aggregate(Sum('price')).get('price__sum')

The result actually is fine now, i.e. the template shows something like 282.400000000000 (which I still could style a bit), but I wonder, if that's the way to do something like that or if there is a better way?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It is better to work with .annotate(…) [Django-doc] since then you aggregate in the same query. This is actually a lot better in a ListView: if you would use a property in a ListView, this will result in an N+1 problem where you make one query to fetch the Invoices, and n extra queries to obtain the price for each Invoice.

You thus can thus annotate in the view:

from django.db.models import Sum, Value
from django.db.models.functions import Coalesce

class InvoiceDetailView(DetailView):
    model = Invoice
    queryset = Invoice.objects.annotate(
        price_=Coalesce(Sum('item__price'), Value(0))
    )

The Coalesce [Django-doc] is necessary to prevent returning NULL/None in case there are no related items.

then in the view you can again use {{ invoice.price_ }}. We can not use price, since there is already a property for this.

You can however let annotate and the property work together with:

class Invoice(models.Model):
    # …

    @property
    def price(self):
        if hasattr(self, 'price_'):
            return self.price_
        return self.item_set.aggregate(
            price=Coalesce(Sum('price'), Value(0))
        )['price']

Here we thus first look if there is a .price_ attribute, and if there is, we will not make an extra query.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...