Django Subquery returns only the first element - django

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

Related

Django query on multiple related objects

I'm having trouble doing a query spanning between a lot of models.
This is the query I do to display all animals and their corresponding vaccines with encounter.status 'in-progress' and the date immunization date is in the futur.
def current_with_futur_vaccines(self):
return (
Encounter.objects.filter(
status="in-progress").filter(
subject__immunizations__recorded__gte=datetime.now(),
)
.select_related("subject")
.prefetch_related("subject__immunizations", "location")
)
The things is when I want to list the immunizations from the query I get all the immunizations for this animal and not only the immunizations that have to take place in the futur.
{% for immunization in object.subject.immunizations.all %}
{{ immunization }}
{% endfor %}
This is the model
class Animal(models.Model):
name = models.CharField(max_length=250)
class Encounter(models.Model):
subject = models.ForeignKey(Animal, on_delete=models.PROTECT)
status = models.CharField(max_length=11)
class Vaccine(models.Model):
name = models.CharField(max_length=250)
class Immunization(models.Model):
subject = models.ForeignKey(
Animal, on_delete=models.PROTECT, related_name="immunizations"
)
recorded = models.DateTimeField(default=timezone.now)
vaccine = models.ForeignKey(Vaccine, on_delete=models.PROTECT)
EDIT
When I call all() I don't expect to have all the results related to the FK. What I want is the results filtered by the query. In my example I have all the encounters filtered with recorded date in the futur but when I can the related Imunization entries with .all() I don't get the filtered Immunization results filtered by recorded but all of them.
EDIT 2:
I think I have figured it out by using a filtered prefetch. Something like this.
def current_with_futur_vaccines(self):
immunizations_prefetch = models.Prefetch("subject__immunizations", Immunizations.objects.filter(recorded__gte=datetime.now())
return (
Encounter.objects.filter(
status="in-progress").filter(
subject__immunizations__recorded__gte=datetime.now(),
)
.select_related("subject")
.prefetch_related(immunizations_prefetch, "location")
)
You don't have a Foreign Key/M2M reference to Immunizations in you Animal Model.

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.

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.

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