Django. How to annotate a object count from a related model - python

lets say I have a model Comments and another model Answers. I want to query all comments but also include in the query the number of answers each comment has. I thought annotate() would be useful in this case, but I just don't know how to write it down. The docs write that:
New in Django 1.8: Previous versions of Django only allowed aggregate
functions to be used as annotations. It is now possible to annotate a
model with all kinds of expressions.
So. This is an example of my models:
class Comments(models.Model):
...
class Answers(models.Model):
comment = models.ForeignKey(Comments)
And this is an example query:
queryset = Comments.objects.all().annotate(...?)
I'm not sure how to annotate the Count of answers each comment has. That is, how many answers point to each comment on the FK field comment. Is it even possible? is there a better way? Is it better to just write a method on the manager?

You need to use a Count aggregation:
from django.db.models import Count
comments = Comments.objects.annotate(num_answers=Count('answers'))

Related

Sort objects in Django admin by a model #property?

I have two models - a Task model, and a Worker model. I have a property on Worker that counts how many tasks they have completed this month.
class Task(models.Model):
# ...
completed_on = models.DateField()
class Worker(models.Model):
# ...
#property
def completed_this_month(self):
year = datetime.date.today().year
month = datetime.date.today().month
return Task.objects.filter(worker=self,
completed_on__year=year,
completed_on__month=month).count()
I've added this field to the Worker admin, and it displays correctly.
I would like to be able to sort by this field. Is there a way to do this?
Edit: It has been suggested that my question is a duplicate of this question, which uses extra(). The Django documentation strongly advises against using the extra() method, and even asks you to file a ticket explaining why you had to use it.
Use this method as a last resort
This is an old API that we aim to deprecate at some point in the future. Use it only if you cannot express your query using other queryset methods. If you do need to use it, please file a ticket using the QuerySet.extra keyword with your use case (please check the list of existing tickets first) so that we can enhance the QuerySet API to allow removing extra().

Django custom annotation function

I want to build a simple hot questions list using Django. I have a function that evaluates "hotness" of each question based on some arguments.
Function looks similar to this (full function here)
def hot(ups, downs, date):
# Do something here..
return hotness
My models for question and vote models (relevant part)
class Question(models.Model):
title = models.CharField(max_length=150)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Vote(models.Model):
question = models.ForeignKey(Question, related_name='questions_votes')
delta = models.IntegerField(default=0)
Now, the delta attribute is either positive or negative. The hot function receives number of positive votes and number of negative votes and creation date of question.
I've tried something like this, but it isn't working.
questions = Question.objects.annotate(hotness=hot(question_votes.filter(delta, > 0),question_votes.filter(delta < 0), 'created_at')).order_by('hotness')
The error I'm getting is: global name 'question_votes' is not defined
I understand the error, but I don't the correct way of doing this.
You can't use python functions for annotations. Annotation is a computation that is done on a database level. Django provides you only a set of basic computations which can be processed by the database - SUM, AVERAGE, MIN, MAX and so on... For more complex stuffs only from version 1.8 we have an API for more complex query expressions. Before Django 1.8 the only way to achieve similar functionality was to use .extra which means to write plain SQL.
So you basically have two options.
First
Write your hotness computation in plain SQL using .extra or via the new API if your Django version is >= 1.8.
Second
Create hotness field inside you model, which will be calculated by a cron job once a day (or more often depending on your needs). And use it for your needs (the hottest list).
For those looking for an updated answer (Django 2.0+) it is possible to subclass Func to generate custom functions for aggregations as per the documentation . There is a good explanation and example here about 80% of the way through the post in the "Extending with custom database functions" section.

Where do I put a function to count foreign keys in Django?

I have a Django model object, Record, which has foreign keys to two other models RecordType and Source:
class Record(models.Model):
title = models.CharField(max_length=200)
record_type = models.ForeignKey(RecordType)
source = models.ForeignKey(Source)
Question: If I want to count the number of Record objects which refer to RecordType with id "x" and Source with id "y", where is the appropriate area of code to put that function?
Right now I have it in views.py and I feel that is a violation of best practices for "fat model, thin views", so I want to move it from views.py. But I'm not entirely sure if this is a row-based or table-based type of operation, so I'm not sure if it should be implemented as a model method, or instead as a manager.
Here's the current (working) logic in views.py:
record_count = Record.objects.filter(record_type__id=record_type_.id, source__id=source_.id).count()
Just to be clear, this isn't a question of how to get the count, but simply in which area of code to put the function.
Here's a similar question, but which was addressing "how to" not "where":
Counting and summing values of records, filtered by a dictionary of foreign keys in Django
If the result involves multiple rows, it is a table-related method, and according to Django conventions, should be a manager method.
From the Django docs:
Adding extra Manager methods is the preferred way to add “table-level” functionality to your models. (For “row-level” functionality – i.e., functions that act on a single instance of a model object – use Model methods, not custom Manager methods.)

Django - sort QuerySet by backwards related model's field

I have the following (simplified) models:
class Post(models.Model):
title=models.CharField(max_length=500)
class Recommendation(models.Model):
post=models.ForeignKey(Post)
submit_time=models.DateTimeField(auto_now_add=True)
And I want to get the list of distinct Posts ordered by Recommendation's submit time.
The first way I tried was the straighforward:
Post.objects.order_by('recommendation__submit_time').distinct()
But surprisingly this gave a QuerySet with duplicate Post objects. Turns out the rows are actually different because Django adds extra columns for the ordering, but does not return them in the results.
Looking around I found a couple answers on SO, including to use aggregation instead of ordering:
Post.objects.all().annotate(Max('recommendation__submit_time')).order_by('recommendation__submit_time__max')
Or to de-normalize the model and add a last_recommended_time field to Post.
Most of the questions/answers already in SO are a couple years old, so I was wondering if there's a more idiomatic and straightforward way to do this than those suggested hacks.
EDIT:
Just thought I made it clear:
The solutions listed above do work and I'm using them (albeit not in production). I'm just interested in better solutions to this issue.
Have you thought about using raw sql
Post.objects.raw("""
SELECT DISTINCT post FROM
(SELECT appname_post.post_id AS post, appname_recommendation.submit_time
FROM appname_post
INNER JOIN appname_recommendation
ON appname_post.post_id = appname_recommendation.post_id
ORDER_BY appname_recommendation.submit_time)
""")

How to perform a query in django that selects all projects where I am a team member of?

I have the concept of a team in my django app.
class Team(models.Model):
name = models.CharField(max_length=200)
#snip
team_members = models.ManyToManyField(User)
I would like to fetch all teams the currently logged in user is member of. Something along the lines of
Team.objects.all().filter(request.user.id__in = team_members.all())
This obvious doesn't work. Does anyone have some suggestions on how to do such query without going directly to sql? I did look at the django documentation of "in" queries, but I couldn't find my use case there.
Many thanks!
Nick.
You don't need in here, Django handles that automatically in a ManyToMany lookup.
Also, you need to understand that the database fields must always be on the left of the lookup, as they are actually handled as parameters to a function.
What you actually want is very simple:
Team.objects.filter(team_members=request.user)
or
request.user.team_set.all()

Categories