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
- Django template indentation guideline
- How to customize django rest auth password reset email content/template
- Django's get_current_language always returns "en"
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()
- How does this Man-In-The-Middle attack work?
- Create a canonical "parent" product in Django Oscar programmatically
- Registered models do not show up in admin
- How do I simulate connection errors and request timeouts in python unit tests
- Django database synchronization for an offline usage
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.
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.