Django - sort QuerySet by backwards related model's field - python

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)
""")

Related

Benefits of foreign key over integerfield

I'm not sure if this is an appropriate question here. I know the answer but I don't really know why, and I need proof when I raise this to my team.
We have a number of Blog Posts on a Django Site. It's possible to "clone" one of those blog posts to copy it to another site. The way the current developer did that was to take the pk of the original post and store it as an IntegerField on the cloned post as clone_source. Therefore to get a story's clones, we do:
clones = BlogPost.all_sites.filter(clone_source=pk)
It seems to me that this would be much better structured as a foreign key relationship.
Am I right? Why or why not?
Deleted objects
If you ever decided to delete the original post, you'd need a separate query to handle whatever you expect to do with the cloned posts instead of using the on_delete kwarg of a FK.
Its an extra query
As noted in the comments, foreign keys allow you to traverse the relationships directly through the ORM relationship methods.
Data structure visualisation tools
These won't be able to traverse any further down from an integer field since it will believe it is at a leaf node.
Throughout all of this though, the elephant in the room is that a "clone" is still just duplicated data so I wonder why you don't just let a blog post be referenced more than once then you don't need to worry about how you store clones.

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

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'))

Django: queryset multiple conditions or gather into new object

I need to get all Comments for all Projects that includes a specific User. Meaning, all comments for all projects that a user is member of.
A user can belong to many Projects, and each Project has many Comments.
How should this be done right? So far I have solved it in my template by creating a nested for-loop, but that's not good since I need to sort the result.
I'm thinking something like:
projects = user.projects
comments = Comment
for p in projects:
for c in p.comments:
comments.append(c)
return comments
...does not seem to work.
Any clues?
I think this will do it:
query = Comment.objects.filter(project__user=person)
If the Comment model has a foreign key to project which has a foreign key to user. This will involve a SQL join statement in the database. It's better to do this on the the database because it's far more efficient. Databases are designed exactly to do this.

Django GenericForeignKey lookup for a given model

I use a voting app (django-ratings if that makes any difference) which uses django's GenericForeignKey, has a ForeignKey to User, and several other fields like date of latest change.
I'd like to get all the objects of one content type, that a single user voted for ordered by date of latest change. As far as I understand - all the info can be found in a single table (except the content_type which can be prefetched/cached). Unfortunately django still makes an extra query each time I request a content_object.
So the question is - how do I get all the votes on a given model, by a given user, with related objects and given ordering with minimum database hits?
Edit: Right now I'm using 2 queries - first selecting all the votes, getting all the objects I need, filtering by .filter(pk__in=obj_ids) and finally populating them to votes objects. But it seems that a reverse generic relation can help solve the problem
Have you checked out select_related()? That may help.
Returns a QuerySet that will automatically "follow" foreign-key relationships, selecting that additional related-object data when it executes its query. This is a performance booster which results in (sometimes much) larger queries but means later use of foreign-key relationships won't require database queries.
https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related
Well right now we're using prefetch_related() from django 1.4 on a GenericRelation. It still uses 2 queries, but has a very intuitive interface.
From looking at the models.py of the django-ratings app, I think you would have to do user.votes.filter(content_type__model=Model._meta.module_name).order_by("date_changed") (assuming the model you want to filter by is Model) to get all the Vote objects. For the related objects, loop through the queryset getting content_object on each item. IMHO, this would result in the least DB queries.

SQL Alchemy: Relationship with grandson

I'm building a SQL Alchemy structure with three different levels of objects; for example, consider a simple database to store information about some blogs: there are some Blog object, some Post object and some Comment objects. Each Post belongs to a Blog and each Comment belongs to a Post. Using backref I can automatically have the list of all Posts belonging to a Blog and similarly for Comments.
I drafted a skeleton for such a structure.
What I would like to do now is to have directly in Blog an array of all the Comments belonging to that Blog. I've tried a few approaches, but they don't work or even make SQL Alchemy cry in ways I can't fix. I'd think that mine is quite a frequent need, but I couldn't find anything helpful.
Colud someone suggest me how to do that?
Thanks.
I think you can use an association proxy
class Blog(Base):
comments = association_proxy('posts', 'comments')
You need to use join:
comments = session.query(Comment).join(Post).filter(Post.blog == b1).all()

Categories