Home > Blockchain >  Problem with adding objects to manytomany fields
Problem with adding objects to manytomany fields

Time:04-07

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 of following_user, but it also adds it to the followings of following_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, 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.

  • Related