How to get mutual followers (mutual friend) for users - Django
How do I get mutual followers (friends of friends) with request.user to be displayed for each users posts, I was able to display the count for mutual followers for each users posts on newsfeed. How can I get it done?
What I tried: I was able to do this but I do not think it is the best way because when I use slice to show three mutual users images it does not work properly for all users.
This is what I tried:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,blank=True,null=True)
profile_pic = models.ImageField(upload_to='UploadedProfilePicture/', default="ProfileAvatar/avatar.png", blank=True)
following = models.ManyToManyField(
'Profile', # Refers to the User model itself
symmetrical=False, # If A follows B, B doesn't automatically follow A
related_name='followers', # Reverse relationship: get followers of a user
blank=True,
)
class Post(models.Model):
poster_profile = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE, blank=True,null=True)
def following_view(request):
posts = Post.objects.filter(
Q(poster_profile__profile__followers__user=request.user)).order_by("?").distinct()
mymutual_followers = request.user.profile.following.filter(id__in=request.user.profile.following.values_list('id', flat=True))
{% for post in posts %}
{% for user_following in mymutual_followers %}
{% if user_following in post.poster_profile.profile.following.all|slice:"3" %}
<img src="{{ user_following.profile_pic.url }}" class="hover-followers-img"/>
{% endif %}
{% endfor %}
{% endfor %}
The code above is what I tried and it worked, but add slice:"3" to show just 3 images do not work properly for some users, so I think maybe it's not the best approach.
The code below is how I got the count of mutual followers and it work perfectly but could not display the mutual followers images; how can I get it done?
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,blank=True,null=True)
profile_pic = models.ImageField(upload_to='UploadedProfilePicture/', default="ProfileAvatar/avatar.png", blank=True)
following = models.ManyToManyField(
'Profile', # Refers to the User model itself
symmetrical=False, # If A follows B, B doesn't automatically follow A
related_name='followers', # Reverse relationship: get followers of a user
blank=True,
)
class Post(models.Model):
poster_profile = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE, blank=True,null=True)
def following_view(request):
posts = (
Post.objects.filter(Q(poster_profile__profile__followers__user=request.user)).order_by("?")
.annotate(
mutual_count=Count(
'poster_profile', filter=Q(poster_profile__profile__following__followers__user=request.user)
) # Got the count for mutual followers
)
.prefetch_related( # what am I doing wrong here....
Prefetch(
'poster_profile',
Post.objects.annotate(
is_mutual=Exists(
Post.objects.filter(
poster_profile=OuterRef('pk'),
poster_profile__profile__followers__user=request.user,
)
)
).filter(is_mutual=True),
to_attr='mutual_followers',
)
)
)
# Template
{% for post in posts %}
{{ post.mutual_count }} # Got mutual count
{% endfor %}
# This is not displaying images.
{% for post in posts %}
{% for img in post.mutual_followers %}
<img src="{{ img.profile_pic.url }}" class="hover-followers-img"/>
{% endfor %}
{% endfor %}
As I see it, you are already calculating the mutual follower count, but what you want is to display the actual mutualal follower objects for each post. The reason your original approach with annotate
and prefetch_related
isnt working is because while annotate
is good for counts, it doesn’t fetch related objects. Meanwhile, prefetch_related
with Prefetch
is mainly built for ManyToMany
or reverse ForeignKey
relationships. It doesn’t do anything for a direct ForeignKey
like poster_profile
.
Doing this directly in your template with something similar to: {% if user_following in post.poster_profile.profile.following.all|slice:"3" %}
, likely ends up causing lots of extra database queries and is pretty messy in general.
I think a good way to handle this is to build the list of mutual followers right in your view! Then slice it down to the top 3 (or however many you nned), and pass everything to your template already prepared. This loops over the list of mutual followers for each post and avoids those slow N+1 queries that happen when slicing or filtering something in the template.
Here’s how you could structure your view:
def following_view(request):
user_profile = request.user.profile
posts = Post.objects.filter(
poster_profile__profile__followers__user=request.user
).select_related('poster_profile', 'poster_profile__profile').distinct()
post_data = []
for post in posts:
poster_profile = post.poster_profile.profile
mutual_followers_qs = user_profile.following.filter(
id__in=poster_profile.following.values_list('id', flat=True)
)
post_data.append({
'post': post,
'mutual_followers': mutual_followers_qs[:3], # You can select any number of followers here
'mutual_count': mutual_followers_qs.count()
})
return render(request, "your_app_here/newsfeed.html", {"post_data": post_data})
And then update your temlpate to something like this:
{% for item in post_data %}
<div>
<p>Post by {{ item.post.poster_profile.username }}</p>
<p>{{ item.mutual_count }} mutual followers</p>
<div>
{% for mutual_follower in item.mutual_followers %}
<img src="{{ mutual_follower.profile_pic.url }}" alt="mutual follower" class="hover-followers-img">
{% empty %}
<span>No mutual followers yet</span>
{% endfor %}
</div>
</div>
{% endfor %}
All of the logic stays in your view, so you avoid slow template loops and unnecessary slicing, and you reliably get mutual followers for each post.