How to make follower-following system with django model

The above solutions are fine and optimal, but I would like to supply a detailed solution for anyone who wants to implement such functionality.

The intermediary Model

from django.contrib.auth import get_user_model
UserModel = get_user_model()

class UserFollowing(models.Model):

    user_id = models.ForeignKey(UserModel, related_name="following", on_delete=models.CASCADE)
    following_user_id = models.ForeignKey(UserModel, related_name="followers", on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=True, db_index=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['user_id','following_user_id'],  name="unique_followers")
        ]

        ordering = ["-created"]

    def __str__(self):
        f"{self.user_id} follows {self.following_user_id}"

THE SERIALIZER for follow and unfollow

Your View for follow and unfollow

class UserFollowingViewSet(viewsets.ModelViewSet):

    permission_classes = (IsAuthenticatedOrReadOnly,)
    serializer_class = UserFollowingSerializer
    queryset = models.UserFollowing.objects.all()

Custom FollowersSerializer and FollowingSerializer

class FollowingSerializer(serializers.ModelSerializer):

    class Meta:
        model = UserFollowing
        fields = ("id", "following_user_id", "created")
class FollowersSerializer(serializers.ModelSerializer):
    class Meta:
        model = UserFollowing
        fields = ("id", "user_id", "created")

Your UserSerializer

from django.contrib.auth import get_user_model

User = get_user_model()

class UserSerializer(serializers.ModelSerializer):

    following = serializers.SerializerMethodField()
    followers = serializers.SerializerMethodField()
    

    class Meta:
        model = User
        fields = (
            "id",
            "email",
            "username",
            "following",
            "followers",
        )
        extra_kwargs = {"password": {"write_only": True}}

    def get_following(self, obj):
        return FollowingSerializer(obj.following.all(), many=True).data

    def get_followers(self, obj):
        return FollowersSerializer(obj.followers.all(), many=True).data

I would design it in different way.

I would not add the information to the User model but explicitly create another table to store information about "followers" and "following".

Schema of the table would be:

class UserFollowing(models.Model):
    user_id = models.ForeignKey("User", related_name="following")

    following_user_id = models.ForeignKey("User", related_name="followers")

    # You can even add info about when user started following
    created = models.DateTimeField(auto_now_add=True)

Now, in your post method implementation, you would do only this:

UserFollowing.objects.create(user_id=user.id,
                             following_user_id=follow.id)

And then, you can access following and followers easily:

user = User.objects.get(id=1) # it is just example with id 1
user.following.all()
user.followers.all()

And you can then create constraint so user cannot follow the same user twice. But i leave this up to you ( hint: unique_together )


models.py

from django.contrib.auth.models import AbstractUser

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

    def count_followers(self):
        return self.followers.count()
    
    def count_following(self):
        return User.objects.filter(followers=self).count()