django 2 - Nested query based off multiple models - python

Update: See bottom.
I'm trying to wrap my head around how to achieve a nested/chained query based on my needs. There might be a better way to get the results I need so please let me know.
Trying to get the authenticated user and get a list of friends, which I have working and I get a queryset object of friends. I would like to pass the queryset object of friends into another query that searches the Post model, matches the username found Friend.users to the Post.creator so I get back another queryset which will have all the Posts of all my friends which I can display in the template.
class Friend(models.Model):
users = models.ManyToManyField(User, blank=True)
owner = models.ForeignKey(User, related_name='owner_friend', on_delete=models.CASCADE, null=True, blank=True)
class Post(models.Model):
creator = models.ForeignKey(User, on_delete=None, null=True)
EDIT:
I'm trying to do this with my code
f = Friend.objects.all().filter(owner__username='admin').filter(users__username='jeff')
output
<QuerySet [<Friend: Friend object (1)>]>
p = Post.objects.all().filter(creator__username__in=f)
ValueError: Cannot use QuerySet for "Friend": Use a QuerySet for "User".
Thanks for the help.
Update 1:
I've changed my models to use ForeignKeys instead. I've been using the shell to test and I'm getting an unexpected result, see below.
class Friend(models.Model):
users = models.ForeignKey(User, related_name='user_friend', on_delete=models.CASCADE, null=True, blank=True)
owner = models.ForeignKey(User, related_name='owner_friend', on_delete=models.CASCADE, null=True, blank=True)
f = Friend.objects.filter(owner__username='admin').values_list('users__username', flat=True)
Output
<QuerySet ['jeff', 'sam']>
Post.objects.filter(creator__username__in=list(f))
Output
<QuerySet []>
If I put in the list manually
Post.objects.filter(creator__username__in=['admin', 'jeff'])
Output
<QuerySet [<Post: Post Title 1>]>
I think if I can get the __in=list(f) to work this should fix the issue.
Update 2
list(f) is actually working, I didn't have a Post linked for the user accounts for the friends being pulled through. Once I created the post for the friends, I'm now getting a queryset.
I'm not sure what I'm doing is the best way but it's working.
For future readers I hope this helps.

The Friend model is a bit strange and is causing you some difficulty. Really, Friend should be the through table in the many-to-many relationship between User and itself. Presumably you are using the built-in User model, which is why you have used an external model; but you can still simulate a through table with two foreign keys:
class Friend(models.Model):
from_user = models.ForeignKey(User, related_name='users_from', on_delete=models.CASCADE)
to_user = models.ForeignKey(User, related_name='users_to', on_delete=models.CASCADE)
Now your query can be:
Post.objects.filter(creator__users_to__from_user__username='admin')
that is, give me all Posts whose creator is on the "to_user" side of the friend relationship where the from_user's username is "admin".

Related

Django get related object's QuerySet inside annotate

My Models
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
#Unnecessary
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_comments")
post = models.ForeignKey(Post, on_delete=models.CASCADE,related_name="post_comments")
liked_users = models.ManyToManyField(User, blank=True, related_name="liked_comments")
disliked_users = models.ManyToManyField(User, blank=True related_name="disliked_comments")
#Unnecessary
What I Have
Normally, i use this code to make my comment objects have the attribute "voting" and order them by that attribute.
comments = models.Comment.objects.filter(post=post).annotate(voting=Count("liked_users")-Count("disliked_users")).order_by("-voting")
But when I want to query posts, I can't reach their comments:
What I Want
I want my QuerySet "posts" to have an attribute for every single post, called "best_3_comments". Which is a QuerySet of comments of them ordered by their voting's.
Can I achieve this without querying all of the comments everytime I query posts?
posts = models.Post.objects.annotate(
best_3_comments = get_query_of_the_posts_comments("post_comments").annotate(
voting=Count("liked_users")-Count("disliked_users")
).order_by("-voting")[:3]
)
What function(s) can i use to get query of the post's comments, and how should i approach this ? I'm completely open to new ideas of ways to do what I want to achieve, I couldn't find which function and how to use for it.
Thank you in advance.
I found a solution. I would like to share it for those in future looking for a way to solve their problems similar to mine.
posts = models.Post.objects.all() # The QuerySet You Want To Use
for post in posts:
post.best_3_comments = models.Comment.objects.filter(post=post).annotate(voting=Count("liked_users")-Count("disliked_users")).order_by("-voting")[:3]
The solution is iterating over our QuerySet, assigning an attribute for all of them which contains a QuerySet.

Django Model.objects.all() queryset not distinct

