Как получить взаимных подписчиков (общего друга) для пользователей - Django

Как мне получить взаимных подписчиков (друзей друзей) с помощью request.user, которые будут отображаться для каждой публикации пользователя, я смог отобразить количество взаимных подписчиков для каждой публикации пользователя в ленте новостей. Как я могу это сделать?

Что я пробовал: мне удалось это сделать, но я не думаю, что это лучший способ, потому что, когда я использую slice для отображения изображений трех общих пользователей, это работает неправильно для всех пользователей.

Вот что я попробовал:

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 %}

Приведенный выше код - это то, что я пробовал, и он сработал, но добавление slice:"3" для отображения только 3 изображений не работает должным образом у некоторых пользователей, поэтому я думаю, что, возможно, это не лучший подход.

Приведенный ниже код показывает, как я получил количество взаимных подписчиков, и он отлично работает, но не может отобразить изображения взаимных подписчиков; как я могу это сделать?

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 %}

Как я понимаю, вы уже подсчитываете количество взаимных подписчиков, но все, что вы хотите, - это отобразить фактические объекты взаимных подписчиков для каждого поста. Причина, по которой ваш первоначальный подход с annotate и prefetch_related не работает, заключается в том, что, хотя annotate хорош для подсчета, он не извлекает связанные объекты. Между тем, prefetch_related с Prefetch в основном предназначен для ManyToMany или обратных ForeignKey отношений. Это ничего не делает для прямого ForeignKey типа poster_profile.

Выполнение этого непосредственно в вашем шаблоне с помощью чего-то похожего на: {% if user_following in post.poster_profile.profile.following.all|slice:"3" %}, скорее всего, приведет к множеству дополнительных запросов к базе данных и в целом будет довольно запутанным.

Я думаю, что хороший способ справиться с этим - создать список взаимных подписчиков прямо в вашем представлении! Затем сократите его до 3 лучших подписчиков (или сколько бы вы их ни выбрали) и перенесите все в уже готовый шаблон. Это позволяет просматривать список взаимных подписчиков для каждой публикации и избежать тех медленных запросов N+1, которые возникают при нарезке или фильтрации чего-либо в шаблоне.

Вот как вы могли бы структурировать свое представление:

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

А затем обновите свой шаблон до чего-то вроде этого:

{% 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 %}

Вся логика остается в поле вашего зрения, поэтому вы избегаете медленных циклов создания шаблонов и ненужной нарезки, а также гарантированно получаете взаимных подписчиков на каждый пост.

Вернуться на верх