Django - reverse lookups - django

For example, I have these models:
class Person(models.Model):
name = models.CharField(max_length=20)
employer = models.CharField(max_length=20)
class Car(models.Model):
person = models.ForeignKey(Person)
name = models.CharField(max_length=10)
model = models.CharField(max_length=10)
...
I want to get all people who own some specific car:
people = Person.objects.filter(car__name="Toyota")
Now I want to write these people out with details about their own car. I can do this:
for person in people:
...
cars = person.car_set.filter(name="Toyota")
...
But it hit the database again. How can I avoid this? Is there any way to do this simpler?

First select the car and the related person when the car name
cars = Car.objects.select_related("person").filter(name="Toyota").order_by("person")
Now you have all the cars whose name is toyota along with the person for that car, ordered_by person.
Now use the python itertools.groupby to group this list for each person
from itertools import groupby
for k, g in groupby(cars, lambda x: x.person):
person = k
cars = list(g)
Now in that iteration you have the person and his cars (whose name is "toyota").
You'll notice that there is only single query that occurs, and then the following operations execute on the cached information.

Check out select_related(), I've used it before to turn many small queries that span multiple models into one large query: https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related
It works by pre-populating the QuerySet, so your access to car_set will already be there and won't result in a new query.

I don't believe there is anyway to avoid the additional database hits. If you're concerned about making too many queries, you can try something like this:
from collections import defaultdict
cars = Car.objects.filter(**kwargs).selected_related('person')
owners = defaultdict(list)
for car in cars:
owners[car.person].append(car)
This should only be one query that selects all the relevant cars and the data about their related person

Related

how to select fields in related tables quickly in django models

I'm trying to get all values in current table, and also get some fields in related tables.
class school(models.Model):
school_name = models.CharField(max_length=256)
school_type = models.CharField(max_length=128)
school_address = models.CharField(max_length=256)
class hometown(models.Model):
hometown_name = models.CharField(max_length=32)
class person(models.Model):
person_name = models.CharField(max_length=128)
person_id = models.CharField(max_length=128)
person_school = models.ForeignKey(school, on_delete=models.CASCADE)
person_ht = models.ForeignKey(hometown, on_delete=models.CASCADE)
how to quick select all info i needed into a dict for rendering.
there will be many records in person, i got school_id input, and want to get all person in this school, and also want these person's hometown_name shown.
i tried like this, can get the info i wanted. And any other quick way to do it?
m=person.objects.filter(person_school_id=1)
.values('id', 'person_name', 'person_id',
school_name=F('person_school__school_name'),
school_address=F('person_school__school_address'),
hometown_name=F('person_ht__hometown_name'))
person_name, person_id, school_name, school_address, hometown_name
if the person have many fields, it will be a hard work for list all values.
what i mean, is there any queryset can join related tables' fields together, which no need to list fields in values.
Maybe like this:
m=person.objects.filter(person_school_id=1).XXXX.values()
it can show all values in school, and all values in hometown together with person's values in m, and i can
for x in m:
print(x.school_name, x.hometown_name, x.person_name)
You add a prefetch_related query on top of your queryset.
prefetch_data = Prefetch('person_set, hometown_set, school_set', queryset=m)
Where prefetch_data will prepare your DB to fetch related tables and m is your original filtered query (so add this below your Person.objects.filter(... )
Then you do the actual query to the DB:
query = query.prefetch_related(prefetch_data)
Where query will be the actual resulting query with a list of Person objects (so add that line below the prefetch_data one).
Example:
m=person.objects.filter(person_school_id=1)
.values('id', 'person_name', 'person_id',
school_name=F('person_school__school_name'),
school_address=F('person_school__school_address'),
hometown_name=F('person_ht__hometown_name'))
prefetch_data = Prefetch('person_set, hometown_set, school_set', queryset=m)
query = query.prefetch_related(prefetch_data)
In that example I've broken down the queries into more manageable pieces, but you can do the whole thing in one big line too (less manageable to read though):
m=person.objects.filter(person_school_id=1)
.values('id', 'person_name', 'person_id',
school_name=F('person_school__school_name'),
school_address=F('person_school__school_address'),
hometown_name=F('person_ht__hometown_name')).prefetch_related('person, hometown, school')

Django make a double foreign key lookup involving one to many to one relations

I have the following models:
def Order(models.Model):
receiver = models.ForeignKey(Receiver)
warehouse = models.ForeignKey(Warehouse)
def Receiver(models.Model):
user = models.ForeignKey(User) #this is not made one to one because user can have more than one receiver
name = ...
zipcode = ...
def Warehouse(models.Model):
city = ...
street = ...
I want to select all Warehouse entries related to request.User object. The only way i can do this now is:
orders = Order.objects.filter(receiver__user=request.User)
# here i set orders warehouse ids to list called ids
user_warehouses = Warehouse.objects.filter(pk__in=ids)
But i have a strong feeling that i am inventing the wheel. Is there a more simple Django-way of doing this?
Warehouse.objects.filter(order__receiver__user=request.user)
you can traverse relations backwards ("reverse lookup") and traverse multiple levels with the double-underscore syntax
https://docs.djangoproject.com/en/1.8/topics/db/queries/#lookups-that-span-relationships

How to ask the Django manytomany manager to match several relations at once?

I've got this model:
class Movie(models.Model):
# I use taggit for tag management
tags = taggit.managers.TaggableManager()
class Person(models.Model):
# manytomany with a intermediary model
movies = models.ManyToManyField(Movie, through='Activity')
class Activity(models.Model):
movie = models.ForeignKey(Movie)
person = models.ForeignKey(Person)
name = models.CharField(max_length=30, default='actor')
And I'd like to match a movie that has the same actors as another one. Not one actor in common, but all the actors in common.
So I don't want this:
# actors is a shortcut property
one_actor_in_common = Movie.object.filter(activities__name='actor',
team_members__in=self.movie.actors)
I want something that would make "Matrix I" match "Matrix II" because they share 'Keanu Reeves' and 'Laurence Fishburne', but not match "Speed" because they share 'Keanu Reeves' but not 'Laurence Fishburne'.
The many to manytomany manager cannot match several relations at once. Down at the database level it all comes down to selecting and grouping.
So naturally speaking the only question the database is capable to answer is this: List acting activites that involve these persons, group them by movie and show only those movies having the same number of those acting activities as persons.
Translated to ORM speak it looks like this:
actors = Person.objects.filter(name__in=('Keanu Reaves', 'Laurence Fishburne'))
qs = Movie.objects.filter(activity__name='actor',
activity__person__in=actors)
qs = qs.annotate(common_actors=Count('activity'))
all_actors_in_common = qs.filter(common_actors=actors.count())
The query this produces is actually not bad:
SELECT "cmdb_movie"."id", "cmdb_movie"."title", COUNT("cmdb_activity"."id") AS "common_actors"
FROM "cmdb_movie"
LEFT OUTER JOIN "cmdb_activity" ON ("cmdb_movie"."id" = "cmdb_activity"."movie_id")
WHERE ("cmdb_activity"."person_id" IN (SELECT U0."id" FROM "cmdb_person" U0 WHERE U0."name" IN ('Keanu Reaves', 'Laurence Fishburne'))
AND "cmdb_activity"."name" = 'actor' )
GROUP BY "cmdb_movie"."id", "cmdb_movie"."title", "cmdb_movie"."id", "cmdb_movie"."title"
HAVING COUNT("cmdb_activity"."id") = 2
I also have a small application that I used to test this, but I don't know wether someone needs it nor where to host it.

Django Models and SELECT with joined two or more tables?

How can I make an select with two (or more) joined tables using Django Models?
For example:
class Album(models.Model):
artist = models.ForeignKey(Musician)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
class Song(models.Model):
album = models.ForeignKey(Album)
name = models.CharField(max_length=100)
num_stars = models.IntegerField()
SELECT * from album, song where (album.id = song.album_id) and (album.artist_id = X)
Django querysets want to return lists of model instances, which doesn't quite map to the the SQL notion of returning data from multiple tables joined into a single result table.
To achieve the effect of the SQL query you provide as an example, you could do one of the following:
1) If you want to iterate over your data by Songs, you can limit the songs you query by the artist id, like so:
songs = Song.objects.filter(album__artist__id=123)
for song in songs:
print song.name
print song.album.name
... do what ever you need to do with song here ...
If you are concerned about performance, you can add select_related() to your queryset.
# the depth argument tells django to only follow foreign key relationships
# from Song one level deep
songs = Song.objects.select_related(depth=1).filter(album__artist__id=123)
for song in songs:
# django has now already fetched the album data, so accessing it
# from the song instance does not cause an additional database hit
print song.album.name
2) If you want to iterate over your data by Album, you can do things like so:
albums = Album.objects.filter(artist__id = 123)
for album in albums:
for song in album.song_set.all():
print song.name
print album.name
... etc ...
You can perform raw sql query, but that's probably not the thing you want to do. Explain how your Models look like and what you want to achieve and probably django ORM will be enough to perform the query.

