Django/ How get sum of order - 'int' object is not iterable - django

I'm trying to withdraw the order amount using the method from the model, but I can't. Have error - 'int' object is not iterable
models.py
class OrderItem(models.Model):
order = models.ForeignKey(Order, on_delete=models.PROTECT)
product = models.ForeignKey(Product, on_delete=models.PROTECT)
price = models.IntegerField()
quantity = models.PositiveIntegerField(default=1)
def get_total_price(self):
return sum(self.get_price())
def get_price(self):
return self.price * self.quantity
def __str__(self):
return str(self.order)
get_price method it's work, but in get_total_price have error, what I'am doing wrong?
order_success.html
{% for item in order_item %}
<tr>
<td>{{item.quantity}}</td>
<td>{{item.price}}</td>
<td>{{item.get_price}}</td>
</tr>
{% endfor %}
<h2>To pay - {{order_item.get_total_price}}</h2> # doesn't work
can you help me write right the sum method, please. I think my method is wrong.

Can you help me write right the sum method, please. I think my method is wrong.
I think your modeling is wrong. An OrderItem has no total_price, it has a certain price that is the quantity times the unit price, but the total price is an attribute of the entire queryset.
We can however calculate such aggregates, by using a queryset in the view for that, like:
from django.db.models import F, Sum
def some_view(request):
order_items = OrderItems.object.all() # might be a different query
aggrs = order_items.aggregate(
total_price=Sum(F('quantity') * F('price'))
)
context = {
'order_items': order_items,
'aggrs': aggrs
}
return render(request, 'some_template.html', context)
In the template, we can then render it like:
{% for item in order_items %}
<tr>
<td>{{item.quantity}}</td>
<td>{{item.price}}</td>
<td>{{item.get_price}}</td>
</tr>
{% endfor %}
<h2>To pay - {{ aggr.total_price }}</h2>
Note: since your order_item is a collection, it makes more sense to use a plular (like order_items here in this sample view).
Note: it makes sense to define this on the Order model, for example:
class Order(models.Model):
# ...
#property
def total_price():
return self.order_item_set.aggregate(
total_price=Sum(F('quantity') * F('price'))
)['total_price']
then we can thus write some_order.total_price to obtain the total price of that Order object.
Note: it is a bit confusing that you have a price field and a get_price method, perhaps you should consider renaming price to
unit_price, and then make get_price a #property named price.

Related

How to get total sum based on the date__year?

Here in the template I am iterating MyModel objects. Now in the template I want to get the total sum of integer fields for the particular year.
I don't know how I can I use these three functions in order to get the total sum of field? Or there might be any better approach ?
model
class MyModel(models.Model):
date = models.DateField()
field1 = models.DecimalField(default=0.0)
field2 = models.DecimalField(default=0.0)
views
def get_total_field1(year):
return MyModel.objects.filter(date__year=year).annotate(total=Sum('field1'))['total']
def get_total_field2(year):
return MyModel.objects.filter(date__year=year).annotate(total=Sum('field2'))['total']
def get_total(year):
return get_total_field1() + get_total_field2()
class MyView(ListView):
model = MyModel
template_name = 'my_template.html'
template
{% for obj in objs %}
<tr>
<td>{{obj.date|date:'Y'}}</td>
<td>Field 1 total sum for obj.date </td>
<td>Field 2 total sum for obj.date</td>
<td>Field 1 total + field2 total </td>
{% endfor %}
Templates are for displaying data. Calculations are more like business logic that should go to Python code. So my advice - calculate everything you need in the code (for "sum" see .annotate() Django method) and then just display pre-calculated variables in your templates.
In your example I would move "get_total_field1" and other methods into the model itself as property, then you can just do
obj.get_total_field1 in your template.

Django - What is the most efficient way to query the average ratings for a list of objects?

I have a simple Product and Rating class like so:
class Product(models.Model):
name = ...
price = ...
class Rating(models.Model):
product = models.ForeignKey("Product", ...)
score = models.PositiveSmallIntegerField(...)
Now I want to get a list all products and the average rating for each product. Simply getting all products is easy:
product_list = Product.objects.all()
If I have a single product and I want to get the average rating for that single product:
product = get_object_or_404(Product, pk=product_id)
ratings = Rating.objects.filter(product=product)
product_avg_rating = ratings.aggregate((Avg("score")))
But let's assume I want to list all products and their average rating. This sounds like it's quite resource/compute heavy. So what is the best way to solve this? Should I make a class method for the Product class that just returns the average, so I can then call this in the template:
{{ product.get_average_rating }}
Or is there a better way to do this? I'm not quite sure what to do here and need some guidance.
Thank you so much in advance!
EDIT: I think I wasn't clear enough with my explanation, so here's an actual example. Let's say I have a query set, filtered by the slug (which is just a searchterm):
products = Product.objects.filter(tags__name__in=[slug])
Now how can I add the average rating to EACH SINGLE product so that I can do this in the template:
{% for product in products %}
{{ product.avg_rating_score }}
{% endfor %}
You can annotate your Product in the first query, so that it will only take one query to obtain the average, like:
product = get_object_or_404(
Product.object.annotate(avg_score=Avg('rating__score')),
pk=product_id
)
This will result in a single Product that contains an extra attribute (only for this specific QuerySet!), in the template, we thus can render it with:
{{ product.avg_score }}
The query that it does is:
SELECT product.*, AVG(rating.score) AS avg_score
FROM product
LEFT OUTER JOIN rating ON product.id = rating.product_id
WHERE product.id = product_id

