Complete & Professional Follow System (Like Twitter/Instagram)
**Django Blog API Tutorial Complete & Professional Follow System (Like Twitter/Instagram)
Now we will add a powerful Follow Feature:
- Users can follow other users
- Users can unfollow other users
- See list of followers of any user
- See list of users a person is following
- Secure: Only authenticated users can follow/unfollow
- Clean API design with proper permissions
Step 1: Update profiles/models.py
Add a ManyToManyField with through model for better control (recommended).
Replace your current profiles/models.py with this updated version:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
related_name='profile'
)
bio = models.TextField(blank=True, null=True)
avatar = models.ImageField(upload_to='avatars/', blank=True, null=True, default='avatars/default.jpg')
location = models.CharField(max_length=100, blank=True, null=True)
website = models.URLField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.user.username}'s Profile"
class Meta:
ordering = ['-created_at']
# ====================== FOLLOW SYSTEM ======================
class Follow(models.Model):
follower = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='following' # user.following.all() → people I follow
)
following = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='followers' # user.followers.all() → people who follow me
)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('follower', 'following') # Prevent duplicate follows
ordering = ['-created_at']
def __str__(self):
return f"{self.follower.username} follows {self.following.username}"
# Auto create Profile when new User is created
@receiver(post_save, sender=User)
def create_or_update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
else:
instance.profile.save()
Why unique_together?
Prevents a user from following the same person twice.
Run migrations after this:
python manage.py makemigrations
python manage.py migrate
Step 2: Create Serializers (profiles/serializers.py)
Update your profiles/serializers.py with these new serializers:
from rest_framework import serializers
from .models import Profile, Follow
from django.contrib.auth.models import User
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.ReadOnlyField(source='user.username')
email = serializers.ReadOnlyField(source='user.email')
followers_count = serializers.IntegerField(source='user.followers.count', read_only=True)
following_count = serializers.IntegerField(source='user.following.count', read_only=True)
class Meta:
model = Profile
fields = [
'id', 'username', 'email', 'bio', 'avatar',
'location', 'website', 'followers_count',
'following_count', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'username', 'email', 'created_at', 'updated_at']
class FollowSerializer(serializers.ModelSerializer):
follower_username = serializers.ReadOnlyField(source='follower.username')
following_username = serializers.ReadOnlyField(source='following.username')
class Meta:
model = Follow
fields = ['id', 'follower_username', 'following_username', 'created_at']
read_only_fields = ['id', 'created_at']
class FollowActionSerializer(serializers.Serializer):
"""Simple serializer for Follow/Unfollow action"""
username = serializers.CharField(help_text="Username of the user you want to follow/unfollow")
Step 3: Create Views (profiles/views.py)
Update profiles/views.py with the complete follow functionality:
from rest_framework import generics, permissions, status, views
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from django.contrib.auth.models import User
from .models import Profile, Follow
from .serializers import ProfileSerializer, FollowSerializer, FollowActionSerializer
class ProfileDetailView(generics.RetrieveUpdateAPIView):
serializer_class = ProfileSerializer
permission_classes = [permissions.IsAuthenticated]
def get_object(self):
return self.request.user.profile
# ====================== FOLLOW VIEWS ======================
class FollowUserView(views.APIView):
"""POST: Follow a user"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
serializer = FollowActionSerializer(data=request.data)
if serializer.is_valid():
username = serializer.validated_data['username']
if username == request.user.username:
return Response({"error": "You cannot follow yourself"},
status=status.HTTP_400_BAD_REQUEST)
try:
user_to_follow = User.objects.get(username=username)
except User.DoesNotExist:
return Response({"error": "User not found"},
status=status.HTTP_404_NOT_FOUND)
# Create follow relationship
follow, created = Follow.objects.get_or_create(
follower=request.user,
following=user_to_follow
)
if created:
return Response({
"message": f"You are now following {username}",
"following": True
}, status=status.HTTP_201_CREATED)
else:
return Response({
"message": f"You are already following {username}"
}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class UnfollowUserView(views.APIView):
"""POST: Unfollow a user"""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
serializer = FollowActionSerializer(data=request.data)
if serializer.is_valid():
username = serializer.validated_data['username']
try:
user_to_unfollow = User.objects.get(username=username)
follow = Follow.objects.get(
follower=request.user,
following=user_to_unfollow
)
follow.delete()
return Response({
"message": f"You have unfollowed {username}",
"following": False
}, status=status.HTTP_200_OK)
except Follow.DoesNotExist:
return Response({
"error": f"You are not following {username}"
}, status=status.HTTP_400_BAD_REQUEST)
except User.DoesNotExist:
return Response({"error": "User not found"},
status=status.HTTP_404_NOT_FOUND)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class FollowersListView(generics.ListAPIView):
"""GET: List all followers of a user"""
serializer_class = FollowSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
username = self.kwargs['username']
user = get_object_or_404(User, username=username)
return user.followers.all()
class FollowingListView(generics.ListAPIView):
"""GET: List all users that a person is following"""
serializer_class = FollowSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
username = self.kwargs['username']
user = get_object_or_404(User, username=username)
return user.following.all()
Step 4: Update URLs (profiles/urls.py)
Replace the content with this:
from django.urls import path
from .views import (
ProfileDetailView,
FollowUserView,
UnfollowUserView,
FollowersListView,
FollowingListView
)
urlpatterns = [
path('profile/', ProfileDetailView.as_view(), name='profile-detail'),
# Follow System
path('follow/', FollowUserView.as_view(), name='follow-user'),
path('unfollow/', UnfollowUserView.as_view(), name='unfollow-user'),
path('followers/<str:username>/', FollowersListView.as_view(), name='followers-list'),
path('following/<str:username>/', FollowingListView.as_view(), name='following-list'),
]
How to Test the Follow Feature
Use Postman or Thunder Client with Bearer Token:
-
Follow a user
POST→http://127.0.0.1:8000/api/follow/
Body:{ "username": "ankit" } -
Unfollow a user
POST→http://127.0.0.1:8000/api/unfollow/
Body: same as above -
Get followers of a user
GET→http://127.0.0.1:8000/api/followers/ankit/ -
Get following list
GET→http://127.0.0.1:8000/api/following/ankit/ -
Get updated profile (shows follower & following count)
GET→http://127.0.0.1:8000/api/profile/
Best Practices Used
- Separate
Followmodel withunique_together - Clear, reusable API endpoints
- Proper error handling and meaningful messages
- Count fields in ProfileSerializer using
source - Only authenticated users can follow/unfollow
- Clean separation of concerns