Annotate on reverse many-to-many - django

I'm trying to work out why this doesn't work:-
class A(models.Model):
contacts = models.ManyToManyField(Contact)
class Contact(models.Model):
name = models.CharField()
If I try and get a count of how many A there are with multiple contacts:-
A.objects.annotate(num_contacts=Count('contacts')).filter(num_contacts__gt=1).count()
there are 10.
but if I have a particular contact and I want to get a count of how many A's they are connected to that have more than 1 contact on them:-
B.a_set.annotate(num_contacts=Count('contacts')).filter(num_contacts__gt=1).count()
I get 0. The num_contacts count always comes out as 1, even when the A has more than 1 contact.
I must have missed something silly but I can't see it. Any ideas?

Related

How to filter values from 3 different models in django

I have 3 classes and i want to filter them based on criteria taken form 3 classes. I am very new to django and especially model. Need your help.
Student, group and nondemandgroup are tables in my Db.
class Students():
name=models.CharField(max_length=200)
surname=models.CharField(max_length=200)
class Group20():
name=models.CharField(max_length=200)
studentss=models.ManyToManyField(Students)
math=models.DecimalField(decimal_places=2,max_digits=1000)
english=models.DecimalField(decimal_places=2,max_digits=1000)
class Nondemandgroup():
name=models.CharField(max_length=200)
studentss=models.ManyToManyField(Students)
acting=models.DecimalField(decimal_places=2,max_digits=1000)
cooking=models.DecimalField(decimal_places=2,max_digits=1000)
i want to get list of student who's final grade is met by fowlloing criteria:
final_grade = group20.objects.filter(math__gt=60, ((math+english+acting)/3)__gt=70)
acting is within nondemandgroup class so my final_grade doesn't work it says no such column as acting.
How to to make acting column work ? I tried Foreign key but it does not work and gives an error.
Or should i create a new model and filter within it where i will create foreign keys of 3 models ?
I am quite confused as i am very new to Django and models are confusing. Explored web however in my case i misunderstand smth and the formula does not work.
Let's go step by step. We want students matching the a given criteria.
First thing we see wrong is student is not connected to group in any way. So let's connect the students with groups
from django.db import models
class Student(models.Model):
name=models.CharField(max_length=200)
surname=models.CharField(max_length=200)
class Group20(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
name=models.CharField(max_length=200)
math=models.DecimalField(decimal_places=2,max_digits=1000)
english=models.DecimalField(decimal_places=2,max_digits=1000)
class Nondemandgroup(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
name=models.CharField(max_length=200)
acting=models.DecimalField(decimal_places=2,max_digits=1000)
cooking=models.DecimalField(decimal_places=2,max_digits=1000)
Now let's come to the part calculating the final grade
Lets get all user's from group20 model who have more than 60 in math. We also need the math and english to calculate average score along with acting. So let's get that value as well.
group20_objs = Group20.objects.filter(math__gt=60).values_list('student__name', 'math', 'english')
This will give value in the following format
[("studentA", "math_score_of_A", "english_score_of_A"), ...]
[("Chantel", "100", "100"), ("Chantel1", "90", "85"),..]
Now lets get the students who's average of math, english and acting is above 70.
final_students = []
for item in group20_objs:
student_name, math_score, english_score = item
# Get acting score for user
# Hoping student will have record here as well
acting_score = Nondemandgroup.objects.filter(student__name=student_name).first(). acting
if (acting_score + math_score + english_score)/3 >= 70:
final_students.append(student_name)
Now final_students list will contains students names with grades higher than 70. Do try to mix up the models if you want.
Also i recommend going through django models docs for a better understanding

Trying to get Count('foo__bar') inside annotate()

Im trying to get the following line works:
artists = models.Artists.objects.all().annotate(tot_votes=Count('votes__work')).order_by('-tot_votes')
(i.e. I want simply to annotate the count of all votes corresponding to every artist.)
But whenever the above line is executed I get the FieldError error Cannot resolve keyword 'votes' into field.
Where
class Votes(models.Model):
work = models.OneToOneField(Works, models.DO_NOTHING, db_column='work')
user = models.OneToOneField(AuthUser, models.DO_NOTHING, db_column='user')
and
class Works(models.Model):
artist = models.ForeignKey(Artists, models.DO_NOTHING, db_column='artist')
# other irrelevant fields
OR simply the relation between the tables is (Votes --> Works --> Artists)
I think you've got that relationship the wrong way round, haven't you? Artist doesn't have any kind of direct relationship to Votes, it only does to Works. The annotate call should be to Count('work__votes').
(Also note that normal naming convention for Django is to use singular names: Vote, Work, Artist.)

Django - reverse search for several different object with foreign key reference

I have stumbled upon a problem, which I can not for the life of me seem to figure out. I even talked to a professor in data handling, without getting closer to a solution. So now I'm reaching out to you guys, hoping to get some suggestions on what to do.
In short, I am trying to create a system holding information about cars, where cars can get parts installed.
Here is the specification for the database:
A car has exactly one owner. An owner can have many cars.
The owner shall be able to groups its cars, so that he can easily add the same part to more than one vehicle at once. The group should then have a name.
A vehicle can have speakers, seats, wheels, all with different sets of attributes. Note that the car may have zero of these parts. It may also have more than one of a part.
The speakers must contain info about prize and manufacturer.
The seats must contain info about height, width and length.
The wheels must contain info about diameter and manufacturer.
It must also be possible to priorities which part should be added first.
The way I have solved this, is by letting a car belong to a group, regardless of whether it should belong to a group or not. If it does not belong to a group, it will be the only car in that group.
I got the requirement about prioritizing part installation today, so this is not implemented in my solution bellow.
My models look like this:
class CarGroup(models.Model):
name = models.CharField(max_length=30)
owner = models.ForeignKey(owners.models.Owner) #Another model
class Car(models.Model):
name = models.CharField(max_length=100, blank=True, null=True)
group = models.ForeignKey(CarGroup)
# Abstract Part Type
class PartType(models.Model):
usedBy = models.ForeignKey(CarGroup)
class Meta:
abstract = True
class Speaker(PartType):
prize = models.DecimalField()
manufacturer = models.CharField(max_length=100)
class Seat(PartType):
height = models.DecimalField()
width = models.DecimalField()
length = models.DecimalField()
class Wheel(PartType):
diameter = models.DecimalField()
manufacturer = models.CharField(max_length=100)
The problem I have with this design, is that i don't know how to get the data out of the database. If I have a car, and want to show all the parts that belong to this car, I must now search through every Part-table (Seat, Wheel, Speaker), to see if any of the objects belongs to the car.
That means I have to do the following:
speakers = Speaker.objects.filter(group=CarGroup.id)
seats = Seat.objects.filter(group=CarGroup.id)
wheels = Whell.objects.filter(group=CarGroup.id)
And then check if these query sets contain any data.
I refuse to believe that this is my best option. Do you guys have any suggestions?

Django Count() in multiple annotations

Say I have a simple forum model:
class User(models.Model):
username = models.CharField(max_length=25)
...
class Topic(models.Model):
user = models.ForeignKey(User)
...
class Post(models.Model):
user = models.ForeignKey(User)
...
Now say I want to see how many topics and posts each users of subset of users has (e.g. their username starts with "ab").
So if I do one query for each post and topic:
User.objects.filter(username_startswith="ab")
.annotate(posts=Count('post'))
.values_list("username","posts")
Yeilds:
[('abe', 5),('abby', 12),...]
and
User.objects.filter(username_startswith="ab")
.annotate(topics=Count('topic'))
.values_list("username","topics")
Yields:
[('abe', 2),('abby', 6),...]
HOWEVER, when I try annotating both to get one list, I get something strange:
User.objects.filter(username_startswith="ab")
.annotate(posts=Count('post'))
.annotate(topics=Count('topic'))
.values_list("username","posts", "topics")
Yields:
[('abe', 10, 10),('abby', 72, 72),...]
Why are the topics and posts multiplied together? I expected this:
[('abe', 5, 2),('abby', 12, 6),...]
What would be the best way of getting the correct list?
I think Count('topics', distinct=True) should do the right thing. That will use COUNT(DISTINCT topic.id) instead of COUNT(topic.id) to avoid duplicates.
User.objects.filter(
username_startswith="ab").annotate(
posts=Count('post', distinct=True)).annotate(
topics=Count('topic', distinct=True)).values_list(
"username","posts", "topics")
Try adding distinct to your last queryset:
User.objects.filter(
username_startswith="ab").annotate(
posts=Count('post')).annotate(
topics=Count('topic')).values_list(
"username","posts", "topics").distinct()
See https://docs.djangoproject.com/en/1.3/ref/models/querysets/#distinct for more details, but basically you're getting duplicate rows because the annotations span multiple tables.

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