I have a User model:
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)
And there's a view for adding/removing user objects from 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)
I want it to add follower_user
to the followers of following_user
and add following_user
to the followings of follower_user
. 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?
Edit:
I added symmetrical=False
to fields but that caused an error:
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'.
CodePudding user response:
Use this as your model:
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)
This makes use of the symmetrical logic that Willem mentioned. This will avoid you having to keep track of the relationship in two ManyToManyFields.
CodePudding user response:
However, instead of doing so, it adds follower_user not only to the
followers
offollowing_user
, but it also adds it to thefollowings
offollowing_user
. Why does it happen?
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'
)
The logic is simply:
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)
If user A is in the .followers
of B, then B is automatically in the .following
of A, so there is no need to use two relations.
Furthermore you do not have to keep track of the number of followers, you can count this using .annotate(…)
[Django-doc] when necessary.
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, theget_object_or_404(…)
will result in returning a HTTP 404 Not Found response, whereas using.get(…)
will result in a HTTP 500 Server Error.