i am using django(3.1.5). and i am trying to get parent model to child model by filter query
i have model like -
class Product(models.Model):
product_name = models.CharField(max_length=255, unique=True)
is_feature = models.BooleanField(default=False)
is_approved = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class ProductGalleryImage(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
product_gallery_image = models.FileField(upload_to='path')
is_feature = models.BooleanField(default=False)
i am getting data from SELECT * FROM products_product AS pp INNER JOIN products_productgalleryimage AS ppgi ON ppgi.product_id = pp.id WHERE ppgi.is_feature=1 AND pp.is_feature=1 AND is_approved=1 ORDER BY pp.created_at LIMIT 4 mysql query.
so how can i get data like this query in django filter query
Firstly you can add related_name to ProductGalleryImage for better query support like this
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='product_images')
Then your query should be like this
products=Product.objects.filter(is_approved=True, is_feature=True, product_images__is_feature=True).order_by('created_at')[:4]
You can simply loop over the other related model like so:
for product_gallery_image in product_instance.productgalleryimage_set.all():
print(product_gallery_image.product_gallery_image)
The productgalleryimage_set here is simply the related model name in lowercase with _set appended. You can change this by setting the related_name attribute on the foreign key.
Note: This will perform a query to fetch each of the product_gallery_image objects of some product instance.
If you want to get the first object only:
product_gallery_image = product_instance.productgalleryimage_set.first()
If you want to perform a join as in your example which will perform only one query you can use select_related (this will only work in forward direction for reverse direction look at prefetch_related):
product_gallery_images = ProductGalleryImage.objects.all().select_related('product')
for product_gallery_image in product_gallery_images:
print(product_gallery_image.product.product_name)
print(product_gallery_image.product_gallery_image)
Related
I trying to make some queries in Django ORM (migration from SQL). My models looks like this
class Iv2(models.Model):
s_id = models.AutoField(primary_key=True)
l_eid = models.CharField(max_length=265)
t_id = models.CharField(max_length=265,unique=True)
class Sv2(models.Model):
id = models.AutoField(primary_key=True)
s_id = models.OneToOneField(Iv2, on_delete=models.PROTECT)
gdd = models.DateTimeField(default=datetime.now)
class Ev2(models.Model):
id = models.OneToOneField(Iv2, to_field='l_eid', on_delete=models.PROTECT)
s_id = models.ForeignKey(Iv2, on_delete=models.PROTECT)
car = models.CharField(max_length=265)
I want to write a query, given t_id(some real search value). I want to get the corresponding Sv2.gdd and Ev2.car
I'm thinking to get s_id and l_eid with the t_id. And when I get s_id. I can query Sv2 and with l_eid I can query Ev2.
Is it possible to achieve everything with one ORM query ? can prefetch/select_related work here?
"Given t_id of Iv2 get Sv2.gdd and Ev2.car"
First get the Sv2 instance by filtering its 1:1 relation by the
real search value t_id:
sv2 = Sv2.objects.filter(s_id__t_id=t_id).first()
sv2.gdd
Now you have 2 options to get Ev2.car
Add related name in Ev2 (more on related_name here )
Assuming your related_name is ev2 you can do:
sv2.ev2.car
Use the default django related_name modelname__set
sv2.ev2_set
I have the following models:
class Shelf(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=200, editable=False)
games = models.ManyToManyField(Game, blank=True, through='SortedShelfGames')
objects = ShelfManager()
description = models.TextField(blank=True, null=True)
class SortedShelfGames(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE)
shelf = models.ForeignKey(Shelf, on_delete=models.CASCADE)
date_added = models.DateTimeField()
order = models.IntegerField(blank=True, null=True)
releases = models.ManyToManyField(Release)
objects = SortedShelfGamesManager.as_manager()
class Game(models.Model):
name = models.CharField(max_length=300, db_index=True)
sort_name = models.CharField(max_length=300, db_index=True)
...
I have a view where I want to get all of a user's SortedShelfGames, distinct on the Game relationship. I then want to be able to sort that list of SortedShelfGames on a few different fields. So right now, I'm doing the following inside of the SortedShelfGamesManager (which inherits from models.QuerySet) to get the list:
games = self.filter(
pk__in=Subquery(
self.filter(shelf__user=user).distinct('game').order_by('game', 'date_added').values('pk') # The order_by statement in here is to get the earliest date_added field for display
)
)
That works the way it's supposed to. However, whenever I try and do an order_by('game__sort_name'), the query takes forever in my python. When I'm actually trying to use it on my site, it just times out. If I take the generated SQL and just run it on my database, it returns all of my results in a fraction of a second. I can't figure out what I'm doing wrong here. The SortedShelfGames table has millions of records in it if that matters.
This is the generated SQL:
SELECT
"collection_sortedshelfgames"."id", "collection_sortedshelfgames"."game_id", "collection_sortedshelfgames"."shelf_id", "collection_sortedshelfgames"."date_added", "collection_sortedshelfgames"."order",
(SELECT U0."rating" FROM "reviews_review" U0 WHERE (U0."game_id" = "collection_sortedshelfgames"."game_id" AND U0."user_id" = 1 AND U0."main") LIMIT 1) AS "score",
"games_game"."id", "games_game"."created", "games_game"."last_updated", "games_game"."exact", "games_game"."date", "games_game"."year", "games_game"."quarter", "games_game"."month", "games_game"."name", "games_game"."sort_name", "games_game"."rating_id", "games_game"."box_art", "games_game"."description", "games_game"."slug", "games_game"."giantbomb_id", "games_game"."ignore_giantbomb", "games_game"."ignore_front_page", "games_game"."approved", "games_game"."user_id", "games_game"."last_edited_by_id", "games_game"."dlc", "games_game"."parent_game_id"
FROM
"collection_sortedshelfgames"
INNER JOIN
"games_game"
ON
("collection_sortedshelfgames"."game_id" = "games_game"."id")
WHERE
"collection_sortedshelfgames"."id"
IN (
SELECT
DISTINCT ON (U0."game_id") U0."id"
FROM
"collection_sortedshelfgames" U0
INNER JOIN
"collection_shelf" U1 ON (U0."shelf_id" = U1."id")
WHERE
U1."user_id" = 1
ORDER
BY U0."game_id" ASC, U0."date_added" ASC
)
ORDER BY
"games_game"."sort_name" ASC
I think you don't need a Subquery for this.
Here's what I ended up doing to solve this. Instead of using a Subquery, I created a list of primary keys by evaluating what I was using as the Subquery in, then feeding that into my query. It looks like this:
pks = list(self.filter(shelf__user=user).distinct('game').values_list('pk', flat=True))
games = self.filter(
pk__in=pks)
)
games = games.order_by('game__sort_name')
This ended up being pretty fast. This is essentially the same thing as the Subquery method, but whatever was going on underneath the hood in python/Django was slowing this way down.
Say I have two models, which I'll call ModelA and ModelB. Both models have a few fields in common, (represented by field_one, field_two, and field_three). Additionally, ModelB has a foreign key to ModelA.
class ModelA(Model):
field_one = models.IntegerField()
field_two = models.TextField()
field_three = models.BooleanField()
class ModelB(Model):
field_one = models.IntegerField()
field_two = models.TextField()
field_three = models.BooleanField()
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE)
I need to update all instances of ModelB to update the field's values to the values of the associated ModelA instances. I need to do this operation entirely in the database, without needing to instantiate any model instances (NOT using .save() or bulk_update()).
I know how I can accomplish this in PostgreSQL using subqueries:
UPDATE model_b SET (field_one, field_two, field_three) =
(SELECT field_one, field_two, field_three FROM model_a
WHERE model_b.model_a_id = model_a.id);
How can I express the above query in Django's ORM?
This is the closest I have been able to get:
ModelB.objects.update(
field_one=Subquery(ModelA.objects.filter(id=OuterRef('model_a_id')).values(field_one)[:1]),
field_two=Subquery(ModelA.objects.filter(id=OuterRef('model_a_id')).values(field_two)[:1]),
field_three=Subquery(ModelA.objects.filter(id=OuterRef('model_a_id')).values(field_three)[:1])
})
However, this results in a subquery for every single field:
UPDATE model_b SET
field_one = (SELECT model_a.field_one FROM model_a WHERE model_a.id = model_b.model_a_id LIMIT 1),
field_two = (SELECT model_a.field_two FROM model_a WHERE model_a.id = model_b.model_a_id LIMIT 1),
field_three = (SELECT model_a.field_three FROM model_a WHERE model_a.id = model_b.model_a_id LIMIT 1);
Unfortunately the ORM doesn't support annotations spreading multiple columns and I'm not aware of a feature request for it.
If you want to stick to the ORM you'll have to take the possible performance hit (maybe PostgreSQL is smart enough to use only a single one; EXPLAIN would tell) otherwise you'll have to switch to .raw SQL.
by passing modelA as an argument in the modelB class arguments, ModelB and modelC will inherit of every field of modelA
class ModelA(Model):
field_one = models.IntegerField()
field_two = models.TextField()
field_three = models.BooleanField()
class ModelB(ModelA):
pass
class ModelC(ModelA):
pass
models.py
class products(models.Model):
name = models.CharField(max_length=100)
sku = models.CharField(max_length=50)
vendor = models.CharField(max_length=50)
brand = models.CharField(max_length=50)
price = models.FloatField()
product_status = models.BooleanField()
quantity = models.IntegerField()
def __str__(self):
return self.name
# categories
class categories(models.Model):
category_name = models.CharField(max_length=50)
parent_id = models.IntegerField()
# product categories
class product_categories(models.Model):
product = models.ForeignKey(products, on_delete=models.CASCADE)
category = models.ForeignKey(categories, on_delete=models.CASCADE)
def __str__(self):
return self.category
I can access 'category' table data(inside django shell) using
data = products.objects.all()
data.values('product_categories__category__category_name')
output: <QuerySet [{'product_categories__category__category_name': 'xxxx'}}]>
If I put this(inside django shell)
data.product_categories.category
output: 'QuerySet' object has no attribute 'product_categories'
How do I get a queryset(can be passed to html) which includes data from "categories" table along with the data of "products" table
There are a couple of issues happening here. First, data is a queryset, which is kind of like a list of objects, even though here there's just one object in the list. What you want is to get an attribute off of the item in the list, so you need something like a data.first() to get to that object before you start dotting into its attributes.
Secondly, the way Django handles reverse FK relationships requires that you refer to the FK by the standard name of, in your case, product_categories_set, OR you set your own related_name attribute on the FK. Something like:
# product categories
class product_categories(models.Model):
product = models.ForeignKey(products, on_delete=models.CASCADE, related_name='product_categories')
category = models.ForeignKey(categories, on_delete=models.CASCADE, related_name='product_categories')
def __str__(self):
return self.category
so that you can refer to your product_categories model from both the product and categories using just data.product_categories.
Thirdly, when accessing a reverse FK relationship, just like in point (1) above, you will get a related manager, from which you can get a queryset of items. Thus, to get the category name, you need to indicate which item you want the category name for. Assuming it's just the first item for everything, it would look something like:
data = products.objects.all()
product_category = data.product_categories.all()
category_name = product_category.category.category_name
Of course once you have more data, you'll not always want to just pick the first item, so you'll need to add filtering logic into the query to make sure you get the item you're looking for.
ETA, I do agree with the comment by Jorge above - a MTM would make this a bit simpler and would, in essence, create your product_categories table for you.
please I need your help how to reduce the database call when using ModelChoiceField as it requires a queryset and I have to use it three times separately with a model that is recursively foreign key on itself, the code is below:
ModelForm code in the init function
self.fields['category'] = forms.ModelChoiceField(queryset=queryset)
self.fields['super_category'] = forms.ModelChoiceField(queryset=)
self.fields['product_type'] = forms.ModelChoiceField(queryset=)
the model class:
class Category(ProjectBaseModel, AuditLogMixin):
parent_id = models.ForeignKey('self', related_name='children', blank=True, null=True, on_delete=models.CASCADE,verbose_name=_('Parent'))
what i tried to do is collect all ids of the desired categories in array and make only one filter queryset with them like the following:
category = auction.category
super_category = category.parent_id
product_type = super_category.parent_id
ids= [category.id,super_category.id,product_type.id]
queryset = Category.objects.filter(id__in=ids)
How to proceed with that solution