3

i want to return a product names in distinct , and also i've used aggregate to some calculation as well

this is my models.py

class CustomerInvoice(models.Model):
    customer = models.CharField(max_length=50)
    items = models.ManyToManyField(Product,through='ProductSelecte')
    date = models.DateTimeField(auto_now_add=True)

class ProductSelecte(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    products= models.ForeignKey(CustomerInvoice,on_delete=models.CASCADE,related_name='product')
    qnt= models.IntegerField(default=1)
    price = models.IntegerField()
    cash = models.IntegerField()

i want to make table to track customers activity

and this is my query

context['clients'] = ProductSelecte.objects.filter(
    products__customer=self.object.name).values('product').annotate(quantity=Sum(F('qnt'))).order_by('product')

till here works fine but i also need to aggregate price using this

.aggregate(
        total_price=Sum(F('price')))

it raise this error

'dict' object has no attribute 'order_by'

thanks for your helping

UPDATE

for example at in first invoice of john 4th june john bought two pen price:20$ with 3 book price : 30$ total price for this invoice :50$ , and for second invoice 5th july he buy 1 pen price:10$ with 2 copybook price:20$ total price:30 , i need a result like this

client : john , activities :  3 pen(total pens he bought till now),price pens : 30  (total pens price he bought till now) , 3 book(total books he bought till now) ; price:30(total books price he bought till now) ,2 copybook ;price:20 
 and then total price of both invoice : 30+50 = 80
art_cs
  • 683
  • 1
  • 8
  • 17
  • 1
    Why do you want to *aggregate*? If you aggregate, then an `.order_by` does not make much sense, since then you derive the sum of *all* teh prices, so the products are "*gone*". – Willem Van Onsem Jul 05 '20 at 15:38
  • i need to know the total price of a client , but without using order_by , we cant distinct values , as i mentioned in the question , i need to know product names with total quantities for that product , then total price of all products for a specific client ! – art_cs Jul 05 '20 at 15:42
  • 1
    then you need `.annotate(..)` since that will make the *aggregate* per *value*. If you need it per client per product, you can not do that with a `QuerySet`, then you need to do some post-processing in the Python logic. – Willem Van Onsem Jul 05 '20 at 15:47
  • yes i need it to make per client , how to do it ?do you mean for loop inside the query? – art_cs Jul 05 '20 at 15:52

1 Answers1

1

You should make the annotate per client per product and then perform some "grouping" at the Django/Python level:

from django.db.models import F, Sum
from itertools import groupby
from operator import itemgetter

qs = ProductSelecte.objects.values(
    customer=F('products__customer')
    product=F('product')
).annotate(quantity=Sum('qnt')).order_by('customer', 'product')

data = {
    k: list(vs)
    for k, vs in groupby(qs, itemgetter('customer'))
}

Here data is a dictionary that maps the name of the customer to a list of dictionaries that contain the id of the product, the customer name and the quantity.

If you pass it to the template, you can for example render this with:

<ul>
{% for c, vs in data.items %}
    <li>{{ c }}</li>
    <ul>
    {% for v in vs %}
        <li>{{ v.product }}: {{ v.quantity }}</li>
    {% endfor %}
    </ul>
{% endfor %}
</ul>

EDIT: based on the updated request, you should make an annotate and aggregate request:

class ProfileClientDetailView(LoginRequiredMixin,DetailView):
    # …

    def get_object(self):
        return get_object_or_404(Client,pk=self.kwargs['pk'])

    def get_context_data(self,*args,**kwargs):
        data = super().get_context_data(*args,**kwargs)
        data['products'] = ProductSelecte.objects.filter(
            products__customer=self.object.name
        ).values('product').annotate(
            quantity=Sum('qnt'),
            total_price=Sum('price')
        ).order_by('product')
        data['total'] = ProductSelecte.objects.filter(
            products__customer=self.object.name
        ).aggregate(
            quantity=Sum('qnt'),
            total_price=Sum('price')
        )
        return data

Then it can be rendered as:

<ul>
{% for pr in products %}
    <li>{{ pr.product }}:  {{ pr.total_price }} ({{ pr.quantity }} items)</li>
</ul>
TOTAL: {{ total.total_price }} ({{ total.quantity }} items)
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • i much appreciate you , in the template we should use {{data.customer}} {{data.product }} ? – art_cs Jul 05 '20 at 16:03
  • sorry , where should we use the query in DetailView http://dpaste.com/0B3SJ5H – art_cs Jul 05 '20 at 16:32
  • 1
    @art_cs: add it to the context, so `data['data'] = { ... }`. – Willem Van Onsem Jul 05 '20 at 16:34
  • but sorry , i also need to know total quantity , for example , two different product A and B it returns separately for example ccustomer John : product A ;2 customer john product B; 3 , i need to output 5 , and also total price of A and B product – art_cs Jul 05 '20 at 16:42
  • 1
    @art_cs: if you need the total, then you `.aggregate(..)`, but then there is no `.order_by(..)` necessary. it will then return a dictionary with the total quantity (or price if you sum up the prices)... – Willem Van Onsem Jul 05 '20 at 16:44
  • yes i did it before it works , but didnt return product names , this is my previous code style https://stackoverflow.com/questions/62741265/dict-object-has-no-attribute-order-by-django – art_cs Jul 05 '20 at 16:45
  • 1
    @art_cs: but if you make the total sum, then evidently it will not return product names, since then you sum up *all* the records, regardless of the products. How would the product names be sensical in such query? – Willem Van Onsem Jul 05 '20 at 16:47
  • https://stackoverflow.com/questions/62741265/dict-object-has-no-attribute-order-by-django please can you check it – art_cs Jul 05 '20 at 16:47
  • i dont know why {{clients.items.all|join:','}} doesnt work in the old question – art_cs Jul 05 '20 at 16:54
  • 1
    @art_cs: it might help if you edit your question with sample data, and exactly how you want the result to look like. At the moment it looks like you ask conflicting things (like both aggregating, and annotating). – Willem Van Onsem Jul 05 '20 at 16:58
  • i updated , please take a look at the last few lines – art_cs Jul 05 '20 at 17:14
  • i edited to post please , is it possible ? if i make two query i think it will work – art_cs Jul 05 '20 at 17:35
  • 1
    thanks , it should work fine , thanks for giving your time , you always helped me – art_cs Jul 05 '20 at 19:36
  • 1
    one more question what is different if we use F() function in Sum(F()) or dont use? – art_cs Jul 05 '20 at 19:51
  • 1
    @art_cs: nothing. `F` is not necessary, since `Sum` takes a field name as parameter if it is a string (you can use a `Value` if you want to pass a value, so `Sum(F('x'))` is equivalent to `Sum('x')`. – Willem Van Onsem Jul 05 '20 at 19:53