Django视图是用来处理请求和响应的,Django默认是按Form和Template来设计的,如果要处理以JSON格式为主的RESTful API,那么就需要对Django请求和响应的处理代码进行优化改造,本文就来介绍DRF在这一部分的技术升级。
Request
DRF把Django的HttpRequest扩展成了Request:
其中最核心的属性是request.data,它和request.POST的区别如下:
request.POST # 只处理表单(Form)数据,只支持POST方法 request.data # 处理任何数据,支持POST、PUT、PATCH方法
Response
DRF的Response继承自Django的django.template.response.SimpleTemplateResponse:
Response可以根据客户端的请求render合适的content type:
return Response(data)
我摘取了rendered_content()函数的代码:
@property def rendered_content(self): renderer = getattr(self, 'accepted_renderer', None) accepted_media_type = getattr(self, 'accepted_media_type', None) context = getattr(self, 'renderer_context', None) assert renderer, ".accepted_renderer not set on Response" assert accepted_media_type, ".accepted_media_type not set on Response" assert context is not None, ".renderer_context not set on Response" context['response'] = self media_type = renderer.media_type charset = renderer.charset content_type = self.content_type if content_type is None and charset is not None: content_type = "{}; charset={}".format(media_type, charset) elif content_type is None: content_type = media_type self['Content-Type'] = content_type ret = renderer.render(self.data, accepted_media_type, context) if isinstance(ret, str): assert charset, ( 'renderer returned unicode, and did not specify ' 'a charset value.' ) return ret.encode(charset) if not ret: del self['Content-Type'] return ret
Status codes
如果在代码中直接写数字形式的状态码如400,是不容易阅读的,于是DRF提供了标识符如HTTP_400_BAD_REQUEST来替代。我列一些常见的状态码标识符:
HTTP_200_OK = 200 HTTP_201_CREATED = 201 HTTP_204_NO_CONTENT = 204 HTTP_400_BAD_REQUEST = 400 HTTP_401_UNAUTHORIZED = 401 HTTP_403_FORBIDDEN = 403 HTTP_404_NOT_FOUND = 404 HTTP_405_METHOD_NOT_ALLOWED = 405 HTTP_500_INTERNAL_SERVER_ERROR = 500 HTTP_502_BAD_GATEWAY = 502 HTTP_503_SERVICE_UNAVAILABLE = 503 HTTP_504_GATEWAY_TIMEOUT = 504
全部的状态码标识符可以在rest_framework.status模块中看到。
@api_view和APIView
DRF对API视图做了2个封装:
- @api_view用于函数视图。
- APIView用于类视图。
它们提供了一些新功能,比如:
- 检查请求是Request对象
- 添加上下文到Response对象
- 返回请求错误如405 Method Not Allowed
- 当request.data格式有误时,抛出ParseError异常
改造views.py
接着就用上面这几个新实现对我们之前写的snippets/views.py进行改造:
from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from snippets.models import Snippet from snippets.serializers import SnippetSerializer @api_view(['GET', 'POST']) def snippet_list(request): """ List all code snippets, or create a new snippet. """ if request.method == 'GET': snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return Response(serializer.data) elif request.method == 'POST': serializer = SnippetSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = SnippetSerializer(snippet) return Response(serializer.data) elif request.method == 'PUT': serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT)
改动点有这些,添加了@api_view,如:
@api_view(['GET', 'POST'])
使用了状态码标识符,如:
status.HTTP_404_NOT_FOUND
使用request.data替代了 data = JSONParser().parse(request),如:
serializer = SnippetSerializer(data=request.data)
使用Response()替代了JsonResponse(),如:
return Response(serializer.data, status=status.HTTP_201_CREATED)
request.data和Response()能根据请求的JSON自动处理content type。
添加后缀格式(可选)
既然DRF能自动处理content type,那么也可以给URL指定具体的后缀格式,比如http://example.com/api/items/4.json。具体添加步骤是,先给view增加1个可选参数format:
def snippet_list(request, format=None):
def snippet_detail(request, pk, format=None):
再更新snippets/urls.py,添加format_suffix_patterns:
from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets/', views.snippet_detail), ] urlpatterns = format_suffix_patterns(urlpatterns)
这并不是必须的,实际上也无需这么做。
测试API
http http://127.0.0.1:8000/snippets/ HTTP/1.1 200 OK ... [ { "id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "friendly" }, { "id": 2, "title": "", "code": "print(\"hello, world\")\n", "linenos": false, "language": "python", "style": "friendly" } ]
跟之前的结果一样。再分别用form和json试试:
# POST using form data http --form POST http://127.0.0.1:8000/snippets/ code="print(123)" { "id": 3, "title": "", "code": "print(123)", "linenos": false, "language": "python", "style": "friendly" } # POST using JSON http --json POST http://127.0.0.1:8000/snippets/ code="print(456)" { "id": 4, "title": "", "code": "print(456)", "linenos": false, "language": "python", "style": "friendly" }
API文档
DRF提供了可视化的API HTML文档,把API URL在浏览器中打开即可看到:
东方说
最近测试开发和业务测试的话题频频出现在TesterHome论坛上,讨论激烈,我觉得从公司的角度来说,只会关注员工的产出有没有给公司带来价值,无论技术多厉害,不能创造价值终究是会优先被裁的。从个人的角度来说,只会业务测试的出路肯定是会越来越窄的,努力提高技术,辅助业务测试,同时提升效率,才是更好的发展方向。千万要谨慎选择只做纯测试工具,要依托于业务,让技术落地,在业务中发挥技术的价值,产生从业务到技术,从技术到业务的良好循环。当然,会技术是个大前提,对技术的学习不能停,比如Django REST framework。