【问题标题】:How to implement a simple "like" feature in Django REST Framework?如何在 Django REST Framework 中实现一个简单的“喜欢”功能?
【发布时间】:2020-09-21 01:19:24
【问题描述】:

我是使用 DRF 为社交媒体克隆构建后端 API 的初学者。前端将在以后构建,而不是在 Django 中。我目前正在使用 Postman 与 API 进行交互。

我正在尝试实现您在 Facebook 或 Instagram 上的“点赞”功能。我无法通过 Postman 发送正确的数据来更新具有多对多关系的字段。

这是我的一些代码:

models.py

class User(AbstractUser):
    liked_haikus = models.ManyToManyField('Haiku', through='Likes')
    pass

class Haiku(models.Model):
    user = models.ForeignKey(User, related_name='haikus', on_delete=models.CASCADE)
    body = models.CharField(max_length=255)
    liked_by = models.ManyToManyField('User', through='Likes') 
    created_at = models.DateTimeField(auto_now_add=True)

class Likes(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    haiku = models.ForeignKey(Haiku, on_delete=models.CASCADE)

序列化器.py

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ['username', 'password', 'url', 'liked_haikus']
        extra_kwargs = { 'password' : {'write_only': True}}

    def create(self, validated_data):
        password = validated_data.pop('password')
        user = User(**validated_data)
        user.set_password(password)
        user.save()
        token = Token.objects.create(user=user)
        return user

class HaikuSerializer(serializers.ModelSerializer):
    class Meta:
        model = Haiku
        fields = ['user', 'body', 'liked_by', 'created_at']

class LikesSerializer(serializers.ModelSerializer):
    model = Likes
    fields = ['haiku_id', 'user_id']

views.py

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]

    @action(detail=True, methods=['get'])
    def haikus(self, request, pk=None):
        user = self.get_object()
        serializer = serializers.HaikuSerializer(user.haikus.all(), many=True)
        return Response(serializer.data)

class UserCreateViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.AllowAny]


class HaikuViewSet(viewsets.ModelViewSet):
    queryset = Haiku.objects.all()
    serializer_class = HaikuSerializer
    permission_classes = [permissions.IsAuthenticated]


class LikesViewSet(viewsets.ModelViewSet):
    queryset = Likes.objects.all()
    serializer_class = LikesSerializer
    permission_classes = [permissions.IsAuthenticated]

urls.py

router = routers.DefaultRouter(trailing_slash=False)
router.register('users', views.UserViewSet)
router.register('haikus', views.HaikuViewSet)
router.register('register', views.UserCreateViewSet)
router.register('likes', views.LikesViewSet)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls)),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    path('api-auth-token', obtain_auth_token, name='api_token_auth')
]

使用 Django Admin,我可以手动将用户设置为喜欢帖子,数据库中的字段将更新并反映在 API 请求中。

使用 Postman,我尝试将 PUT 和 PATCH 都发送到,例如:

http://127.0.0.1:8000/haikus/2

使用“表单数据”,其中 key ="liked_by" 和 value="3"(其中 3 是 user_id)。我收到了 200 响应和端点的 JSON 数据,但数据没有变化。

我已尝试 GET 和 POST 到 http://127.0.0.1:8000/likes,但收到以下错误消息:

AttributeError: 'list' 对象没有属性 'values'

我查看了 DRF 文档中的嵌套序列化程序,但它们的用例似乎并不完全相同。

如何更正我的代码并使用 Postman 正确更新多对多字段?

我想我可能需要为一个或多个 ViewSet 或 Serializer 编写一个更新函数,但我不知道是哪一个,也不知道该怎么做。

感谢所有指导、更正和资源。

【问题讨论】:

  • 可能无法正常工作,因为在LikesSerializer 中您忘记在modelfields 之前传递class Metafields

标签: django django-rest-framework postman


【解决方案1】:

要更新 liked_byMany2Many 字段,序列化程序希望您提供主键。

只需编辑您的HaikuSerializer,如下所示。它会起作用的。

class HaikuSerializer(serializers.ModelSerializer):
    liked_by = serializers.PrimaryKeyRelatedField(
        many=True,
        queryset=User.objects.all())

    class Meta:
        model = models.Haiku
        fields = ['created_by', 'body', 'liked_by', 'created_at']

    def update(self, instance, validated_data):
        liked_by = validated_data.pop('liked_by')
        for i in liked_by:
            instance.liked_by.add(i)
        instance.save()
        return instance

