Django ORM queries - python

I've these tables:
class ContestQuestions(models.Model):
contest = models.ForeignKey(Contest, on_delete=models.CASCADE,
related_name='contest_question_contest')
quetions = models.ForeignKey(Question, on_delete=models.CASCADE,
related_name='contest_question_questions')
class UserResponse(models.Model):
user = models.ForeignKey(User, on_deleted=models.CASCADE, related_name='user_response')
response = models.ForeignKey(Answer, on_delete=models.CASCADE,
related_name='user_answer')
Other related tables I've:
class Contest(models.Model):
name = charfield
date = charfield
is_active = bool
class Question(models.Model):
title = charfield
created_at = datetimefield
class Answer(models.Model):
question = FK(Question)
answer = charfield #4 options of 1 question
is_true = bool
I need to get some stat about every quiz. So from every quiz, I want to know the top 5 most correctky answered questions and most incorrectly answered questions and total number of people who attempted that question.
How can I write a query for it?

I would add a method to your Contest model so that you can access this data like so:
class Contest(models.Model):
...
def top_answered_questions(self, correct=True):
return Questions.objects.filter(
contest_question_questions__contest=self
).annotate(
correct_answers=Count('answer', filter=Q(answer__is_true=correct))
).order_by(correct_answers)
You can then call contest.top_answered_questions()[:5] and change the correct parameter to get correctly and incorrectly answered questions.

Related

Realizing rating in django

