How to get total sum based on the date__year? - django

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.

Related

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

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.

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')
)

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.

Django 1.7.7 get object count in template through double relation

I have 3 models, Entry model and Category model, and I have created intermediate model CategoryEntry.
class Entry(models.Model):
entry_text = models.TextField()
class Category(models.Model):
user = models.ForeignKey(User)
category_text = models.CharField(max_length=200)
entries = models.ManyToManyField(Entry, through='CategoryEntry')
class CategoryEntry(models.Model):
category = models.ForeignKey(Category, related_name="related_entry_categories")
entry = models.ForeignKey(Entry)
viewed = models.BooleanField(default=False)
How can I get in template Users total Entry count.
For example I can get total users Category count with
{{ user.category_set.count }}
So I tried many different ways, but don't get how to follow next relation
{{ user.category_set.entries.count}}
{{ user.category_set.categoryentry_set.count}}
{{ user.category_set.all.categoryentry_set.count}}
{{ user.category_set.related_entry_categories.count }}
Is this even possible (good thing to do) to count in template? Or is there better way?
Thanks!
your queries don't make sense because category_set is a collection of objects rather than a single object, so you cannot simply ask for category_set.entries.count
first you have to think about what you want... do you want:
individual count of entries for each category in category_set?
or total count of entries across all categories in category_set?
For the former you need to annotate the queryset. this will have to be done in the view rather than template because the method needs arguments:
from django.db.models import Count
user_categories = user.category_set.annotate(entry_count=Count('entries'))
# then pass the user_categories queryset into your template along with user
you can then iterate over user_categories in the template to display individual counts:
{% for category in user_categories %}
No. of entries: {{ category.entry_count }}
{% endfor %}
For the latter you can use aggregate, again in the view:
from django.db.models import Count
total = user.category_set.aggregate(entry_count=Count('entries'))
# note that aggregate method returns a dict:
print total['entry_count']
# then pass total['entry_count'] as a value into your template along with user