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

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'

Related

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()

In Django, how to create join on either primary keys?

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)

Django join tables with ORM and conditional Where clause

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)

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 how to filter queryset based on nested many to many relations in one query

Let's say I have a product that can have various child products, and the models are defined like this
class Country(models.Model):
name = models.CharField()
class Product(models.Model):
parent = models.ForeignKey(
'self', null=True, blank=True, related_name='children')
name = models.CharField()
countries = models.ManyToManyField(Country)
my goal is to retrieve all products that have one or more child products that are linked to a specific country.
In my use case I need this information as a Queryset. What I have tried is this and it works:
valid_products = []
desired_country = Country.objects.get(name='mycountry')
for product in Product.objects.all():
for child in product.children.all():
countries = child.countries.all()
for country in countries:
if country == desired_country:
valid_products.append(product.id)
desired_queryset = Product.objects.filter(pk__in=valid_products)
This method requires and additional query to convert my result into a queryset and I would like to avoid that.
Is it possible to filter a queryset like this directly with the Django ORM?
You can simply follow the relations using the double-underscore syntax. So:
desired_queryset = Product.objects.filter(children__countries= desired_country)

Categories

Resources