Reputation: 15778
I have 3 models:
Product
that is linked with a ManyToMany Tag
modelTag
that is linked with a ManyToMany Product
modelTagContent
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.
Upvotes: 0
Views: 85
Reputation: 18513
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
Upvotes: 1
Reputation: 639
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.
Upvotes: 0
Reputation: 3497
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)
Upvotes: 0