I have the following Django 3.0 models:
class Profile(models.Model):
id = ShortUUIDField(primary_key=True, unique=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
class Bounty(models.Model):
id = ShortUUIDField(primary_key=True, unique=True)
creator = models.ForeignKey('Profile', related_name="created_bounties", on_delete=models.SET_NULL, null=True, blank=True)
original_completion = models.OneToOneField('ProfileTrophy', related_name="original_bounty",on_delete=models.SET_NULL, null=True, blank=True)
name = models.CharField(max_length=50)
class ProfileTrophy(models.Model):
id = ShortUUIDField(primary_key=True, unique=True)
profile = models.ForeignKey('Profile', related_name="bounty_completions", on_delete=models.CASCADE)
bounty = models.ForeignKey('Bounty', related_name="bounty_completions", on_delete=models.CASCADE)
name = models.CharField(max_length=50, null=True, blank=True)
So the premise is that there are profiles, and bountys. Profiles can create bountys (stored as creators on the bounty). Profiles can complete a bounty, which is stored as a ProfileTrophy (trophy for completing the bounty) and they can complete bountys they created or bountys created by others (bounties can have many completions, but the original by the creator is stored in original_completion).
The problem I'm running into is that if I have two Profiles, Bob and Jim, and Bob creates a bounty called "Bounty 1" and completes it everything is fine. If Jim then completes "Bounty 1" everything works fine, except when I call either Bounty.objects.all() or Bob.created_bounties.all() I get <QuerySet [<Bounty: Bounty 1>, <Bounty: Bounty 1>]> and queryset[0]==queryset[1].
The database shows only one bounty, and everything looks as it should. If I look at the queryset SQL of Bounty.objects.all() I see
SELECT "core_bounty"."id" FROM "core_bounty" LEFT OUTER JOIN "core_profiletrophy" ON ("core_bounty"."id" = "core_profiletrophy"."bounty_id")
Which if I'm reading right the left outer join is the problem because it will match both ProfileTrophys and so return the bounty twice.
Any help on why this duplication is happening in the queryset and what I'm doing wrong?
Edit: I should add that everything within the app works fine, I only noticed because the Bounty object shows up twice in the admin dashboard and it really bothered me.
Edit 2: Removing creator and original_completion from the Bounty model has no effect on the problem
Ok so it turns out it was related to something I left out, the Meta ordering was set to ["bounty_completions"] which was causing the ordering to need to call in the left join.

Retrieving model objects created by different users in Django

I am making simple feed which consists of entries made by authors which current user is subscribed to. I have 3 models which are default user model, my "Post" model which is related to User via ForeignKey:
class Post(models.Model):
...
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
...
"Relations" model which has 2 fields:
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name="follows")
following = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followed")
So I wrote this code to retrieve needed posts:
user = request.user
posts = Post.objects.filter(author__in = [relation.following_id for relation in user.follows.all()]).all()
And honestly it works just fine, but is there any way to make my query better? Thank you.
Yes, you can make the JOIN in the database, so:
Post.objects.filter(author__followed__follower=user)
or if you want to include Post objects for which user is the author, you can work with Q-objects:
from django.db.models import Q
Post.objects.filter(Q(author=user) | Q(author__followed__follower=user))
Since you do the JOIN at the database side, you do this in one query, wheras the list comprehension will be performed in 2 queries.

Django related model field querying (mutual friends)

I have a friendship model:
class Friendship(models.Model):
user = models.ForeignKey(
Account, on_delete=models.CASCADE, related_name="friend1", null=True, blank=True)
other_user = models.ForeignKey(
Account, on_delete=models.CASCADE, related_name="friend2", null=True, blank=True)
date_created = models.DateTimeField(auto_now=True)
objects = FriendshipManager()
class Meta:
verbose_name = "friendship"
verbose_name_plural = "friendships"
unique_together = ("user", "other_user")
def __str__(self):
return f'{self.user} is friends with {self.other_user}.'
and this function to return all users who are mutual friends of two accounts
def mutual_friends(self, account1, account2):
mutual_friends = Account.objects.filter(
Q(friend2__user=account1) & Q(friend2__user=account2))
return mutual_friends
Based on my (limited) understanding of how the query api works, I would think this should return all users who have a "friend2" relationship with the Friendship table where the "friend1" user is either account1 or account2. I'm still getting used to querying with django, so if someone can let me know what I'm doing wrong that'd be great.
Thanks!
Your model design doesn't seem right to me. As of now, you can put any Account instance as user or other_user, and as they both refer the same model (Account), while doing any retrieval from database, you need to account for both of the fields.
A better design IMO would be to use a ManyToManyField (many-to-many relationship) in the Account model to itself, as one account can have multiple other accounts as friends and vice-versa. So:
class Account(models.Model):
...
friends = models.ManyToManyField('self')
...
Now, you can add friends like e.g.:
account_foo.friends.add(account_bar, account_spam)
account_* are Account instances.
You can get all friends of account_foo like:
account_foo.friends.all()
Check out the many-to-many doc for various examples of data integration and querying.
Now, to find the mutual friends of e.g. account_foo and account_bar, you can first get all the friends of account_foo and then see which of them are also friends with account_bar:
friends_with_foo = account_foo.friends.values_list('pk', flat=True)
mutual_friends_of_foo_bar = account_bar.friends.filter(pk__in=friends_with_foo)

Counting and filtering objects in a database with Django

I'm struggling a little to work out how to follow the relation and count fields of objects.
In my Django site, I have a profile model:
user = models.ForeignKey(User, unique=True)
name = models.CharField(_('name'), null=True, blank=True)
about = models.TextField(_('about'), null=True, blank=True)
location = models.CharField(_('location'), null=True, blank=True)
website = models.URLField(_('website'), null=True, blank=True)
My understanding is that this is using the username as the foreign key.
I would like to be able to count and display the number of completed profiles my users have filled out, and ones that have a specific "element / field"? (name)* filled out. I tried:
Profile.objects.all().count()
That gave me the same number of profiles as users, which I am guessing is because the profile model exists for each user, even if it is blank.
I'm unsure how to count profiles that have one of these fields completed in them, and I am also unsure how to count the number of completed "name" fields that have been completed.
I tried:
Profile.objects.all().name.count()
Django has some good docs on queryset api, but its currently going a little over my head
please excuse my use of incorrect terminology.
You should be able to get them using:
Profile.objects.filter(name__isnull=False)

Categories

Resources