将对象添加到多对多字段的问题

Problem with adding objects to manytomany fields

我有一个用户模型:

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)

adding/removing 个用户对象的视图来自 followers/followings:

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

这利用了 Willem 提到的 symmetrical 逻辑。这将避免您必须跟踪两个 ManyToManyFields 中的关系。

However, instead of doing so, it adds follower_user not only to the followers of following_user, but it also adds it to the followings of following_user. Why does it happen?

因为 Django 与 self 的关系默认是对称的。您应该通过设置 symmetrical=False [Django-doc] 使关系不对称,只需使用 one ManyToManyField,所以:

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

逻辑很简单:

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)

如果用户 AB.followers 中,则 B自动在A.following中,所以不需要使用两个关系

此外,您不必跟踪关注者的数量,必要时可以使用 .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.