What is a better way of realizing rate field in model. Now I have this one:
class Story(models.Model):
...
rate = models.(help here)
class Rating(models.Model):
rate = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(10.0)])
story = models.ForeignKey(Story, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
Or there is another way of doing this?
As #Liudvikas Bajarunas said, it's enough to define story as a foreign key on the Rating model. You can access the story ratings using rating_set:
story_ratings = story.rating_set.all()
See the documentation on following relationships backwards for more info.
You can combine that approach with aggregation to get the average rating of a story:
class Story(models.Model):
...
#property
def average_rating(self):
return self.rating_set.all().aggregate(Avg('rate'))['rate__avg']
There are some improvements that you can make:
It is better to refer to the user model with the AUTH_USER_MODEL setting [Django-doc] to refer to the user model, since you can later change your mind about it;
You probably want to make user and story unique together, such that a user can not make two ratings for the same story;
some databases, like PostgreSQL allow us to enforce range constraints at the database level, and thus make it more safe.
we thus can rewrite this to:
from django.conf import settings
from django.db import models
from django.db.models import CheckConstraint, Q, UniqueConstraint
class Rating(models.Model):
rate = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(10.0)])
story = models.ForeignKey(Story, on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class Meta:
constraints = [
CheckConstraint(check=Q(rate__range=(0, 10)), name='valid_rate'),
UniqueConstraint(fields=['user', 'story'], name='rating_once')
]
You should either go with a through field like this:
class Story(models.Model):
rates = models.ManyToManyField(User, through=Rating)
class Rating(models.Model):
rate = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(10.0)])
story = models.ForeignKey(Story, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
or you can do it your way with a separate model which in this case your either should remove the rate field from Story model or remove the story field from Rating model:
class Story(models.Model):
...
# rate = models.(help here) No need anymore
class Rating(models.Model):
rate = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(10.0)])
story = models.ForeignKey(Story, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
and your queryset will be something like this:
story.rating_set.all()
Which will include all the ratings for the selected story instance.

What's the best way to implement django models for multiple choice against multiple strings

I'm working on a project using Django(2.2) and Python(3.7) in which I have to implement models for a specific scenrio:
I have a Vote in which we have five questions like:
DELIVERING VALUE
EASY TO RELEASE
FUN
HEALTH OF CODEBASE
TEAMWORK
and each of these questions has options in the form of dropdown as:
-- Select --
--helpful--
--not helpful--
--disaster--
and all of these votes will belong to a group and later I need to perform some aggregations to get the total no of votes for of these options (like helpful) against each question (like DELIVERING VALUE).
Here's how currently I have implemented this scenario in Django models:
From models.py:
class UserGroup(models.Model):
email = models.EmailField(primary_key=True)
group = models.CharField(max_length=250, default='notingroup')
def __str__(self):
return self.group
VOTE_CHOICES = (
('helpful', "helpful"),
('meh', 'meh'),
('disaster', 'disaster')
)
class VotingValues(models.Model):
value1 = models.CharField(max_length=40)
value2 = models.CharField(max_length=40)
value3 = models.CharField(max_length=40)
value4 = models.CharField(max_length=40)
value5 = models.CharField(max_length=40)
score1 = models.CharField(choices=VOTE_CHOICES, max_length=20)
score2 = models.CharField(choices=VOTE_CHOICES, max_length=20)
score3 = models.CharField(choices=VOTE_CHOICES, max_length=20)
score4 = models.CharField(choices=VOTE_CHOICES, max_length=20)
score5 = models.CharField(choices=VOTE_CHOICES, max_length=20)
user = models.EmailField(max_length=255)
group = models.CharField(max_length=250, default='notingroup')
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.user + ' \'s ' + 'vote in ' + self.group
is there a better approach to implement this scenario?
I really don't like puting the questions inside same model as answers. maybe it's better idea to put questions in another model (something like Question)
class Question(models.Model):
q1 = models.CharField(max_length=40)
q2 = ...
class VotingValues(models.Model):
question = models.ForeignKey(Question)
a1 = ...
I think this is cleaner compared to your solution, but you can think more about it. if it's just a one time thing above answer is good enough but if you might need to create more surveys with more or less questions it's clear that for each survey you need to change current models or create new ones
to solve this issue, first thing comes to my mind is putting questions and answers inside a json field or array field
class Question(models.Model):
q = JSONField()
class VotingValues(models.Model):
question = models.ForeignKey(Question)
a = JSONField()
(note that I don't know how aggregated queries work with json field so you might need to look it up)
other thing comes to my mind is instead of using json field add a new model to store individual questions:
class Question(models.Model):
question = models.CharField
group = foreign key to question group
class QuestionGroup(models.Model):
name = models.CharField
class Vote(models.Model):
question = foreign key to question
score = ...
for current survey: you create 1 question_group and add 5 questions for that group. when user votes for that question_group you add 5 rows in vote table (one row for each question in question group)

Searching by related fields in django admin

I've been looking at the docs for search_fields in django admin in the attempt to allow searching of related fields.
So, here are some of my models.
# models.py
class Team(models.Model):
name = models.CharField(max_length=255)
class AgeGroup(models.Model):
group = models.CharField(max_length=255)
class Runner(models.Model):
"""
Model for the runner holding a course record.
"""
name = models.CharField(max_length=100)
agegroup = models.ForeignKey(AgeGroup)
team = models.ForeignKey(Team, blank=True, null=True)
class Result(models.Model):
"""
Model for the results of records.
"""
runner = models.ForeignKey(Runner)
year = models.IntegerField(_("Year"))
time = models.CharField(_("Time"), max_length=8)
class YearRecord(models.Model):
"""
Model for storing the course records of a year.
"""
result = models.ForeignKey(Result)
year = models.IntegerField()
What I'd like is for the YearRecord admin to be able to search for the team which a runner belongs to. However as soon as I attempt to add the Runner FK relationship to the search fields I get an error on searches; TypeError: Related Field got invalid lookup: icontains
So, here is the admin setup where I'd like to be able to search through the relationships. I'm sure this matches the docs, but am I misunderstanding something here? Can this be resolved & the result__runner be extended to the team field of the Runner model?
# admin.py
class YearRecordAdmin(admin.ModelAdmin):
model = YearRecord
list_display = ('result', 'get_agegroup', 'get_team', 'year')
search_fields = ['result__runner', 'year']
def get_team(self, obj):
return obj.result.runner.team
get_team.short_description = _("Team")
def get_agegroup(self, obj):
return obj.result.runner.agegroup
get_agegroup.short_description = _("Age group")
The documentation reads:
These fields should be some kind of text field, such as CharField or TextField.
so you should use 'result__runner__team__name'.

django query to use annotate with a filter for each annotations

I have the following database model -
class ObjectDetail(models.Model):
title = models.CharField()
img = models.ImageField()
description = models.TextField()
uploaded_by = models.ForeignKey(User, related_name='uploaded_by')
class Vote(models.Model):
vote_type = models.BooleanField(default = False)
voted_by = models.ForeignKey(User, related_name='voted_by')
voted_for = models.ForeignKey(User, related_name='voted_for')
shared_object = models.ForeignKey(ObjectDetail, null=True, blank=True)
dtobject = models.DateTimeField(auto_now_add=True)
Now, in my views I want to get the number of upvotes and downvotes for each of the objects.
One way of doing it would be to add a function under class ObjectDetails
as follows -
#property
def upvote(self):
upvote = Vote.objects.filter(shared_object__id = self.id,
vote_type = True).count()
return upvote
#property
def downvote(self):
downvote = Vote.objects.filter(shared_object__id = self.id,
vote_type = False).count()
return downvote
But this would, cause two queries for each of the object, present in the database.
Another method would be to use annotate
obj = ObjectDetail.objects.select_related().filter(FILTER_CONDITION).annotate(upvote=Count('vote'), downvote=Count('Vote')).order_by('-shared_time')
The above statement is wrong in a sense that it just gives me the counts of votes, irrespective of upvotes and downvotes.
if you see into the model, you can get upvote by filtering vote__vote_type = True and a downvote by vote__vote_type=False
How to add these two conditions/filters in the query statement?
So my prime objective is to get the two values of upvote and downvote for each of the items, with making least db queries, such that in the template, if i do
{{ obj.upvote }} I can get the number of upvote on the object, and the similar for downvote.
Please let me know, thanks.
Did you try with values() to group the different vote_types?
https://docs.djangoproject.com/en/dev/topics/db/aggregation/#values
Vote.objects.select_related().filter(FILTER_CONDITION).values('shared_object', 'vote_type').annotate(vote_count=Count('vote_type'))
At this point you can use regroup in the template to loop on ObjectDetailss
https://docs.djangoproject.com/en/dev/ref/templates/builtins/#regroup

Error passing User to ForeignKey in Django

I'm trying to pass a User model as the parameter for a ForeignKey in my models.py file, but I am getting the error TypeError: int() argument must be a string or a number, not 'User'.
Here are my files, please tell me what I'm doing wrong:
models.py
from django.db import models
class Lesson(models.Model):
name = models.CharField(max_length=30)
author = models.ForeignKey('auth.User')
description = models.CharField(max_length=100)
yt_id = models.CharField(max_length=12)
upvotes = models.IntegerField()
downvotes = models.IntegerField()
category = models.ForeignKey('categories.Category')
views = models.IntegerField()
favorited = models.IntegerField()
def __unicode__(self):
return self.name
populate_db.py
from django.contrib.auth.models import User
from edu.lessons.models import Lesson
from edu.categories.models import Category
users = User.objects.all()
cat1 = Category(1, 'Mathematics', None, 0)
cat1.save()
categories = Category.objects.all()
lesson1 = Lesson(1, 'Introduction to Limits', users[0], 'Introduction to Limits', 'riXcZT2ICjA', 0, 0, categories[0], 0, 0)
lesson1.save() # Error occurs here
Using positional arguments here is very confusing and appears to be the cause.
I can reproduce your error by using positional arguments on a ForeignKey on one of my own models. Using kwargs solves the problem.
I'm not even interested in looking into why - I have never used confusing positional arguments to populate a model (seems like they would break ALL the time too with confusing messages if you ever modified your model)
Edit: or much worse, a silent error with input fields going to the wrong model fields over time.
You should use keyword arguments as well as simply the initialization it by using default field values.
class Lesson(models.Model):
name = models.CharField(max_length=30)
author = models.ForeignKey('auth.User')
description = models.CharField(max_length=100)
yt_id = models.CharField(max_length=12)
upvotes = models.IntegerField(default=0)
downvotes = models.IntegerField(default=0)
category = models.ForeignKey('categories.Category')
views = models.IntegerField(default=0)
favorited = models.IntegerField(default=0)
def __unicode__(self):
return self.name
lesson1 = Lesson(name='Introduction to Limits', author=users[0], description='Introduction to Limits', yt_id='riXcZT2ICjA', category=categories[0])
lesson1.save()
from django.contrib.auth import User (forgot exact import call)
author = models.ForeignKey(User)
Edit (additions): I would import the User the way that I stated and use 'author.category' for the other relationships. This has been resolved though by people who know more about Django than I do.

Categories