如何使用 django 模型制作追随者系统

Posted

技术标签:

【中文标题】如何使用 django 模型制作追随者系统【英文标题】:How to make follower-following system with django model 【发布时间】:2020-03-06 17:51:07 【问题描述】:

我是学习django rest框架的学生

我正在用 django rest 框架做一个简单的 sns

我需要追随者系统。所以,我试图做到,但有一些麻烦

首先这是我的带有 AbstractBaseUser 和 PermissionsMixin 的用户模型

class User(AbstractBaseUser, PermissionsMixin):
    user_id = models.CharField(max_length=100, unique=True, primary_key=True)
    name = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    is_staff = models.BooleanField(default=False)
    followers = models.ManyToManyField('self', related_name='follower',blank=True)
    following = models.ManyToManyField('self', related_name='following',blank=True)
    profile_image = models.ImageField(blank=True)

现场追随者是关注我的人,关注的是我关注的人

当我用这个 APIView 类添加以下内容时

class AddFollower(APIView):
    permission_classes = [IsAuthenticated, ]
    def post(self, requset, format=None):
        user = User.objects.get(user_id=self.request.data.get('user_id'))
        follow = User.objects.get(user_id=self.request.data.get('follow'))
        user.following.add(follow)
        user.save()
        follow.followers.add(user)
        follow.save()
        print(str(user) + ", " + str(follow))
        return JsonResponse('status':status.HTTP_200_OK, 'data':"", 'message':"follow"+str(follow.user_id))

user_id 是我,follow 是我想关注的人

我想将关注添加到 user_id 的关注字段并将 user_id 添加到关注的关注字段

但它不起作用

我想要的结果是这样的(带有用户信息api)


        "followers": [],
        "following": [
            "some user"
        ],

一些用户的用户信息


        "followers": [
            "user above"
        ],
        "following": [
        ],

但真正的结果是这样的

      
        "followers": [
            "some user"
        ],
        "following": [
            "some user"
        ],

一些用户的用户信息


        "followers": [
            "user above"
        ],
        "following": [
            "user above"
        ],

这不是我想要的

我不知道这个问题我需要一些帮助

谢谢

【问题讨论】:

【参考方案1】:

我会以不同的方式设计它。

我不会将信息添加到User 模型,而是显式创建另一个表来存储有关“关注者”和“关注者”的信息。

表的架构将是:

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)

现在,在您的 post 方法实现中,您只需要这样做:

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

然后,您可以轻松访问关注者和关注者:

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

然后您可以创建约束,这样用户就不能两次关注同一个用户。但我把这个留给你(提示:unique_together)

【讨论】:

哇!!它工作成功。谢谢!!我用你的提示阻止了两次关注同一个用户。谢谢你的好意。 这是一个很好的解决方案,谢谢,但我想知道这是否是一个非常大的项目的最佳解决方案。 @OpeyemiOdedeyi 是的。即使是大型项目也应该没问题 - 在数据库级别,根据需要使用一些索引。然后,您需要在需要时使用查询来检索关注者/关注者。但这不是这个问题的一部分。 @고현석 请是否可以分享您实现此功能的示例。我仍然对此感到困惑。我想知道您的模型、序列化程序、视图是如何处理的。拜托,我真的很感激。 @EnthusiastMartin 是否可以分享一个您实现此功能的示例。我仍然对此感到困惑。我想知道您的模型、序列化程序、视图是如何处理的。拜托,我真的很感激。【参考方案2】:

以上解决方案都很好,并且是最优的,但我想为任何想要实现此类功能的人提供详细的解决方案。

中介模型

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"

用于关注和取消关注的序列化器

您的关注和取消关注视图

class UserFollowingViewSet(viewsets.ModelViewSet):

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

自定义 FollowersSerializer 和 FollowersSerializer

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")

您的 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

【讨论】:

它工作正常,但你没有在这里写Unfollow结构的代码。你也知道如何请求关注吗?就像我想关注一个用户,用户必须接受它? 是的,这很容易,关注用户只需发送 POST 请求到 /your-following-route/ 创建模型并取消关注用户,您发送 DELETE 请求到 /your-following -路线/ID。如果您想在 Twitter 上创建待处理功能,用户需要接受您的关注请求,您可以使用一个标志来执行此操作。因此,当用户登录并接受您的关注时,您会更新模型中的标志。我希望这会有所帮助? 帮助了。非常感谢! 我认为你在UserFollowing: python def __str__(self): return f"self.user_id follows self.following_user_id" 中错过了return 声明 感谢您的帮助。但是其中有一个问题,当我在 django admin 中签入时,用户可以自己关注。如何在 ModelViewset DRF 中发送删除请求。我需要为这些创建一个 URL 吗?【参考方案3】:
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()

【讨论】:

这感觉是唯一明智的解决方案【参考方案4】:

这就是我解决问题的方法。

上面有一个很好的答案,但有人需要详细说明。所以我在写这个

我在用户模型中删除了字段关注者和关注者。并创建了新模型 UserFollowing。

您可以在爱好者马丁的回答中看到这个模型。我没有使用任何序列化程序来创建对象。

只需要两个视图(关注、取消关注)。

在关注视图中

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

这样我们就可以建立Following-Follower 关系。

使用这种关系的方式

在用户信息视图中

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")

我通常使用 JsonResponse 来响应。

serializer.data 和 qs 是用户对象

在 UserFolwingSerializer 中

fields = '__all__'

我用过这个。

【讨论】:

我的一个问题是,在关注视图中,user.id 和 follow.id 是什么? user.id 是传递给我们想要关注的用户,follow.id 是传递给我们要关注的用户吗? (user_id=user.id, following_user_id=follow.id) 这部分我指的是 如果我是用户 A 而你是用户 B 并且我(Usre A)想关注你(用户 B)。在这种情况下,user_id 是我,following_user_id 是你 兄弟,如果打扰到你,抱歉。拜托,你能看看这个:github.com/opeodedeyi/creative-api我尝试在那个应用程序中实现它,但仍然有问题。 我终于明白了,我花了很长时间,但我今天终于明白了。【参考方案5】:

我创建了一个类似于 Instagram 的关注和取消关注系统。

功能:

如果启用私人帐户 --> 发送关注请求。 如果用户在阻止列表中 --> 他们不能关注对方用户。 用户可以查看挂起的请求、已发送的请求列表、被阻止的用户 列表等。

让我们开始吧:

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)
在这里您可以关注、取消关注、发送关注请求、接受关注请求, 拒绝请求,您可以删除您的关注者。 从前端传递相反的用户配置文件ID类型 行动(关注、取消关注..)。

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"),
]

如果对任何步骤有任何疑问,请发表评论。我将简要介绍一下。

【讨论】:

【参考方案6】:

很好的答案。对我来说,要么是@GST Talib 的答案,要么是一个非常相似的答案,而不使用self

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

这样,当您添加user1.following.add(user2) 时,您可以看到user2.followers 中反映的关系

而且不需要额外的代码。

【讨论】:

这样用户可以跟随自己,如何解决这个问题。 查看逻辑检查请求用户和<pk>请求。在某些情况下,关注自己是可行的,尤其是如果您拥有具有多种功能的帐户

以上是关于如何使用 django 模型制作追随者系统的主要内容,如果未能解决你的问题,请参考以下文章

Django 上的用户追随者模型。不能在指定中间模型的 ManyToManyField 上使用 add()。改用accounts.Contact的经理

如何在 Django 中实现追随者/追随者

Django 模型:用户和关注者的数据库设计

如何使用或使用 Django 的模型过滤系统?

如何使用 MongoDB 建模“点赞”投票系统

Laravel 追随者/追随者关系