Django Subquery returns only the first element

This is a simplified version of the models:
class Toy(models.Model):
#generic fields
class Order(models.Model):
customer = models.ForeignKey(Customer)
class OrderItem(models.Model):
order = models.ForeignKey(Order)
toy = models.ForeignKey(Toy)
points = models.PositiveSmallIntegerField(default=3)
A customer can make multiple orders, to it increases the number of points per toy.
This subquery, only returns the first row of OrderItem:
class Customer(models.Model):
def get_toys_with_points():
order_items = OrderItem(toy=models.OuterRef('pk'), order__customer=self)
toys = Toy.objects.annotate(
points_sum = models.Sum(Subquery(order_items.values('points')))
)
return toys
So, when I pull that into my template:
{% for toy in customer.get_toys_with_points %}
{{ toy.points_sum }}
{% endfor %}
I am always getting the value of the first row (even if there are more purchases that would sum up to 25 for example).
You don't need a subquery here.
toys = Toy.objects.filter(orderitem__order__customer=self).annotate(
points_sum=models.Sum('orderitem__points')
)

Prefetch related starting from a single object - geting first in second prefetch and count and order

I have 3 Models Product,Company Categories.
class Product(Meta):
categories = models.ManyToManyField(Category)
company = models.ForeignKey(Company, related_name='products', on_delete=models.CASCADE)
updated_at = models.DateTimeField(auto_now_add=False, auto_now=True)
I need:
to get all the products of a company
show the product first category
count the number products per company and show
order products by reverse updated_at
I start from:
1. Company.objects.get(pk=company_pk).prefetch_related('products')
will give me an error, because get returns an object:
class CompanyProductListView(ListView):
model = Company
template_name_suffix = '_company_list'
def get_queryset(self):
company_pk = self.kwargs.get('pk')
return Company.objects.get(pk=company_pk).prefetch_related('products')
get without prefetch works.
return Company.objects.filter(pk=company_pk).prefetch_related('products')
there is no error, but in template:
{% for company in company_list %}
{{ company.name}}
{% endfor %}
I loop even is one, but doesn't show me anything.
Besides that I need to attach first category to each product, and count the number of products
I'm thinking on access something like this:
{{company.name}}
{% for product in company.products %}
{{ product.name }}
{{ product.category }}
This query will get a little complicated, but should help you solve your issue.
PS: I haven't tested this but should mostly work. Will take a deeper look once I get some more time.
First we get the company we want:
company = Company.objects.get(pk=company_pk)
Then we fetch all the first categories for all products, it can be done by using this question as a guide:
first_categories = Category.objects.order_by('product__id', '-id').distinct('product__id')
Now we use the first_categories to use to limit the amount of data we prefetch (giving this a different perspective, we will query the Product model instead of the Company model)
product_list = Products.objects.filter(company=company).prefetch_related(
Prefetch('categories', queryset=first_categories)
)
def get_queryset():
company_pk = ...
company = ...
first_categories = ...
product_list = ...
return product_list

Output calculations to template

I want to perform row level math on a model and display the results in the template. The database has a row for each company per day.
class MyModel(models.Model):
company = model.CharField(...
daily_target = model.PositiveSmallIntger(...
actual = model.PositiveSmallIntger(...
date = model.DateField(...
In the template I'd want to display the result of 100 * actual / daily_target for the logged-in company. I have no problem doing this in the python interpreter but am confused about how to do this with views & templates.
You could add a property to the model:
class MyModel(models.Model):
company = model.CharField(...
daily_target = model.PositiveSmallIntger(...
actual = model.PositiveSmallIntger(...
...
#property
def pct_target(self):
return 100. * self.actual / self.daily_target
Then in your template:
{% for item in queryset %}
{{ item.pct_target }}
{% endfor %}
The disadvantage of this is that you cannot filter or order the queryset by the property. Another option would be to annotate your queryset with the calculated field.