【讨论】:

  • 谢谢!这很近!利用您的代码,我可以 PATCH 到端点,但每次我这样做时,它都会替换前一个 PATCH 的数据,而不是添加到它。我可以一次发送多个键:值对,它会在字段中创建它们的列表。如果我只想在字段中添加另一个 pk,有没有办法不必总是重新发送整个 key:value 对列表?
  • 我更新了我的答案。通过覆盖序列化程序update 方法,您可以实现您想要的。这将帮助你@AshleySay。 不客气
  • 谢谢!这就像一个魅力。我对代码进行了一些尝试,并查看了文档中的一些术语,所以我认为我对正在发生的事情有一个非常扎实的把握。我将做出一个自我回答,解释您的代码中发生的事情,以便其他人可以了解更多信息,而不仅仅是复制/粘贴。
【解决方案2】:

adnan kaya 提供了正确的代码,我已对他投了赞成票,并将他作为正确答案进行了检查。我想通过他的解决方案来为这个问题的未来读者解释它。

liked_by = serializers.PrimaryKeyRelatedField(
        many=True,
        queryset=User.objects.all())

您可以在此处阅读有关 PrimaryKeyRelatedField 的信息:https://www.django-rest-framework.org/api-guide/relations/

由于liked_by 是一个ManyToManyField,它具有特殊属性,即ManyToMany 关系在数据库中创建一个新表,将pks 相互关联。这一行告诉 Django,该字段将通过其主键引用这些表之一。它告诉它 Liked by 里面会有多个对象,它告诉它这些对象将来自一个特定的查询集。

def update(self, instance, validated_data):
        liked_by = validated_data.pop('liked_by')
        for i in liked_by:
            instance.liked_by.add(i)
        instance.save()
        return instance

ModelSerializers 是一个类,它提供了自己的内置创建和更新函数,这些函数非常基本并且以简单的方式操作。例如,更新只会更新字段。它将获取传入的数据并使用它来替换它所指向的字段中的现有数据。

您可以在此处阅读有关 ModelSerializer 的更多信息:https://www.django-rest-framework.org/api-guide/serializers/#modelserializer

您可以覆盖这些函数并通过声明它们来指定自定义函数。我在这里宣布更新。 Update 是一个接受 3 个参数的函数。第一个是自己。你可以随心所欲地调用它,但是为了可读性,有一个强烈的约定叫它 self 。本质上,这是将函数所属的类导入到函数中,这样您就可以利用所有类函数和变量。接下来是实例。实例是当前在您尝试更新的条目中的数据。它是一个像字典一样的对象。最后,还有validated_data。这是您尝试发送到条目以对其进行更新的数据。例如,当使用表单数据更新数据库时,这将是一个字典。

liked_by = validated_data.pop('liked_by')

因为validated_data 是一个字典,你可以在它上面使用.pop() 方法。 Pop 可以获取字典的键并“将其弹出”,然后将值留给您(更正式地说,.pop('key') 将返回它的 'value')。这很好,因为至少在我的情况下,它是您想要添加到条目中的值。

  for i in liked_by:
            instance.liked_by.add(i)

这是一个简单的python for 循环。这里有一个 for 循环,因为在我的用例中,validated_data 字典的值可能是一个列表。

.add() 方法是一种特殊的方法,可用于多对多关系。您可以在此处阅读有关多对多关系的特殊方法:https://docs.djangoproject.com/en/3.1/ref/models/relations/

它做它所宣传的。它会将您发送给它的值添加到您调用它的数据中,而不是替换该数据。在这种情况下,它是 instance.liked_by(条目的当前内容)。

instance.save()

这会保存实例的新状态。

return instance

返回新实例,现在附加经过验证的数据。

我不确定这是否是向社交媒体网络应用程序实现类似功能的最理想、pythonic 或最有效的方式,但它是一种直接的方式。此代码可以重新用于将各种多对多关系添加到您的模型中(例如朋友列表/关注者和标签)。

这是我对这里发生的事情的理解,我希望它可以帮助理解多对多关系这个令人困惑的话题,以便更清楚地了解。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-26
    • 1970-01-01
    • 2022-01-11
    • 2018-06-26
    • 2017-12-17
    • 2021-10-19
    • 2020-10-31
    相关资源
    最近更新 更多