【问题标题】:Django REST Framework browsable API error when using different serializers for different HTTP methods对不同的 HTTP 方法使用不同的序列化程序时,Django REST Framework 可浏览 API 错误
【发布时间】:2023-03-09 10:30:01
【问题描述】:

我必须为现有的基于 Django 的网络应用程序创建一个 API,并且我正在使用 DRF 来执行此操作。虽然我以前从未使用过 DRF,但在我遇到这个问题之前,一切都非常清晰和良好。这是有问题的网址:

http://www.<customer-website-name>.com/api/friendship-requests/<sender-pk>/

根据给我的需求规范,应该这样处理:

  • [GET] 返回一个FriendshipRequest 对象,该对象由其pk = &lt;sender-pk&gt; 的用户发送给当前经过身份验证的用户,如果此类请求不存在,则返回404。
  • [PUT] 接受 AcceptReject 对象(例如,JSON {"accepted": false})。如果accepted == True,清理FriendshipRequest,新建Friendship对象,返回201;如果accepted == False,将FriendshipRequest标记为拒绝,返回204

这是AcceptReject 类,它是序列化程序:

class AcceptReject(object):
    def __init__(self, accepted):
        self.accepted = accepted

class AcceptRejectSerializer(serializers.Serializer):
    accepted = serializers.BooleanField()

这是我用来处理 url 的视图:

class FriendshipRequestDetail(mixins.RetrieveModelMixin,
            generics.GenericAPIView):

    def get_serializer_class(self):
        if self.request.method == 'PUT':
            return serializers.AcceptRejectSerializer
        return serializers.FriendshipRequestSerializer

    def get_object(self):
        sender = generics.get_object_or_404(get_user_model(),
                pk=self.kwargs['pk'])
        obj = generics.get_object_or_404(FriendshipRequest, 
                from_user=sender,
                to_user=self.request.user)

        self.check_object_permissions(self.request, obj)

        return obj

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return Response(status=status.HTTP_204_NO_CONTENT)

我天真地认为重写 get_serializer_class() 以根据请求方法返回序列化程序就足够了,但事实并非如此。

另外,如您所见,我什至还没有开始编写 PUT 处理程序。当我尝试使用可浏览的 api 时,它的存在就足以导致以下错误:

Environment:


Request Method: GET
Request URL: http://127.0.0.1:8000/api/friendship-requests/5/

Django Version: 1.8.4
Python Version: 3.4.3
Installed Applications:
('django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.gis',
 'rest_framework',
 'rest_framework.authtoken',
 'friendship',
 'REDACTED.REDACTED')
Installed Middleware:
('django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.middleware.security.SecurityMiddleware')


Traceback:
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
  164.                 response = response.render()
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/django/template/response.py" in render
  158.             self.content = self.rendered_content
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/rest_framework/response.py" in rendered_content
  71.         ret = renderer.render(self.data, media_type, context)
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/rest_framework/renderers.py" in render
  669.         context = self.get_context(data, accepted_media_type, renderer_context)
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/rest_framework/renderers.py" in get_context
  614.         raw_data_put_form = self.get_raw_data_form(data, view, 'PUT', request)
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/rest_framework/renderers.py" in get_raw_data_form
  561.                 content = renderer.render(serializer.data, accepted, context)
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/rest_framework/serializers.py" in data
  487.         ret = super(Serializer, self).data
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/rest_framework/serializers.py" in data
  223.                 self._data = self.to_representation(self.instance)
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/rest_framework/serializers.py" in to_representation
  447.                 attribute = field.get_attribute(instance)
File "/home/manvis/REDACTED/env/lib/python3.4/site-packages/rest_framework/fields.py" in get_attribute
  418.             raise type(exc)(msg)

Exception Type: AttributeError at /api/friendship-requests/5/
Exception Value: Got AttributeError when attempting to get a value for field `accepted` on serializer `AcceptRejectSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `FriendshipRequest` instance.
Original exception text was: 'FriendshipRequest' object has no attribute 'accepted'.

就像我说的,这是我第一次使用 DRF,但是,如果我正确理解了跟踪,似乎错误源于 DRF 试图为可浏览的 api 呈现表单,不幸的是,我不能去没有它,因为可浏览的 api 也是必需的。

我还需要更改/覆盖什么才能使可浏览的 api 用于此视图?

【问题讨论】:

    标签: django python-3.x django-rest-framework


    【解决方案1】:

    问题来自这样一个事实,即您违反了 REST 约定。您在PUT 请求中使用操作而不是资源表示。在 REST 中,您宁愿在 FriendRequest 对象中包含 is_accepted 或 state 字段,通过修改此字段,您将接受或拒绝请求。我强烈建议您向您的规范设计者指出这一点。

    在可浏览的 API 中,DRF 生成一个表单来更新记录,因此它通过调用 get_serializer()(返回 AcceptRejectSerializer)获取序列化器并将此序列化器转换为表单。然后它通过调用get_object()(返回FriendshipRequest 对象)获取记录,并尝试用从记录中获取的数据填充表单。显然记录和序列化程序中的字段不匹配,所以你得到一个错误。

    作为一个糟糕的解决方案,您可以在 get_object() 方法中添加 accept 属性:

    def get_object(self):
        sender = generics.get_object_or_404(get_user_model(),
                pk=self.kwargs['pk'])
        obj = generics.get_object_or_404(FriendshipRequest, 
                from_user=sender,
                to_user=self.request.user)
    
        self.check_object_permissions(self.request, obj)
    
        obj.accept = False
    
        return obj
    

    或者添加到FriendshipRequest模型中:

    class FriendshipRequest(...):
        # ...
    
        accept = False
    

    或者您可以尝试override view跳过填表步骤。

    【讨论】:

    • 即使在你建议的修改之后,我仍然得到同样的错误。
    • 这意味着get_object()在渲染可浏览API时被GET请求调用(我的假设失败了)。作为一个糟糕的解决方案,您可以尝试将答案中的行替换为 obj.accept = False 或将 accept = property(lambda: False) 添加到 FriendshipRequest 模型中。否则,您可以尝试override view 跳过填表步骤。但正如我所说,整个问题在于您违反了 REST 约定并做 DRF 不支持的事情。
    • 我选择了obj.accepted = False 解决方案。我知道这违反了 REST 约定,但是,根据设计规范的人的说法,这个 Web 应用程序最初是在没有考虑以后添加 API 的情况下制作的。他还说他正在尝试将Django-Friendship 库(提供友谊功能和所有模型)的功能映射到 API 请求。
    • 好吧,这取决于你。也许将其作为明确的行动路线是更好的选择……例如/api/friendship-requests/&lt;sender-pk&gt;/accept/ 和 /api/friendship-requests/&lt;sender-pk&gt;/reject/.
    猜你喜欢
    • 2020-07-17
    • 2014-02-09
    • 1970-01-01
    • 2019-05-25
    • 1970-01-01
    • 1970-01-01
    • 2017-10-11
    • 2020-03-05
    • 2021-07-30
    相关资源
    最近更新 更多