ORM Query with multiple LEFT joins - python

Toy example: Let's say I have the following models:
# Person ---lives_in--> City ---part_of---> State
class Person(models.Model)
name = models.CharField(max_length=100)
lives_in = models.ForeignKey('City', on_delete=models.CASCADE)
class City(models.Model)
name = models.CharField(max_length=100)
part_of = models.ForeignKey('State', on_delete=models.CASCADE)
class State(models.Model):
name = models.CharField(max_length=100)
How do I get a list of people who live in a particular state using Django ORM?
In regular SQL, it be something like
SELECT p.*
FROM person p
LEFT JOIN city c ON (p.lives_in = c.id)
LEFT JOIN state s ON (c.part_of = s.id)
WHERE c.name = 'MA'

You could simply traverse through the relationships using __ notation.
people_in_ma = Person.objects.filter(lives_in__part_of__name="MA")
https://docs.djangoproject.com/en/2.2/topics/db/queries/#lookups-that-span-relationships

Related

Can't join two tables on a foreign key field using django ORM?

class weapons(models.Model):
weapon = models.CharField(max_length=11)
country = models.IntegerField()
flags = models.IntegerField(default=0)
title = models.CharField(max_length=1000)
class compare(models.Model):
weapon = models.CharField(max_length=11)
user_id = models.IntegerField()
flags = models.IntegerField(default=0)
WeaponNode = models.ForeignKey(weapons, on_delete=models.PROTECT)
When I run this function:
compare.objects.filter(user_id=1).values_list('weapon', 'WeaponNode__title')
I expect this query raw:
SELECT apps_compare.weapon, apps_weapons.title FROM apps_compare INNER JOIN apps_weapons ON (apps_compare.weapon = apps_weapons.weapon) WHERE apps_compare.user_id = 1
Result should be:
But it returns this instead:
SELECT "apps_compare"."weapon", "apps_weapons"."title" FROM "apps_compare" INNER JOIN "apps_weapons" ON ("apps_compare"."WeaponNode_id" = "apps_weapons"."id") WHERE "apps_compare"."user_id" = 1
apps_weapons.title returns null:
In other examples I saw, they only used id with JOIN ON but I want to use a weapon value instead of id. How can I do accomplish with ORM? If it's not possible with ORM, then what are the other ways?
You are here using the ForeignKey the wrong way. If the weapon in your Compare model refers to weapons in the Weapon model, it should be unique, and then use that as a to_field=… [Django-doc]:
class Weapon(models.Model):
weapon = models.CharField(max_length=11, unique=True)
# …
class Compare(models.Model):
weapon = models.ForeignKey(
Weapon,
on_delete=models.CASCADE
to_field='weapon',
db_column='weapon'
)
# …
Then you can query with:
Compare.objects.values('weapon', 'weapon__title')

How to join tables by list of ids in Django ORM

I have two tables - Players and Buildings. For example:
class Players(models.Model):
username = models.CharField(max_length=500)
buildings = models.CharField(validators=[int_list_validator], default=list, max_length=100)
class Buildings(models.Model):
building_name = models.CharField(max_length=500)
Player can have a lot of buildings, there are in the Player.buildings field as list of ids.
I need to write a query to get building information for the player. In Postgres SQL, I got that query:
SELECT *
FROM players p
LEFT JOIN buildings b on b.id=ANY(buildings) where p.id = %s;
But how I can write that SQL query, using Python and Django ORM?
You should use singular names for your models.
class Player(models.Model):
username = models.CharField(max_length=500)
buildings = models.ManyToManyField(Building)
class Building(models.Model):
building_name = models.CharField(max_length=500)
Now when you want to access the set of buildings for your player you can do the following:
Player.objects.get("pk of player").buildings.all()

Getting the many-to-many fields of a many-to-many object

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'

Stringing together a series of foreign keys to return a table value in Django

How do I select the ctype knowing the user?
models.py:
from django.contrib.auth.models import User
class CompanyType(models.Model):
ctype = models.CharField(max_length=20)
class Company(models.Model):
name = models.CharField(max_length=50)
companytype = models.ForeignKey(CompanyType, on_delete=models.CASCADE)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
I know I can do it this way, but is there a way to chain it altogether in one line?
def getcompanytype(request):
user_id = request.user.id
company_id = Profile.objects.get(user_id__exact=user_id).company_id
companytype_id = Company.objects.get(id=company_id).companytype_id
companytype = CompanyType.objects.get(id=companytype_id).ctype
return(companytype)
essentially I want this SQL statement:
SELECT
ct.ctype
From auth_user u
left outer join Profile p
on p.user_id = u.id
left outer join Company c
on p.company_id = c.id
left outer join CompanyType ct
on ct.id = c.companytype_id
where u.id = 1 # actually current user ID, not hardcoded "1"
I think you can do this by following the reverse relationships of the ForeignKeys in a filter or get call, joining the different relations together with double underscores:
CompanyType.objects.get(company__profile__user__id__exact=user_id).ctype
Although I'd suggest you try that out in a Django shell and make sure that the reverse relationships are named how you'd expect! (For example, CompanyType will have a company_set attribute that gives all the companies with a particular CompanyType; the _set shouldn't be included when you do filter queries.)

django view limit subquery

I have 3 models:
models.py:
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class Musician(models.Model):
person = models.ForeignKey(Person)
instrument = models.CharField(max_length=100)
class Album(models.Model):
artist = models.ForeignKey(Musician)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
I'm trying to build a view that would:
Show all persons with their albums excluding those ALBUMS rated with 1 star.
views.py:
inner_qs = Musician.objects.filter(Album__num_stars__exact=1).values_list('person', flat=True).distinct()
person_list = Person.objects.exclude(Musician__Album__id__in=inner_qs).distinct()
The query aways excludes all artists that could have had an album with 1 star.
I need something that on SQL would look like this:
SELECT p.first_name, p.last_name,m.instrument, m.name, m.num_stars
FROM Person p
INNER JOIN Musician m ON m.person = p.id
INNER JOIN Album a ON a.artist = m.id
WHERE m.num_stars <> 1
I know that the view is excluding at the Person level, but ¿how do I exclude elements further into the db model?
Thanks!
If you need all fields data from linked objects (result sql will be similar to what you need):
Album.objects.filter(~Q(num_stars=1)).select_related()
And only person data:
Person.objects.filter(~Q(musician__album__num_stars=1))
Person.objects.filter(Musician__Album__num_stars__exact=1).values_list('person', flat=True).distinct()
results a sql same result:
SELECT DISTINCT p.first_name, p.last_name,m.instrument, m.name, m.num_stars
FROM Person p
INNER JOIN Musician m ON m.person = p.id
INNER JOIN Album a ON a.artist = m.id
WHERE m.num_stars <> 1

Categories

Resources