Django querysets select average and best result - django

I have a model like this:
class Quiz(models.Model):
user = models.ForeignKey(User, unique=True)
school_class = models.CharField(max_length=3, choices=klasa_choices)
points = models.DecimalField(max_digits=5, decimal_places=2,default=0)
date = models.DateTimeField(null=True,blank=True)
active = models.BooleanField(default=False)
Basically I'd like to get the average score (points) and the best result with the corresponding user for each school_class . Can this be done easily? (i.e without additional computing?)
So far I've come to:
Quiz.objects.values('school_class').annotate(avg=Avg('points'),max=Max('points')).order_by('-avg')
but how do I also get the user with the best score?

You need a second query. You can use the results of the first one to save on ordering your tables:
quizzes = Quiz.objects.values('school_class').annotate(avg=Avg('points'),max=Max('points')).order_by('-avg')
Quiz.objects.values('user','school_class').filter(points=quizzes.max)
If you only want one, add .latest('date'), .latest('-date'), or [0] to get just one.

Related

Annotating values from filtered related objects -- Case, Subquery, or another method?

I have some models in Django:
# models.py, simplified here
class Category(models.Model):
"""The category an inventory item belongs to. Examples: car, truck, airplane"""
name = models.CharField(max_length=255)
class UserInterestCategory(models.Model):
"""
How interested is a user in a given category. `interest` can be set by any method, maybe a neural network or something like that
"""
user = models.ForeignKey(User, on_delete=models.CASCADE) # user is the stock Django user
category = models.ForeignKey(Category, on_delete=models.CASCADE)
interest = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)])
class Item(models.Model):
"""This is a product that we have in stock, which we are trying to get a User to buy"""
model_number = models.CharField(max_length=40, default="New inventory item")
product_category = models.ForeignKey(Category, null=True, blank=True, on_delete=models.SET_NULL, verbose_name="Category")
I have a list view showing items, and I'm trying to sort by user_interest_category for the currently logged in user.
I have tried a couple different querysets and I'm not thrilled with them:
primary_queryset = Item.objects.all()
# this one works, and it's fast, but only finds items the users ALREADY has an interest in --
primary_queryset = primary_queryset.filter(product_category__userinterestcategory__user=self.request.user).annotate(
recommended = F('product_category__userinterestcategory__interest')
)
# this one works great but the baby jesus weeps at its slowness
# probably because we are iterating through every user, item, and userinterestcategory in the db
primary_queryset = primary_queryset.annotate(
recommended = Case(
When(product_category__userinterestcategory__user=self.request.user, then=F('product_category__userinterestcategory__interest')),
default=Value(0),
output_field=IntegerField(),
)
)
# this one works, but it's still a bit slow -- 2-3 seconds per query:
interest = Subquery(UserInterestCategory.objects.filter(category=OuterRef('product_category'), user=self.request.user).values('interest'))
primary_queryset = primary_queryset.annotate(interest)
The third method is workable, but it doesn't seem like the most efficient way to do things. Isn't there a better method than this?

Django Foreignkey with edit fields and select field how to put in form

