Search code examples
pythondjangoaggregatesubtotal

DJANGO - Subtotal by category and total by item


Good evening !

Let's assume I have a database with :

Category | Item | Price
Cat1 | Item1 | $1
Cat1 | Item2 | $2
Cat2 | Item3 | $3
Cat2 | Item3 | $3
Cat2 | Item4 | $4

I would like to display the total sales by category and below the sales details group by items

Cat1 - Total Price = $3
  Item1 - $1
  Item2 - $2
Cat2 - Total Price = $10
  Item3 - $6 (the sum of the two item3)
  Item4 - $4

I almost succed but I don't know how to have the subtotal by category. My code below :

Model.py

class Category(models.Model):
Category= models.CharField(max_length=100)

class Item(models.Model):
Category= models.ForeignKey(Category, on_delete=models.CASCADE)
Item= models.CharField(max_length=100)

class Order(models.Model):
Category= models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
Item=  models.ForeignKey(Item, on_delete=models.SET_NULL, null=True)
Price=  models.DecimalField()

Views.py

def summary(request):
metrics =  {
'total': Sum('Price'),
}

orders=Order.objects.values('Category','Item').annotate(**metrics))

Template.html

 {% for order in orders%}
       {% ifchanged order.Item%}

       <tr>
           <td>{{ order.Item}} </td>
           <td>{{order.total}} </td>
        </tr>

       {% endifchanged %}
        <tr>
        <td>{{order.Item}}</td>
        <td> {{order.total}}</td>
        </tr>


    {% endfor %}

Solution

  • You can make use of a Prefetch(..) object [Django-doc] to make aggregates per Category and per Item:

    from django.db.models import Prefetch, Sum
    
    def summary(request):
        categories = Category.objects.annotate(
            total=Sum('item__order__Price')
        ).prefetch_related(
            Prefetch(
                'item_set',
                Item.objects.annotate(total=Sum('order__Price')),
                to_attr='items_with_price'
            )
        )
        return render(request, 'template.html', {'categories': categories})

    In the template, one can then render this with:

    {% for category in categories %}
        <tr>
            <td><b>{{ category.Category }}</b></td>
            <td><b>{{ category.total }}</b></td>
        </tr>
        {% for item in category.items_with_price %}
            <tr>
                <td>{{ item.Item }}</td>
                <td>{{ item.total }}</td>
            </tr>
        {% endfor %}
    {% endfor %}