当为不同的HTTP方法使用不同的序列化程序时,Django REST Framework可浏览的API错误

时间:2015-09-03 11:59:29

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

我必须为现有的基于Django的Web应用程序创建一个API,并且我正在使用DRF这样做。虽然我之前从未使用过DRF,但在遇到这个问题之前,一切都非常清晰。这是相关网址:

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

根据我给出的要求规范,它应该像这样处理:

  • [GET]返回由FriendshipRequest或404用户发送给当前已通过身份验证的用户的pk = <sender-pk>对象,如果此类请求不存在。
  • [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()

这是我用来处理网址的视图:

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适用于此视图?

1 个答案:

答案 0 :(得分:0)

问题来自于您破坏REST约定的事实。您可以在PUT请求中使用操作,而不是资源表示。在REST中,您希望在is_accepted对象中包含stateFriendRequest字段,并且通过修改此字段,您可以接受或拒绝请求。我强烈建议您向规范的设计人员指出。

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

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

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跳过表单填写步骤。