user2084044
user2084044

Reputation:

Sum all values of database column with Queryset Model.objects.aggregate(Sum(***))

So I'm creating an expense sheet app in Django and I'm trying to get the total sum of all the expense costs, which I then want to display in my template.

My Model:

class Expense(models.Model):
    PAYMENT_CHOICES = (
      ('Cash', 'cash'), 
      ('Credit', 'credit'),
      ('Debit', 'debit')
    )
    date = models.DateField()
    store = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=20, decimal_places=2)
    payment_type = models.CharField(max_length=8, choices=PAYMENT_CHOICES)
    category = models.CharField(max_length=100)

    def __unicode__(self):
      return u'%s' % (self.date)

    def _price_sum(self):
      return self.objects.aggregate(total_price = Sum('price'))

    price_sum = property(_price_sum)

I'm trying to call 'price_sum' in my template with the tag {{ expenses.price_sum }}

my template looks like this

{% if expenses %}
<table class='table table-hover'>
<tr>
  <thead>
    <th>Date</th>
    <th>Store</th>
    <th>Price</th>
    <th>Payment Type</th>
    <th>Category</th>
    <th></th>
    <th></th>
  </thead>
</tr>
 {% for expense in expenses %}
<tr>
  <tbody>
    <td>{{expense.date}}</td>
    <td>{{expense.store}}</td>
    <td>${{expense.price|floatformat:"2"|intcomma}}</td>
    <td>{{expense.payment_type}}</td>
    <td>{{expense.category}}</td>
    <td>
      <form action='{{expense.id}}/' method='get'>
        <input type="submit" value="Edit" class="btn">
      </form></td>
    <td>
      <form action='{{expense.id}}/delete/' method='post'>{% csrf_token %}
        <input type="submit" value="Delete" class="btn btn-danger" data-dismiss="alert">
      </form></td>
  </tbody>
</tr>

{% endfor %}

</table>
<h3>Table: {{ expenses.price_sum }}</h3>
{% else %}
    <p>No expenses logged.</p>
{% endif %}

I'm not certain if I'm using the template tag incorrectly or if my function is wrong or what is going wrong. I know this is probably really simple but I've searched all over StackOverflow and the Django and Python docs and anything I could find and I just can't figure it out.

UPDATE: views.py

from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render
from expenses.models import Expense
from expenses.form import ExpenseForm

def index(request,template='expenses/index.html'):
  all_expenses = Expense.objects.all()
  return render(request, template, {'all_expenses': all_expenses})

def new(request, template='expenses/new.html'):
  if request.method == 'POST':
    new_expense = ExpenseForm(request.POST)
    if new_expense.is_valid() and new_expense.clean():
      new_expense.save()
      return HttpResponseRedirect('/expenses/')
  else:
    new_expense = ExpenseForm()

  return render(request, template, {'new_expense':new_expense})

def edit(request, expense_id, template='expenses/edit.html'):
  expense = Expense.objects.get(id=expense_id)
  if request.method == 'POST':
    form = ExpenseForm(request.post, instance=expense)
    if form.is_valid and form.clean():
      expense = form.save()
      return HttpResponseRedirect('/expenses/')
    else:
      expense = ExpenseForm(instance=expense)

  return render(request, template, {'expense':expense})

def delete(request, expense_id):
  if request.method == 'POST':
    expense = Expense.objects.get(id=expense_id).delete()
    return HttpResponseRedirect('/expenses/')
  else:
    return HttpResponse(status=404)

Upvotes: 2

Views: 1736

Answers (2)

Daniel Roseman
Daniel Roseman

Reputation: 599530

You don't say which of those views is the one you want to use the aggregate in (and I wonder why you've posted all of them). However, what's clear is that you never pass an Expense object in any of them, and since price_sum is an instance method on Expense, you will never have access to it.

But actually you don't want to do that. price_sum should not be an instance method, as it never interacts with the instance at all. In fact, if you tried to run the method from the shell, you would see this error:

AttributeError: Manager isn't accessible via Expense instances

which should be self-explanatory.

Instead of doing this, you should define a custom manager and define a price_sum method there. Then you can acccess Expenses.objects.price_sum(), which you can pass directly to your template in the context.

Upvotes: 0

falsetru
falsetru

Reputation: 369044

aggregate return dictionary that looks like {'total_price': Decimal('1234')}.

So, change _price_sum as follow:

def _price_sum(self):
  return self.objects.aggregate(total_price=Sum('price'))['total_price']

Or, change the template as follow:

{{ expenses.price_sum.price_sum }}

Upvotes: 2

Related Questions