Django ORM: count a subset of related items

I am looking to find a way to annotate a queryset with the counts of a subset of related items. Below is a subset of my models:
class Person(models.Model):
Name = models.CharField(max_length = 255)
PracticeAttended = models.ManyToManyField('Practice',
through = 'PracticeRecord')
class Club(models.Model):
Name = models.CharField(max_length = 255)
Slug = models.SlugField()
Members = models.ManyToManyField('Person')
class PracticeRecord(PersonRecord):
Person = models.ForeignKey(Person)
Practice = models.ForeignKey(Practice)
class Practice(models.Model):
Club = models.ForeignKey(Club, default = None, null = True)
Date = models.DateField()
I'm looking to make a queryset which annotates the number of club specific practices attended by a person. I can already find the total number of practices by that person with a query of Person.objects.all().annotate(Count('PracticeRecord'))
However I would like someway to annotate the number of practices that a person attends for a specific club.
I would prefer something using the django ORM without having to resort to writing raw SQL.
Thanks.
However I would like someway to annotate the number of practices that a person attends for a specific club.
Let us see.
First, find the specific club.
club = Club.objects.get(**conditions)
Next, filter all Persons who have practiced at this club.
persons = Person.objects.filter(practicerecord__Practice__Club = club)
Now, annotate with the count.
q = persons.annotate(count = Count('practicerecord'))
Edit
I was able to successfully make this work in my test setup: Django 1.2.3, Python 2.6.4, Postgresql 8.4, Ubuntu Karmic.
PS: It is a Good Idea™ to use lower case names for the fields. This makes it far easier to use the double underscore (__) syntax to chain fields. For e.g. in your case Django automatically creates practicerecord for each Person. When you try to access other fields of PracticeRecord through this field you have to remember to use title case.
If you had used lower case names, you could have written:
persons = Person.objects.filter(practicerecord__practice__club = club)
# ^^ ^^
which looks far more uniform.
PPS: It is Count('practicerecord') (note the lower case).
I'm afraid that raw sql is the only option here. Anyway it's not that scary and hard to manage if you put it to model manager.