Django: SQL query with distinct on model function - django

Hi Stackoverflow people,
in my current project, I have the following model structure:
class Project(models.Model):
student_id = models.ForeignKey(User)
class_a = models.ForeignKey(ClassA)
class_b = models.ForeignKey(ClassB)
...
class ClassA(models.Model):
teacher_name = models.CharField(...)
class ClassB(models.Model):
teacher_name = models.CharField(...)
ClassA and ClassB are very different and only they only have the teacher_name in common, therefore I keep it in separate classes.
In addition, in each project only class_a or class_b can contain a ForeignKey (it can not be assigned to two classes).
When I display all projects and I want to list the names of the teacher, therefore I created a little model function, which returns the teacher's name:
def get_teacher_name(self):
if self.class_a:
return self.class_a.teacher_name
if self.class_b:
return self.class_b.teacher_name
Now, I want to write a query which returns the names of teachers for a student_id and only returns the teachers name once, e.g.:
For Student X
Project Teacher
Biology Mrs. X
Physics Mr. Y
(-> Math Mr. Y should be disregarded)
How should I structure the Django query? I would like to do
Project.objects.filter(student_id=user.pk).values('get_teacher_name').distinct(), but this is not possible. Is there a solution for it?
Thank you for your suggestions!

You'er looking for values and annotate
https://docs.djangoproject.com/en/dev/topics/db/aggregation/#order-of-annotate-and-values-clauses
Which will give you unique values for a values call.
import itertools
class_a_names = user.project_set.values_list('class_a__teacher_name', flat=True).annotate()
class_b_names = user.project_set.values_list('class_b__teacher_name', flat=True).annotate()
unique_teachers = set(itertools.chain(class_a_names, class_b_names))

Related

querying a django model with foreign key relationship

In my models.py I have a model 'courses'. A course can have one name and one description but multiple links and instructors. I tried to stablish this using foreign key relationship. Now, lets say I want access the name of a particular course, I can do so by c.name where c would be an object obtained by using a filter query. But how would I access lets say the second instructor of that course? Also how would I add a new instructor to that course?
class courses(models.Model):
name=models.CharField(max_length=200)
description=models.TextField()
class links(models.Model):
link=models.CharField(max_length=200)
course=models.ForeignKey(courses,on_delete=models.CASCADE)
class instructors(models.Model):
inst=models.CharField(max_length=200)
course=models.ForeignKey(courses,on_delete=models.CASCADE)
Adding a new instructor requires a courses object. So, you can do the following,
course = courses.objects.create(name="OS", description="course desc")
instructor = instructors.objects.create(inst="name", course=course)
How are you defining the order of instructors? If it is the creation of instructors object. then, you can access the second instructor of a course as the following.
all_instructors = instructors.objects.filter(course=course)
if len(all_instructors) > 1:
second_instructor = all_instructors[1]
N.B. You should rename your models to singular noun i.e. Course, Link, Instructor

Is there a simple way to "extract" a list of reated objects from a django queryset?

I have a rather complicated model setup:
class Tournament(models.Model):
pass
class Category(models.Model):
pass
class Discipline(models.Model):
pass
class Judge(models.Model):
pass
class Evaluation(models.Model):
tournament = models.ForeignKey(Tournament)
judge = models.ForeignKey(Judge)
discipline = models.ForeignKey(Discipline)
category = models.ForeignKey(Category)
Of course, those models are incomplete, but their contents aren't important for the problem... Given a Tournament, I need to get the distinct "Category" objects, along with the "Disciplines" evaluated for each of these.
I have tried this:
eval_set = tournament_object.evaluation_set.distinct('category')
categories = [jdgt.categoria for jdgt in eval_set]
Isn't there a cleaner way? I feel like there should be a sort of "extract('category')" that I could call on the queryset...
Any ideas will be appreciated!!!!
Try this:
categories = tournament_object.evaluation_set.distinct( 'category' ).values_list('categoria', flat=True)
Read more on values_list here

Django - reverse lookups

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

Django model aggregation

I have a simple hierarchic model whit a Person and RunningScore as child.
this model store data about running score of many user, simplified something like:
class Person(models.Model):
firstName = models.CharField(max_length=200)
lastName = models.CharField(max_length=200)
class RunningScore(models.Model):
person = models.ForeignKey('Person', related_name="scores")
time = models.DecimalField(max_digits=6, decimal_places=2)
If I get a single Person it cames with all RunningScores associated to it, and this is standard behavior. My question is really simple: if I'd like to get a Person with only a RunningScore child (suppose the better result, aka min(time) ) how can I do?
I read the official Django documentation but have not found a
solution.
I am not 100% sure if I get what you mean, but maybe this will help:
from django.db.models import Min
Person.objects.annotate(min_running_time=Min('time'))
The queryset will fetch Person objects with min_running_time additional attribute.
You can also add a filter:
Person.objects.annotate(min_running_time=Min('time')).filter(firstName__startswith='foo')
Accessing the first object's min_running_time attribute:
first_person = Person.objects.annotate(min_running_score=Min('time'))[0]
print first_person.min_running_time
EDIT:
You can define a method or a property such as the following one to get the related object:
class Person(models.Model):
...
#property
def best_runner(self):
try:
return self.runningscore_set.order_by('time')[0]
except IndexError:
return None
If you want one RunningScore for only one Person you could use odering and limit your queryset to 1 object.
Something like this:
Person.runningscore_set.order_by('-time')[0]
Here is the doc on limiting querysets:
https://docs.djangoproject.com/en/1.3/topics/db/queries/#limiting-querysets

Django: construct a QuerySet inside a view?

I have models as follows:
class Place(models.Model):
name = models.CharField(max_length=300)
class Person(models.Model):
name = models.CharField(max_length=300)
class Manor(models.Model):
place = models.ManyToManyField(Place, related_name="place"))
lord = models.ManyToManyField(Person, related_name="lord")
overlord = models.ManyToManyField(Person, related_name="overlord")
I want to get all the Places attached with the relation 'lord' to a particular person, and then get the centre, using a GeoDjango method. This is as far as I've got:
person = get_object_or_404(Person, namesidx=namesidx)
manors = Manor.objects.filter(lord=person)
places = []
for manor in manors:
place_queryset = manor.place.all()
for place in place_queryset:
places.append(place)
if places.collect():
centre = places.collect().centroid
However, this gives me:
AttributeError at /name/208460/gamal-of-shottle/
'list' object has no attribute 'collect'
Can I either (a) do this in a more elegant way to get a QuerySet of places back directly, or (b) construct a QuerySet rather than a list in my view?
Thanks for your help!
The way you're doing this, places is a standard list, not a QuerySet, and collect is a method that only exists on GeoDjango QuerySets.
You should be able to do the whole query in one go by following the relations with the double-underscore syntax:
places = Place.objects.filter(manor__lord=person)
Note that your use of related_name="place" on the Manor.place field is very confusing - this is what sets the reverse attribute from Place back to Manor, so it should be called manors.