2025, Nov 04 03:00
How to Render Mutual Followers per Post in Django: Compute Intersections in the View, Not the Template
Learn how to render mutual followers on a Django newsfeed: compute intersections in the view, slice once, avoid template slicing and prefetch_related pitfalls.
Rendering a mutual followers preview per post sounds straightforward until you mix counts, object fetching, and template slicing. Counting mutuals is easy with annotate, but reliably showing the actual mutual follower objects (and only a few avatars per post) exposes the limits of doing work in the template and the wrong layer for prefetching. Let’s walk through a robust, view-driven approach that fixes the inconsistency you see with slice in templates and avoids unnecessary queries.
Problem setup
You need, for every post on the newsfeed, to display the author’s mutual followers with the logged-in user. The count already works, but showing the corresponding profile pictures becomes unreliable when slicing in the template, and attempts with prefetch_related didn’t attach the right objects.
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',
        symmetrical=False,
        related_name='followers',
        blank=True,
    )
class Post(models.Model):
    poster_profile = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
# View attempt that mixes logic into the template
def stream_view(request):
    feed_items = Post.objects.filter(
        Q(poster_profile__profile__followers__user=request.user)
    ).order_by("?").distinct()
    my_following_only = request.user.profile.following.filter(
        id__in=request.user.profile.following.values_list('id', flat=True)
    )
    return render(request, "app/newsfeed.html", {"feed_items": feed_items, "my_following_only": my_following_only})
# Template fragment with slicing inside membership test
# This worked inconsistently across users when using slice:"3".
{% for entry in feed_items %}
  {% for candidate in my_following_only %}
    {% if candidate in entry.poster_profile.profile.following.all|slice:"3" %}
      <img src="{{ candidate.profile_pic.url }}" class="hover-followers-img" />
    {% endif %}
  {% endfor %}
{% endfor %}
Why this goes wrong
Two separate ideas got mixed. First, annotate is great for aggregate numbers like mutual counts, but it doesn’t fetch actual related objects for you to render. Second, prefetch_related with Prefetch shines with ManyToMany or reverse ForeignKey relationships. It won’t help when you try to prefetch through a direct ForeignKey like poster_profile. Finally, performing membership checks and slicing inside the template repeatedly triggers extra queries and creates inconsistent results, because the slice is applied to a per-iteration queryset evaluation rather than a single precomputed list.
Here is the count-focused attempt that does not provide the mutual follower objects for rendering:
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',
        symmetrical=False,
        related_name='followers',
        blank=True,
    )
class Post(models.Model):
    poster_profile = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
# View with annotate and Prefetch that doesn't attach the needed objects
def stream_view(request):
    feed_items = (
        Post.objects.filter(Q(poster_profile__profile__followers__user=request.user)).order_by("?")
        .annotate(
            mutual_total=Count(
                'poster_profile',
                filter=Q(poster_profile__profile__following__followers__user=request.user)
            )
        )
        .prefetch_related(
            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',
            )
        )
    )
    return render(request, "app/newsfeed.html", {"feed_items": feed_items})
# Template
{% for entry in feed_items %}
  {{ entry.mutual_total }}
{% endfor %}
# Attempting to render mutual follower images won't work because they weren't fetched
{% for entry in feed_items %}
  {% for avatar in entry.mutual_followers %}
    <img src="{{ avatar.profile_pic.url }}" class="hover-followers-img" />
  {% endfor %}
{% endfor %}
The fix: compute mutuals in the view, slice once, render simply
The stable solution is to assemble, per post, the intersection between the logged-in user’s following and the post author’s following inside the view. Slice it to the desired size there, and send both the sliced queryset and the full count to the template. This keeps the template clean, avoids N+1 queries, and prevents per-iteration slice inconsistencies.
def feed_view(request):
    viewer_profile = request.user.profile
    posts_qs = (
        Post.objects
        .filter(poster_profile__profile__followers__user=request.user)
        .select_related('poster_profile', 'poster_profile__profile')
        .distinct()
    )
    feed_bundle = []
    for post in posts_qs:
        author_profile = post.poster_profile.profile
        overlap_qs = viewer_profile.following.filter(
            id__in=author_profile.following.values_list('id', flat=True)
        )
        feed_bundle.append({
            'post_obj': post,
            'mutual_objs': overlap_qs[:3],
            'mutual_size': overlap_qs.count(),
        })
    return render(request, "your_app_here/newsfeed.html", {"feed_bundle": feed_bundle})
And the corresponding template becomes a pure rendering layer without logic-heavy filters or membership checks:
{% for row in feed_bundle %}
  <div>
    <p>Post by {{ row.post_obj.poster_profile.username }}</p>
    <p>{{ row.mutual_size }} mutual followers</p>
    <div>
      {% for mf in row.mutual_objs %}
        <img src="{{ mf.profile_pic.url }}" alt="mutual follower" class="hover-followers-img" />
      {% empty %}
        <span>No mutual followers yet</span>
      {% endfor %}
    </div>
  </div>
{% endfor %}
Optional slicing variations
If you want to skip the first two mutuals and show the rest, do it in the view at the same place where you slice. The same applies if you want the count that corresponds to a sliced subset.
# show items starting from index 2
'mutual_objs': overlap_qs[2:],
# count of the sliced subset
'addition_count': overlap_qs[2:].count(),
Why this matters
Moving intersection logic and slicing out of templates gives consistent results for all users, keeps the number of queries predictable, and removes per-iteration queryset evaluation. annotate remains useful for numbers, but when you need objects per post, compute them in the view and pass them as ready-to-render data structures. prefetch_related is powerful for ManyToMany and reverse ForeignKey trees, but it won’t attach arbitrary filtered objects to a direct ForeignKey field like poster_profile.
Takeaways
Keep templates focused on presentation, not data shaping. Build per-post mutual follower querysets in the view, slice them once, and ship both the avatars you want to display and the corresponding counts in a single payload. This avoids the inconsistent results you observed with slice in template code and ensures your newsfeed renders the correct mutual followers for every user.
The article is based on a question from StackOverflow by user30880337 and an answer by Viktor Sbruev.