So, in Django, we have two models.
model1(models):
pk1 = models.pk(model2, on_delete=models.CASCADE, related_name='fk1')
pk2 = models.pk(model2, on_delete=models.CASCADE, related_name='fk2')
some_field = models.charField()
model2(models)
somefield = models.charfield()
I'd like to create a query to join then and match on either the first primary key or the second
The sql equivalent would be
select *
from model1
join model2 on (model2.id = model1.pk1__id or model2.id = model1.pk2__id)
for now I'm stucked with
Model1.objects.select_related('model2')
which always match on the first pk
I've tried transversing throught the foreign keys
Model1.objects.values('fk1__some_field', 'fk2__some_field')
but as you inspect the query, it shoes that it does two joins, naming the second table something like 't6'
Adding a ForeignKey to both models will give you the capability to filter through both tables and query for results. It can be done like so:
key = models.ForeignKey(ModelReference, on_delete=models.CASCADE)
The ModelReference is the model that the ForeignKey is based on.
On the other hand, if you want to create a more detailed db relationship, you can add a OneToOne or ManyToMany Fields like so:
field = models.OneToOneField(ModelReference, on_delete=models.CASCADE, primary_key=True)
car = models.ManyToManyField(Cars)
Related
I want to only execute a prefect_related on my queryset when a boolean value in my database is True.
I have the following models:
from django.db import models
class Map(models.Model):
has_ratings = models.BooleanField()
class Place(models.Model):
name = models.CharField()
map = models.ForeignKey(Map, on_delete=models.CASCADE)
...
class Rating(models.Model):
rating = models.IntegerField()
place = models.ForeignKey(place, on_delete=models.CASCADE)
...
Not every map has the rating functionality turned on. Therefore I only want to conditionally fetch the ratings and prevent the extra query to the Rating table for maps without ratings.
The query below always queries the Rating table.
Place.objects.all().prefetch_related(
Prefetch(
"ratings",
queryset=Rating.objects.all(),
to_attr="rating_list",
)
)
So I tried to add a filter:
Place.objects.all().prefetch_related(
Prefetch(
"ratings",
queryset=Rating.objects.all(),
to_attr="rating_list",
)
).filter(Q(map__has_rating=True))
This doesn't work because it filters all places for a Map with has_rating=False.
Is this doable without loading the map in memory? How can I can I make the prefetch optional based on a database value with Dango querysets?
I have 4 tables to join; Personnels,Machines and Locations. I want to join these tables and add where clause to end of the ORM query if request body includes filtering data. Here is my models and raw query (I want to write this query in django ORM) and sample if condition for where clause;
Models ;
class Sales(models.Model):
MachineId = models.ForeignKey(Machines,on_delete=models.CASCADE,db_column='MachineId',related_name='%(class)s_Machine')
PersonnelId = models.ForeignKey(Personnels,on_delete=models.CASCADE,db_column='PersonnelId',related_name='%(class)s_Personnel')
LocationId = models.ForeignKey(Locations,on_delete=models.CASCADE,db_column='LocationId',related_name='%(class)s_Location')
class Meta:
db_table = "Sales"
class Machines(models.Model):
Name = models.CharField(max_length=200)
Fee = models.DecimalField(max_digits=10,decimal_places=3)
class Meta:
db_table = "Machines"
class Personnels(models.Model):
name = models.CharField(max_length=200)
surname = models.CharField(max_length=200)
class Meta:
db_table = "Personnels"
class Locations(models.Model):
Latitude = models.FloatField()
Longitude = models.FloatField()
LocationName = models.CharField(max_length=1000)
class Meta:
db_table = "Locations"
As you see I have 4 models. "Sales" table has foreignkeys to others. I want to get all informations in tables with using these foreign keys.(With Inner Join)
query = '''select * from "Sales" as "SL" INNER JOIN "Personnels" as "PL" ON ("SL"."PersonnelId" = "PL"."user_id") INNER JOIN "Machines" as "MC" ON ("SL"."MachineId" = "MC"."id") INNER JOIN "Locations" as "LC" ON ("SL"."LocationId" = "LC"."id") '''
if request.method=='POST':
if request.data['personnel_name'] and request.data['personnel_name'] is not None:
personnel_name = request.data['personnel_name']
condition = '''WHERE "PL"."name" = '{0}' '''.format(personnel_name)
query = query+condition
As it is seen, there are lots of quotes (if I don't write,postgresql makes some trouble) and code is not clean.
My question is, how can I write this query with using django ORM? As you see, I want to add where conditions dynamically. How can I achieve that?
I'm going to use conventional naming, with only class names captilized, and model names singular.
class Sale(models.Model):
machine = models.ForeignKey(Machine, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
location = models.ForeignKey(Location, on_delete=models.CASCADE)
db_column and db_table is useful if you have to connect the django app use an existing database. If not, django will create sensible table names by default. The table name can be different from the model field name.
To create a join where, use a queryset filter.
Sale.objects.filter(person__name='Jane Janes')
You might not need more joins, since django will perform additional queries when needed, but it can be achieved using select_related, and can give you better performance, since it reduces the total number of sql queries needed.
Sale.objects.filter(person__name='Jane Janes').select_related('machine', 'person', 'location')
It can be useful to inspect the actual SQL that will be performed when you evalute a queryset. You can do this by accessing the QuerySet.query property.
queryset = Sale.objects.select_related('machine').filter(
person__name='Jim', location__name='London')
print(queryset.query)
I'm using Django 1.11.6, python 3.4.2, postgresql, PyCharm 4.5.2, and windows 10 (only for development purposes).
The goal is to utilize the 'Lookups that span relationships' from the Django docs.
# models
class AlphaType(models.Model):
page_type = models.CharField(max_length=50, primary_key=True, null=False)
created_on = models.DateTimeField(auto_now_add=True)
modified_on = models.DateTimeField(auto_now=True)
class AlphaBoard(models.Model):
title = models.CharField(max_length=50)
alpha_text = models.TextField(max_length=30000)
created_on = models.DateTimeField(auto_now_add=True)
modified_on = models.DateTimeField(auto_now=True)
fk_page_type = models.ForeignKey(AlphaType, on_delete=models.CASCADE, default='general')
#views
....
q = AlphaBoard.objects.filter(fk_page_type__page_type='general')
print(q.query)
....
Just fyi, the tables have the app name prepended to the model name and the foreign key has 'id' appended to the foreign key column name.
Result of the query print.
SELECT
"alpha_alphaboard"."id", "alpha_alphaboard"."title",
"alpha_alphaboard"."alpha_text", "alpha_alphaboard"."created_on",
"alpha_alphaboard"."modified_on", "alpha_alphaboard"."fk_page_type_id"
FROM
"alpha_alphaboard"
WHERE
"alpha_alphaboard"."fk_page_type_id" = "general"
What I was expecting.
SELECT
"alpha_alphaboard"."id", "alpha_alphaboard"."title",
"alpha_alphaboard"."alpha_text", "alpha_alphaboard"."created_on",
"alpha_alphaboard"."modified_on", "alpha_alphaboard"."fk_page_type_id"
FROM
"alpha_alphaboard"
INNER JOIN "alpha_alphaboard" ON "alpha_alphatype"
"alpha_alphaboard"."fk_page_type_id" = "alpha_alphatype"."page_type"
WHERE
"alpha_alphatype"."page_type" = "general"
Questions
Why is the query ignoring the page_type relation from the filter? Look at the result of the printed query and the filter within the views. I should also add that I had a related_name="fk_page_type" within the AlphaBoard.fk_page_type, but I removed it. So a follow up question is why is it still picking up the related_name?
How do you use the "relationship" from the docs to get the expected?
Is there a way to specify the join type?
Since page_type is the primary key of the AlphaType model and its value is just written in the fk_page_type column of the AlphaBoard table, no join is needed:
q = AlphaBoard.objects.filter(fk_page_type__page_type='general')
is the same as
q = AlphaBoard.objects.filter(fk_page_type_id='general')
the field of the related model you are using in your filter is the exact foreign key value that is written in the primary table.
As for the related_name, it is used to access the reverse relation:
class AlphaBoard(models.Model):
fk_page_type = models.ForeignKey(AlphaType, on_delete=models.CASCADE, related_name='boards')
t = AlphaType(...)
boards = t.boards.all() # gives all boards with type t
boards = t.alphaboard_set.all() # default: lowermodelname_set
If I have the user-group relation as a ManyToMany relationship, and a group-team relation as a ManyToMany relationship, how would I find the user-team relation as a query object?
For example if I had a models.py like this:
class Group(models.Model):
name = CharField(max_length=100)
class User(models.Model):
username = models.CharField(max_length=100)
groups = models.ManyToManyField(Group, blank=True)
class Team(models.Model):
teamname = models.CharField(max_length=100)
groups = models.ManyToManyField(Group, blank=True)
I know I can do it by creating a for loop like follows, but it would be much more useful given django's architecture to have it as a query object.
My ugly solution:
user = User.objects.get(username="Ed")
users_teams = []
user_groups = Group.objects.filter(user=user)
for group in user_groups:
group_teams = Team.objects.filter(group=group)
for team in group_teams:
user_teams.append(team)
So this would give me a list query objects that I could associate with a user, but it isn't as easy to use as a single query set the contains query objects for each user-team relation.
I would prefer something that looks like this:
User.objects.get(username="Ed").groups.team_set.objects.all()
which definitely doesn't work.
Any ideas?
Team.objects.filter(groups__user__username="Ed")
which generates you:
SELECT
`api_team`.`id`,
`api_team`.`teamname`
FROM `api_team`
INNER JOIN `api_team_groups` ON (`api_team`.`id` = `api_team_groups`.`team_id`)
INNER JOIN `api_group` ON (`api_team_groups`.`group_id` = `api_group`.`id`)
INNER JOIN `api_user_groups` ON (`api_group`.`id` = `api_user_groups`.`group_id`)
INNER JOIN `api_user` ON (`api_user_groups`.`user_id` = `api_user`.`id`)
WHERE `api_user`.`username` = 'ed'
I have following models in my django app:
Product model:
class Product(BaseModel):
company = models.ForeignKey(Company, null=True, blank=True)
title = models.CharField(max_length=128,verbose_name="Product title")
Order model:
class Order(BaseModel):
company = models.ForeignKey(Company)
products = models.ManyToManyField(Product)
I am not able to add multiple same products to an order.
For example, I have product called "Car", how can I add multiple Car objects to single Order?
Every Order may contain multiple Products (same or not).
You need to use third table for that task, for example:
class OrderProduct(BaseModel):
order = models.ForeignKey(Order)
product = models.ForeignKey(Product)
quantity = models.IntegerField()
and then you can use intermediary table https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany
class Order(BaseModel):
company = models.ForeignKey(Company)
products = models.ManyToManyField(Product, through='OrderProduct')
Unlike normal many-to-many fields, you can’t use add, create, or assignment (i.e., order.products.add(prod)) to create relationships. You should manually create record in through table:
prod = Product.objects.get(uuid=product)
#order.products.add(prod)
q = product_quantity
order = order # order record must be created before
OrderProduct.objects.create(order=order, product=prod, quantity=q)
check this questions:
How can I add the same object to a ManyToMany field?
adding the same object twice to a ManyToManyField
The many to many relationship with foreign keys works backwards. You don't need a foreign key in your Order model. What you need is a foreign key in the Product model linking to Orders (Every product will have an order) :
class Product(BaseModel):
company = models.ForeignKey(Company, null=True, blank=True)
order = models.ForeignKey(Order)
title = models.CharField(max_length=128,verbose_name="Product title")