I apologize if that question was raised before. But I have been struggling with this for weeks and couldn't find anything useful.
I have the following problem (it is simplified a lot but essentially my problem is presented)
I have a Model that has a lot of fields. It's called
class DocAide(models.Model):
id = models.AutoField(primary_key=True)
pulse = models.DecimalField('Pulse', max_digits=3, decimal_places=0)
weight = models.DecimalField('Weight (kg)', max_digits=3, decimal_places=0)
bp_sys = models.DecimalField('BP Sys', max_digits=3, decimal_places=0)
bp_dia = models.DecimalField('BP Dia', max_digits=3, decimal_places=0)
temperature = models.DecimalField('Temp. deg C', max_digits=2, decimal_places=1)
drugs = models.ManyToManyField(Drug, blank=True)
date = models.DateField(editable=False, default=timezone.now)
doctors_notes = models.TextField('Patient is complaining about:', default='')
note = models.TextField(max_length=100, default='')
The ForeignKey Drugs has Names of drugs with quantity I would like to have the possibility to select multiple drugs but with edit fields that show what dosage needs to be taken and when, it should be like a prescription. The Model looks like this:
class Drug(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, default='')
QUANTITY_STR = ['Bottle', 'Tablet' 'Injection', 'Capsules', 'other']
QUANTITY = ((str, str) for str in QUANTITY_STR)
quantity = models.CharField(max_length=2, choices=QUANTITY, default='Bottle')
category = models.CharField(max_length=150, default='')
strength = models.CharField(max_length=150, default='')
in_supply_stock = models.PositiveIntegerField(default=0)
in_main_stock = models.PositiveIntegerField(default=0)
date = models.DateField(editable=False, default=timezone.now)
charge = models.PositiveIntegerField(default=0)
morning = models.CharField(validators=[int_list_validator], max_length=3, default=0)
midday = models.CharField(validators=[int_list_validator], max_length=3, default=0)
evening = models.CharField(validators=[int_list_validator], max_length=3, default=0)
night = models.CharField(validators=[int_list_validator], max_length=3, default=0)
days = models.CharField(validators=[int_list_validator], max_length=3, default=0)
tablets = models.CharField(validators=[int_list_validator], max_length=3, default=0)
How can I accomplish that in a form or template. I tried with Inlineformset it doesn't work. Also later I would like to have them preselected as well.
But for now I would like to have a button that produces a line with a dropdown list of the drugs and the edit fields of the model.
Thank you in advance.
As others have said, a Drug object should not have a quantity associated with it, but a prescription "entry" should.
I think this is the Model structure you need:
QUANTITY_STR = ['Bottle', 'Tablet' 'Injection', 'Capsules', 'other']
class DocAide(models.Model):
# same properties but remove `drugs` from this model
class Drug(models.Model):
# same properties but remove `quantity` property
class Prescription(model.Model):
drug = model.ForeignKey(to=Drug, related_name='prescriptions')
doc_aide = model.ForeignKey(to=DocAide, related_name='prescriptions')
quantity = models.IntegerField()
qty_container = models.CharField(max_length=10, choices=QUANTITY_STR, default=QUANTITY_STR[0])
I changed a few things for you assuming I understood your business logic correctly. Such as how the quantity field works.
I created two fields to describe the quantity. quantity holds the numerical value, and qty_container holds the container's name if you will, like "Bottle", "Injection" and so on.
qty_container has a max_length equal to the number of characters in the word "Injection" since it is the largest word that might fit in this field. You had the default of that field be greater than the max_length which would cause an error.
Now I'm not sure why you wanted to save a tuple of two strings in the quantity field so I ignored that, but if you can comment on your intended logic here I might be able to edit the answer.
Anyway, the Prescription model.
This model will act as an intermediary between Drug and DocAide, and it is the one that will hold the quantity information. I've linked it with Drug and DocAide using foreign keys and set the related_name to suitable names. These "related_names" you'll find show up in the referenced model. So for example if you can do
doc_aide = DocAide.objects.get(pk=1)
for presc in doc_aide.prescriptions:
print(presc.drug.name)
print(presc.quantity)
This means that one DocAide object will be linked with one or many Prescription objects, each of those holds quantity info and is linked with a Drug object:
DocAide (id, etc) >> Prescription (doc_aide_id, drug_id, qty_info) >> Drug (id, etc)
As a side note, after you're sure everything works and you're good to go, you might need to look into query optimization in Django, because as it is written now, it's pretty unoptimized. But don't worry about optimization until you've finished and your code works correctly.
You likely want to move some fields of your Drug model to an intermediary one that defines a foreign key to both your Drug and DocAide models.
This model should then be used as the through option of your DocAide.drugs field.
If you are using the admin you'll be able to rely on inlines to display an input form that allows selecting the desired Drug and annotating extra fields otherwise you'll likely have to build your own mechanism that relies on ModelFormSet.

Django two parameter filter query with foreign key

My Django models look like this:
class User(models.Model):
userid = models.CharField(max_length=26,unique=True)
#some more fields that are currently not relevant
class Followers(models.Model):
user = models.ForeignKey('User',related_name='usr')
coins = models.IntegerField()
followers = models.CharField(max_length=26, null=True, blank=True)
I would now like to make a filter query in my Followers table selecting every entry where users have ID x and followers have ID y (I expect to get one result from the query).
To visualize what I have tried and know won't work is this:
queryfilter = Followers.object.filter(followers=fid, user=uid)
and this:
queryfilter = Followers.object.filter(followers=fid, user__userid=uid)
In the end I would like to access the coins:
c = queryfilter.coins
It may be possible that I cannot do it with one single query and need two, since I am trying to do a filter query with two tables involved.
Firstly, I have modified your 'Followers' model (for naming convention).
models.py
class Follower(models.Model):
user = models.ForeignKey('User', related_name='followers')
coins = models.IntegerField()
key = models.CharField(max_length=26, null=True, blank=True)
Your queryset should be ..
views.py
#coins
coins = Follower.objects.filter(key=fid, user__userid=uid).get().coins

How to create a query based on fields of another queryset

