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.
Related
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)
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".
I may have a simple Django filtering questing. I need a way to select only unique on a ForeignKey, like a distinct.
my models
class Post(models.Model):
user = models.ForeignKey(User)
....
class Share(models.Model):
post = models.ForeignKey(Post)
user = models.ForeignKey(User, blank=True, null=True)
....
I need a way to get all shares with only unique post.user's and need only to get the latest post, to make a inbox.
I ended up with something like ..
Share.objects.filter(user=request.user).distinct("post__user").order_by("post__created")
But i gets an error::
DISTINCT ON fields is not supported by this database backend
Is there a way to make this work?, and what is the best solution?
I want to relate the Model of the Note in my web app with the user that created it .I guess the relation should be Many-to-One.So that i can then filter data by user.Help my the right code , explain , do you thing this is the right method to use in order to have separate data for each user.I really want your opinion on that.
class Note(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
cr_date = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(........) <----- should be something like that
You can add that as foreign key to user model,
#if you are using user model provided by django contrib
from django.contrib.auth.models import User
class Note(models.Model):
#user other fields
owner = models.ForeignKey(User)
I'm new to Python and Django, so please be patient with me.
I have the following models:
class User(models.Model):
name = models.CharField(max_length = 50)
...
class Post(models.Model):
userBy = models.ForeignKey(User, related_name='post_user')
userWall = models.ForeignKey(User, related_name='receive_user')
timestamp = models.DateTimeField()
post = models.TextField()
class Friend(models.Model):
user1 = models.ForeignKey(User, related_name='request_user')
user2 = models.ForeignKey(User, related_name='accept_user')
isApproved = models.BooleanField()
class Meta:
unique_together = (('user1', 'user2'), )
I know that this may not be the best/easiest way to handle it with Django, but I learned it this way and I want to keep it like this.
Now, all I want to do is get all the post from one person and it's friends. The question now is how to do it with the Django filters?
I think in SQL it would look something like this:
SELECT p.* FORM Post p, Friend f
WHERE p.userBy=THEUSER OR (
(f.user1=THEUSER AND f.user2=p.userBy) OR
(f.user2=THEUSER AND f.user1=p.userBy)
)
With no guarantee of correctness, just to give an idea of the result I'm looking for.
from django.db.models import Q
Post.objects.filter( \
Q(userBy=some_user) | \
Q(userBy__accept_user__user1=some_user) | \
Q(userBy__request_user__user2=some_user)).distinct()
UPDATE
Sorry, that was my fault. I didn't pay attention to your related_name values. See updated code above. Using userBy__accept_user or userBy__request_user alone won't work because that'll be a reference to Friend which you can't compare to to User. What we're doing here is following the reverse relationship to Friend and then once we're there, seeing if the other user on the friend request is the user in question.
This also illustrates the importance of describing reverse relationships appropriately. Many people make the same mistake you've made here and name the related_name after the model they're creating the FK to (User), when actually, when we're talking about reversing the FK, we're now talking about Friend. Simply, your related names would make more sense as something like: friend_requests and accepted_friends.
(with User u)
friends_u1 = Friend.objects.filter(user1 = u).getlist('user2_id', flat=True)
friends_u2 = Friend.objects.filter(user2 = u).getlist('user1_id', flat=True)
friends_and_user = friends_u1+friends_u2+u.id
Post.objects.filter(userBy__id__in = friends_and_user)