Reputation: 9826
I'm using Django 2.0
and Django REST Framework
I have a model like below.
class Contact(models.Model):
first_name = models.CharField(max_length=100)
class AmountGiven(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
@property
def total_payable(self):
return self.amount
@property
def amount_due(self):
returned_amount = 0
for returned in self.amountreturned_set.all():
returned_amount += returned.amount
return self.total_payable - returned_amount
class AmountReturned(models.Model):
amount_given = models.ForeignKey(AmountGiven, on_delete=models.CASCADE)
amount = models.FloadField()
I have to get the top 10 contacts of the amount given
and due
respectively.
In my view, I'm filtering data like
@api_view(http_method_names=['GET'])
def top_ten(request):
filter_type = request.query_params.get('type', None)
if filter_type == 'due':
# query for due type
elif filter_type == 'given':
qs = Contact.objects.filter(
user=request.user
).values('id').annotate(
amount_given=Sum('amountgiven__amount')
).order_by(
'-amount_given'
)[:10]
graph_data = []
for q in qs:
d = {}
contact = Contact.objects.get(pk=q['id'])
d['contact'] = contact.full_name if contact else 'Unknown'
d['value'] = q['amount_given']
graph_data.append(d)
return Response(graph_data)
else:
raise NotFound('No data found for given filter type')
the type
query can be due or given.
The code for given
type is working fine as all fields are in the database. But how can I filter based on the virtual field for due
type?
What I have to do is to annotate
Sum of amount_due
property group by contact
.
Upvotes: 3
Views: 14548
Reputation: 543
You cannot filter based on @property
.
As far as I understand your problem correctly you can aggregate sum of related AmountGiven
and sum of AmountReturned
, then calculate due
field which keep result of subtracting letter and former.
The query:
from django.db.models import Sum, Value
from django.db.models.functions import Coalesce
Contact.objects.filter(
amountgiven__amount__gt=0
).annotate(
due=Sum('amountgiven__amount') - Coalesce(Sum('amountgiven__amountreturned__amount'), Value(0))
).order_by('-due').values_list('due', 'id')
will return:
<QuerySet [{'id': 3, 'due': 2500.0}, {'id': 1, 'due': 2450.0}, {'id': 2, 'due': 1500.0}]>
However with this solution you cannot distinct between many AmountGiven
across one Contact
. You get big picture like results.
If you want split due value per AmountGiven
instance the just annotate like so:
AmountGiven.objects.annotate(
due=Sum('amount') - Coalesce(Sum('amountreturned__amount'), Value(0))
).order_by('-due').values_list('due', 'contact__id', 'id')
which returns
<QuerySet [
{'contact__id': 3, 'id': 3, 'due': 2500.0},
{'contact__id': 1, 'id': 1, 'due': 1750.0},
{'contact__id': 2, 'id': 2, 'due': 1500.0},
{'contact__id': 1, 'id': 4, 'due': 350.0},
{'contact__id': 1, 'id': 5, 'due': 350.0}
]>
References
Upvotes: 5