I have an app that saves Student quizzes using two models:
StudentQuiz saves the questions the student was asked
StudentQuestion saves the responses of the student for each of those questions.
class StudentQuestion(models.Model):
user = models.ForeignKey(User)
question = models.ForeignKey('Question') # comes from a Questions table
answer = models.CharField("Answer", max_length = 100, blank=True, null=True)
q_score = models.IntegerField("Score", blank=True, null=True)
class StudentQuiz(models.Model):
user = models.ForeignKey(User)
date = models.DateField("Quiz Date", blank=True, null=True)
total_score = models.IntegerField("Score", blank=True, null=True)
ques1 = models.ForeignKey(StudentQuestion, related_name='q1')
ques2 = models.ForeignKey(StudentQuestion, related_name='q2')
ques3 = models.ForeignKey(StudentQuestion, related_name='q3')
ques4 = models.ForeignKey(StudentQuestion, related_name='q4')
ques5 = models.ForeignKey(StudentQuestion, related_name='q5')
I want to find the number of questions a student took in a certain date range for which he got a score of, say, 1.
So I create the first queryset:
quizzes_done = StudentQuiz(user=mystudent, date__gte=start_date, date__lte=end_date)
Now, I want to look at all questions that are in these StudentQuizzes and want to count the number of questions that have q_score = 1.
Currently, I am just looping over the QuerySet and doing this programmatically. But the QuerySet could be huge.
Is there a way to do this using django's DB APIs?
take a look at the docs on queries that span relationships
basically, you should just be able to reference the student_quiz associated with your StudentQuestion object in a query on StudentQestion, filtering on q_score and user, and then use __ to access the StudentQuiz properties you want (e.g. filter(student_quiz__date_gte=blah)

Django: Filter in multiple models linked via ForeignKey?

I'd like to create a filter-sort mixin for following values and models:
class Course(models.Model):
title = models.CharField(max_length=70)
description = models.TextField()
max_students = models.IntegerField()
min_students = models.IntegerField()
is_live = models.BooleanField(default=False)
is_deleted = models.BooleanField(default=False)
teacher = models.ForeignKey(User)
class Session(models.Model):
course = models.ForeignKey(Course)
title = models.CharField(max_length=50)
description = models.TextField(max_length=1000, default='')
date_from = models.DateField()
date_to = models.DateField()
time_from = models.TimeField()
time_to = models.TimeField()
class CourseSignup(models.Model):
course = models.ForeignKey(Course)
student = models.ForeignKey(User)
enrollment_date = models.DateTimeField(auto_now=True)
class TeacherRating(models.Model):
course = models.ForeignKey(Course)
teacher = models.ForeignKey(User)
rated_by = models.ForeignKey(User)
rating = models.IntegerField(default=0)
comment = models.CharField(max_length=300, default='')
A Course could be 'Discrete mathematics 1'
Session are individual classes related to a Course (e.g. 1. Introduction, 2. Chapter I, 3 Final Exam etc.) combined with a date/time
CourseSignup is the "enrollment" of a student
TeacherRating keeps track of a student's rating for a teacher (after course completion)
I'd like to implement following functions
Sort (asc, desc) by Date (earliest Session.date_from), Course.Name
Filter by: Date (earliest Session.date_from and last Session.date_to), Average TeacherRating (e.g. minimum value = 3), CourseSignups (e.g. minimum 5 users signed up)
(these options are passed via a GET parameters, e.g. sort=date_ascending&f_min_date=10.10.12&...)
How would you create a function for that?
I've tried using
denormalization (just added a field to Course for the required filter/sort criterias and updated it whenever changes happened), but I'm not very satisfied with it (e.g. needs lots of update after each TeacherRating).
ForeignKey Queries (Course.objects.filter(session__date_from=xxx)), but I might run into performance issues later on..
Thanks for any tipp!
In addition to using the Q object for advanced AND/OR queries, get familiar with reverse lookups.
When Django creates reverse lookups for foreign key relationships. In your case you can get all Sessions belonging to a Course, one of two ways, each of which can be filtered.
c = Course.objects.get(id=1)
sessions = Session.objects.filter(course__id=c.id) # First way, forward lookup.
sessions = c.session_set.all() # Second way using the reverse lookup session_set added to Course object.
You'll also want to familiarize with annotate() and aggregate(), these allow you you to calculate fields and order/filter on the results. For example, Count, Sum, Avg, Min, Max, etc.
courses_with_at_least_five_students = Course.objects.annotate(
num_students=Count('coursesignup_set__all')
).order_by(
'-num_students'
).filter(
num_students__gte=5
)
course_earliest_session_within_last_240_days_with_avg_teacher_rating_below_4 = Course.objects.annotate(
min_session_date_from = Min('session_set__all')
).annotate(
avg_teacher_rating = Avg('teacherrating_set__all')
).order_by(
'min_session_date_from',
'-avg_teacher_rating'
).filter(
min_session_date_from__gte=datetime.now() - datetime.timedelta(days=240)
avg_teacher_rating__lte=4
)
The Q is used to allow you to make logical AND and logical OR in the queries.
I recommend you take a look at complex lookups: https://docs.djangoproject.com/en/1.5/topics/db/queries/#complex-lookups-with-q-objects
The following query might not work in your case (what does the teacher model look like?), but I hope it serves as an indication of how to use the complex lookup.
from django.db.models import Q
Course.objects.filter(Q(session__date__range=(start,end)) &
Q(teacher__rating__gt=3))
Unless absolutely necessary I'd indeed steer away from denormalization.
Your sort question wasn't entirely clear to me. Would you like to display Courses, filtered by date_from, and sort it by Date, Name?