Проблема с добавлением объектов во многие поля

У меня есть модель пользователя:

class User(AbstractUser):
    followers_num = models.IntegerField(default=0)
    followings_num = models.IntegerField(default=0)
    followers = models.ManyToManyField('self', blank=True, symmetrical=False)
    followings = models.ManyToManyField('self', blank=True, symmetrical=False)

И есть представление для добавления/удаления объектов пользователей из последователей/фолловеров:

def follow(request, follow, user):
    if (request.method == 'PUT'):
        following_user = User.objects.get(username=user)
        follower_user = User.objects.get(username=request.user.username)
        if follow:
            # Follow
            following_user.followers.add(follower_user)
            following_user.followers_num += 1

            follower_user.followings.add(following_user)
            follower_user.followings_num += 1
        else:
            # Unfollow
            following_user.followers.remove(follower_user)
            following_user.followers_num -= 1

            follower_user.followings.remove(following_user)
            follower_user.followings_num -= 1
        
        following_user.save()
        follower_user.save()
        return HttpResponse(status=204)

Я хочу, чтобы он добавил follower_user к последователям following_user и добавил following_user к последователям follower_user. Однако вместо этого он добавляет follower_user не только к последователям following_user, но и к последователям following_user. Почему так происходит?

Редактирование: Я добавил symmetrical=False к полям, но это вызвало ошибку:

SystemCheckError: System check identified some issues:

ERRORS:
network.User.followers: (fields.E304) Reverse accessor for 'network.User.followers' clashes with reverse accessor for 'network.User.followings'.
        HINT: Add or change a related_name argument to the definition for 'network.User.followers' or 'network.User.followings'.
network.User.followings: (fields.E304) Reverse accessor for 'network.User.followings' clashes with reverse accessor for 'network.User.followers'.
        HINT: Add or change a related_name argument to the definition for 'network.User.followings' or 'network.User.followers'.

Используйте это в качестве модели:

class User(AbstractUser):
    followers_num = models.IntegerField(default=0)
    followings_num = models.IntegerField(default=0)
    followers = models.ManyToManyField('self', related_name='followings', null=True, blank=True)

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

Однако вместо этого он добавляет follower_user не только к followers из following_user, но и добавляет его к followings из following_user. Почему это происходит?

Because relations in Django to the self are by default symmetric. You should make the relation asymmetric by setting symmetrical=False [Django-doc] and simply use one ManyToManyField, so:

class User(AbstractUser):
    followers = models.ManyToManyField(
        'self',
        blank=True,
        symmetrical=False,
        related_name='following'
    )

Логика проста:

from django.shortcuts import get_object_or_404

def follow(request, follow, user):
    if request.method == 'PUT':
        following_user = get_object_or_404(User, username=user)
        follower_user = request.user
        if follow:
            following_user.followers.add(follower_user)
        else:
            following_user.followers.remove(follower_user)
        return HttpResponse(status=204)

Если пользователь A находится в .followers из B, то B автоматически находится в .following из A, поэтому нет необходимости использовать два отношения.

Более того, вам не обязательно следить за количеством последователей, вы можете подсчитать это с помощью .annotate(…) [Django-doc] при необходимости.


Note: It is often better to use get_object_or_404(…) [Django-doc], then to use .get(…) [Django-doc] directly. In case the object does not exists, for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using .get(…) will result in a HTTP 500 Server Error.

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