Suppose i have clients in my model (model Client), and suppose each client has a shopping cart (model Cart). Each cart has many items (model CartItems), then to finish, each item has a relation with one product (model Product).
Now, here goes my problem. I want to have the average of all shopping carts, which is based in the sum of all items of each cart, for each client. So i'll try to demonstrate you with more details below.
Try to imagine the directions of the relations like this:
Client->Cart->CartItems<-Product
Simplified description of each model:
Client (
id #pk
)
Cart (
id #pk
client_id #fk that references to Client.id
)
CartItems (
id #pk
cart_id #fk that references to Cart.id
product #fk that references to Product.id
)
ProductId (
id #pk
value # The price of the product
)
In pure SQL i've found the solution, and would be something like this query:
SELECT * FROM Client
INNER JOIN Cart ON Client.id = Cart.client_id
INNER JOIN
(SELECT AVG(c.total) AS average, cart_id FROM
(SELECT SUM(Product.price) AS total, CartItems.cart_id AS cart_id
FROM CartItems
INNER JOIN Product ON CartItems.product = Product.id
GROUP BY CartItems.cart_id) AS c GROUP BY c.cart_id) AS d
ON d.cart_id = Cart.id;
Anyone has any idea about how to convert this query to Django's model's patterns?
You sould do something like:
Cart.objects.values('client_id').annotate(cant=Sum('cart_id__product__value')).aggregate(Avg('cant'))
Notice that the annotations does not return a queryset, you should return the values.
Further reading: Aggregations in Django
My answer comes very late but I came accross this while searching for a solution to a similar issue.
In my case, I have a ManyToManyField relationship between the Cart and the CartItems, which made it fairly simple.
The models now look like this
Cart (
id #pk
client_id #fk that references to Client.id
items #manytomany relationship to CartItems
)
CartItems (
id #pk
product #fk that references to Product.id
)
To get the average for each cart, the query would just look like this
Cart.objects.annotate(carts_avg=Avg('items__product__value'))
Related
I have 2 models, Product and Productdetails with a OneToOne relationship like this:
class Product(IotaModel):
details = models.OneToOneField(
ProductDetails,
null=True,
on_delete=models.SET_NULL
)
I want a queryset that do this:
SELECT *
FROM product_details
WHERE id = (
SELECT details_id
FROM products
WHERE id=<product_id>
)
I tried this:
details_id = Product.objects.filter(pk=product_pk, active=True).only('details')[:1]
return ProductDetails.objects.filter(pk=details_id, active=True)
But it does not work becouse .only('details') gave me the fields (id, details_id) and the next filter takes id instead of details_id.
I also try to add .defer('id') but that does not work.
I know that I can get the details_id using .values_list('details_id') but I think this would involve making 2 queries to the Database.
What could be the best approach to reach the desired SQL query in just 1 hit to the DB?
Thanks in advance!
You should make just the query for the ProductDetails. Something like this I think.
product_details = ProductDetails.objects.get(
active=True, product__id=product_pk, product__active=True
)
The double underscore it's to look on related objects.
I'm trying to make a query in django grouping by a field with choices. I wanna get all choices values, algo choices with no records in the database.
My model:
CHOICES = (
('new', 'New'),
('in_process', 'In process'),
('finished', 'Finished')
)
class Task(models.Model):
...
status = models.CharField(max_length=10, choices=CHOICES)
My current query is:
qs = Task.objects\
.values('status')\
.annotate(total=models.Count('status'))
At this moment, I only have finished task in db, but I wanna get all choices values, with zero if it doesn't have records.
Any idea?
You can do this with:
Task._meta.get_field('status').choices
The choices on a model field are only enforced at the application (Python) level - the DB doesn't know what choices are available.
In an online shop I use django-mptt for product categories with several levels. I also have products that each belong to a category.
Now I'd like to visualise the category tree like this:
all(18)
- edible (10)
-- food (4)
-- drink (6)
- inedible (8)
-- wood (3)
-- rock (5)
where the numbers in scopes are the product counts for each category.
I am able to visualize the category tree. I can also place the product count behind the category names, but the way I am doing it seems very inefficient. I basically have a get_number_of_products() methods at the category model which returns the number of products for a category. But this needs a new database query each time.
One way to deal with this is to use caching as I don't change the product count that often, but I'd prefer a method that needs less database queries to get the tree with the product count.
What you want is add_related_count
Category.objects.add_related_count(
Category.objects.get_descendants(include_self=True), # Queryset
Product, # Related mobile
'category', # Name of the foreignkey field
'count', # Name of the property added to the collection
cumulative=True) # Cumulative or not.
For instance in the admin you can use something like that:
class CategoryAdmin(DraggableMPTTAdmin):
mptt_indent_field = "name"
list_display = ('tree_actions', 'indented_title',
'related_products_count', 'related_products_cumulative_count')
list_display_links = ('indented_title',)
def get_queryset(self, request):
qs = super().get_queryset(request)
# Add cumulative product count
qs = Category.objects.add_related_count(
qs,
Product,
'category',
'products_cumulative_count',
cumulative=True)
# Add non cumulative recipe count
qs = Category.objects.add_related_count(qs,
Product,
'categories',
'products_count',
cumulative=False)
return qs
def related_products_count(self, instance):
return instance.products_count
related_product_count.short_description = 'Related products (for this specific category)'
def related_products_cumulative_count(self, instance):
return instance.products_cumulative_count
related_products_cumulative_count.short_description = 'Related products (in tree)'
My answer is connected to Django-Oscar, but you may find it out helpful
{{ for tree category in tree_categories }} is a Category class, so it's possible get children by
{{ tree_category.get_num_children }}
I would like to augment one of my model admins with an interesting value. Given a model like this:
class Participant(models.Model):
pass
class Registration(models.Model):
participant = models.ForeignKey(Participant)
is_going = models.BooleanField(verbose_name='Is going')
Now, I would like to show the number of other Registrations for this Participant where is_going is False. So, something akin to this SQL query:
SELECT reg.*, COUNT(past.id) AS not_going_num
FROM registrations AS reg, registrations AS past
WHERE past.participant_id = reg.participant_id AND
past.is_going = False
I think I can extend the Admin's queryset() method according to Django Admin, Show Aggregate Values From Related Model, by annotating it with the extra Count, but I still cannot figure out how to work the self-join and filter into this.
I looked at Self join with django ORM and Django self join , How to convert this query to ORM query, but the former is doing SELECT * AND the latter seems to have data model problems.
Any suggestions on how to solve this?
See edit history for previous version of the answer.
The admin implementation below will display "Not Going Count" for each Registration model. The "Not Going Count" is the count of is_going=False for the registration's participant.
#admin.register(Registration)
class RegistrationAdmin(admin.ModelAdmin):
list_display = ['id', 'participant', 'is_going', 'ng_count']
def ng_count(self, obj):
return obj.not_going_count
ng_count.short_description = 'Not Going Count'
def get_queryset(self, request):
qs = super(RegistrationAdmin, self).get_queryset(request)
qs = qs.filter(participant__registration__isnull=False)
qs = qs.annotate(not_going_count=Sum(
Case(
When(participant__registration__is_going=False, then=1),
default=0,
output_field=models.IntegerField())
))
return qs
Below is a more thorough explanation of the QuerySet:
qs = qs.filter(participant__registration__isnull=False)
The filter causes Django to perform two joins - an INNER JOIN to participant table, and a LEFT OUTER JOIN to registration table.
qs = qs.annotate(not_going_count=Sum(
Case(
When(participant__registration__is_going=False, then=1),
default=0,
output_field=models.IntegerField())
)
))
This is a standard aggregate, which will be used to SUM up the count of is_going=False. This translates into the SQL
SUM(CASE WHEN past."is_going" = False THEN 1 ELSE 0 END)
The sum is generated for each registration model, and the sum belongs to the registration's participant.
I might misunderstood, but you can do for single participant:
participant = Participant.objects.get(id=1)
not_going_count = Registration.objects.filter(participant=participant,
is_going=False).count()
For all participants,
from django.db.models import Count
Registration.objects.filter(is_going=False).values('participant') \
.annotate(not_going_num=Count('participant'))
Django doc about aggregating for each item in a queryset.
I have 3 models:
a model Product that is linked with a ManyToMany Tag model
a model Tag that is linked with a ManyToMany Product model
a model TagContent that is linked with a OneToMany Tag model (= one content is linked to only one Tag and one Tag may have one or more TagContent)
The model TagContent is for multilanguage: one Product may have many Tag, but those Tag show up in the Web page through TagContent:
class Tag(BaseModel):
name = models.CharField(max_length=60, unique=True, default='')
class TagContent(BaseTranslatableModel):
tag = models.ForeignKey(Tag, null=True, default=None)
slug = models.SlugField(max_length=60, unique=True)
locale = models.CharField(max_length=2) # (e.g. "fr")
class Produit(BaseModel):
name = models.CharField(max_length=60, unique=True)
tags = models.ManyToManyField(Tag, related_name='produits')
Here's what I want to do: in my main page, the customer choose the language. Thus I will display all the products based on the language the customer has chosen.
In my main view, I want to display all the products, and all the tags so the user can click on a tag, and i'll filter the product.
The problem is that the tag should be translated in the current language. This means I have to filter the TagContent with the current locale, then get all Tag that are linked to those TagContent then get all the Product that are linked to those tags.
This should give something like (but it doesn't work because the foreign key is in the TagContent, and this is the main problem that blocks me):
Product.objects.filter(tags__in=
Tag.objects.filter(contents__in=
TagContent.objects.filter(langue__locale__exact=locale)
)
)
And in the templating model I need something like:
{% for p in products %}
{% for tag. in p.tags.all %}
{{ tag.name }}
{% endfor %}
{% endfor %}
In other words, I'd like to do this SQL query:
SELECT tc.slug, tc.name
FROM produit p
JOIN produit_tag pt
ON pt.produit_id = p.id
JOIN tag t
ON pt.tag_id = t.id
JOIN tag_content tc
ON tc.tag_id = t.id
JOIN langue l
ON tc.langue_id=l.id
WHERE l.locale='fr'
-> 2 minutes to write this SQL, 3 hours that I'm looking for the solution.
You can use this orm query to get the products:
products = Product.objects.prefetch_related("Tag", "TagContent").
filter(tags__tagcontent__locale="fr")
Django will produce a SQL just like your hand written one. However, multiple JOINs within one SQL is perhaps not a good idea, especially if the joined tables are quite large. Consider breaking the query into 2 might result in better performance (depends on the DB you are using):
fr_tags = Tag.objects.filter(tagcontent__locale="fr")
products = Product.objects.filter(tags__in=fr_tags)
Read about Field lookup that spans relationships:
https://docs.djangoproject.com/en/1.8/topics/db/queries/#lookups-that-span-relationships
Since you already have the SQL query, why don't you just send a raw query instead. And you can just pass the data into your template. It would be something similar to this:
from django.db import connections
cursor = connection.cursor()
query = (""" SELECT tc.slug, tc.name
FROM produit p
JOIN produit_tag pt
ON pt.produit_id = p.id
JOIN tag t
ON pt.tag_id = t.id
JOIN tag_content tc
ON tc.tag_id = t.id
JOIN langue l
ON tc.langue_id=l.id
WHERE l.locale='fr' """)
cursor.execute(query)
data = []
for row in cursor.fetchall():
slug = row[0]
name = row[1]
data_point = {'name': name, 'slug': slug}
data.append(data_point)
If you're not using PostgreSQL this is useless to you, but my former lead developer now maintains the django-pgviews project. It's useful when you have SQL queries containing complex joins that Django might not be able to do efficiently (or at all), which you can represent in a custom SQL View. You sacrifice Django's management, but you can keep using the ORM to query data for reading. Note also the django.contrib.postgres project that is in development.