[Fixed]-How to make follower-following system with django model

35πŸ‘

βœ…

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 )

17πŸ‘

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
πŸ‘€unicdev

5πŸ‘

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()
πŸ‘€GST Talib

4πŸ‘

I created a follow and unfollow system similar to Instagram.

Functionality:

  • If private account enable –> send follow request.
  • If user in block list –> they can not follow of opposite user.
  • User can check pending request, sended request list,blocked user
    list,etc.

Let’s start :

Models.py:

    class Profile(models.Model):
        user = models.OneToOneField(to = User,on_delete=models.CASCADE,related_name='profile')
          .......
        private_account = models.BooleanField(default = False)
        followers = models.ManyToManyField('self',blank=True,related_name='user_followers',symmetrical=False)
        following = models.ManyToManyField('self',blank=True,related_name='user_following',symmetrical=False)
        panding_request = models.ManyToManyField('self',blank=True,related_name='pandingRequest',symmetrical=False)
        blocked_user = models.ManyToManyField('self',blank=True,related_name='user_blocked',symmetrical=False)
        created_date = models.DateTimeField(auto_now_add=True)
        
        def __str__(self):
            return '%s' %(self.user)

  • Here you can follow, unfollow, send follow request, accept follow request,
    decline request and you can remove your follower.
  • From the front end side pass opposite user profile id and type
    of action
    (follow,unfollow..).

views.py:

    class FollowUnfollowView(APIView):
        permission_classes = [IsAuthenticated]
        
        def current_profile(self):
            try:
                return Profile.objects.get(user = self.request.user)
            except Profile.DoesNotExist:
                raise Http404
            
        def other_profile(self,pk):
            try:
                return Profile.objects.get(id = pk)
            except Profile.DoesNotExist:
                raise Http404
        
        def post(self, request,format=None):    
            
            pk = request.data.get('id')              # Here pk is opposite user's profile ID
            req_type = request.data.get('type')        
            
            current_profile = self.current_profile()
            other_profile = self.other_profile(pk)
            
            
            if req_type == 'follow':
                if other_profile.private_account:
                    other_profile.panding_request.add(current_profile)
                    return Response({"Requested" : "Follow request has been send!!"},status=status.HTTP_200_OK)
                else:
                    if other_profile.blocked_user.filter(id = current_profile.id).exists():
                        return Response({"Following Fail" : "You can not follow this profile becuase your ID blocked by this user!!"},status=status.HTTP_400_BAD_REQUEST)
                    current_profile.following.add(other_profile)
                    other_profile.followers.add(current_profile)
                    return Response({"Following" : "Following success!!"},status=status.HTTP_200_OK) 
            
            elif req_type == 'accept':
                current_profile.followers.add(other_profile)
                other_profile.following.add(current_profile)
                current_profile.panding_request.remove(other_profile)
                return Response({"Accepted" : "Follow request successfuly accespted!!"},status=status.HTTP_200_OK)
            
            elif req_type == 'decline':
                current_profile.panding_request.remove(other_profile)
                return Response({"Decline" : "Follow request successfully declined!!"},status=status.HTTP_200_OK)
            
            elif req_type == 'unfollow':
                current_profile.following.remove(other_profile)
                other_profile.followers.remove(current_profile)
                return Response({"Unfollow" : "Unfollow success!!"},status=status.HTTP_200_OK)
                
            elif req_type == 'remove':     # You can remove your follower
                current_profile.followers.remove(other_profile)
                other_profile.following.remove(current_profile)
                return Response({"Remove Success" : "Successfuly removed your follower!!"},status=status.HTTP_200_OK)

    # Here we can fetch followers,following detail and blocked user,pending request,sended request.. 
    
        def patch(self, request,format=None):
        
            req_type = request.data.get('type')
        
            if req_type == 'follow_detail':
                serializer = FollowerSerializer(self.current_profile())
                return Response({"data" : serializer.data},status=status.HTTP_200_OK)
        
            elif req_type == 'block_pending':
                serializer = BlockPendinSerializer(self.current_profile())
                pf = list(Profile.objects.filter(panding_request = self.current_profile().id).values('id','user__username','profile_pic','overall_pr'))
                return Response({"data" : serializer.data,"Sended Request" :pf},status=status.HTTP_200_OK)

    # You can block and unblock user

        def put(self, request,format=None):
            pk = request.data.get('id')              # Here pk is oppisite user's profile ID
            req_type = request.data.get('type')
        
            if req_type == 'block':
                self.current_profile().blocked_user.add(self.other_profile(pk))
                return Response({"Blocked" : "This user blocked successfuly"},status=status.HTTP_200_OK)
            elif req_type == 'unblock':
                self.current_profile().blocked_user.remove(self.other_profile(pk))
                return Response({"Unblocked" : "This user unblocked successfuly"},status=status.HTTP_200_OK)

serializers.py:

class EachUserSerializer(serializers.ModelSerializer):
    username = serializers.CharField(source='user.username')
    class Meta:
        model = Profile
        fields = ('id','username','profile_pic')
        read_only_fields = ('id','username','profile_pic')

class FollowerSerializer(serializers.ModelSerializer):
    followers = EachUserSerializer(many=True, read_only= True)
    following = EachUserSerializer(many=True,read_only=True)
    
    class Meta:
        model = Profile
        fields = ('followers','following')
        read_only_fields = ('followers','following')

class BlockPendinSerializer(serializers.ModelSerializer):
    panding_request = EachUserSerializer(many=True, read_only= True)
    blocked_user = EachUserSerializer(many=True,read_only=True)

    class Meta:
        model = Profile
        fields = ('panding_request','blocked_user')  
        read_only_fields = ('panding_request','blocked_user')

urls.py:

from django.urls.conf import path
from .views import *


urlpatterns = [
    .....   
    path('follow_unfollow/',FollowUnfollowView.as_view(),name = "follow_unfollow"),
]

If any doubt of any step please comment. I will briefly describe.

1πŸ‘

This is how i solved my problem.

there is a good answer above, but someone need a detail for it. so i'm writing this

I removed field followers and following in User model. And Created new model UserFollowing.

You can see this model in Enthusiast Martin's answer. I didn't use any serializer to create object.

Just two views (Follow, UnFollow) were needed.

In Follow View

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

with this we can create Following-Follower relationship.

The way to use this relationship

In User Information View

following_data = UserFollowingSerializer(qs.following.all(), many=True)
followers_data = UserFollowingSerializer(qs.followers.all(), many=True)
return JsonResponse({'status':status.HTTP_200_OK, 'data':{'user':serializer.data, 'following':following_data.data, 'followers':followers_data.data}, "message":"success"})

I usually use JsonResponse for response.

serializer.data and qs are user object

In UserFolowingSerializer

fields = '__all__'

I used this.

πŸ‘€HyeonSeok

0πŸ‘

Very good answers. For me it would be either @GST Talib's answer or a very similar one without using self

    following = models.ManyToManyField('User', blank=True, related_name='followers')

That way when you add user1.following.add(user2) you can see the relationship being reflected in user2.followers

And no need for extra code.

Leave a comment