从本文开始,我们开始进入到REST framework比较核心的部分。我们首先介绍一下组成框架的几个重要的组成部分。
Request对象
REST framework引入了一个Request对象,它是继承自常规的HttpRequest,提供了更加灵活的request解析。Request对象最核心的功能是request.data属性,这个属性和request.POST很像,但是对于开发Web API,它的用处更大。
request.POST # 只处理表达数据。 只对POST方法有用。 request.data # 处理任意数据。 对POST, PUT以及PATCH方法都有用。
Response对象
REST framework也引入了一个Response对象,它是TemplateResponse的一种。它携带未渲染的数据内容,并通过内容协商(content negotiation)来决定返回给客户端的内容的类型是什么。
return Response(data) # Renders to content type as requested by the client.
Status codes
使用数字形式的HTTP状态码不容易让人理解,而且当得到一个错误的状态码时,我们也不容易注意到。REST framework通过定义的一些常量清晰明了的定义了每个状态码,比如status模块中HTTP_400_REQUEST。使用这种方式比使用数字形式的状态码要容易理解多了。
API视图的封装
REST framework提供了两种方式来写API视图。
对于基于函数的视图(function based views),我们可以使用@api_view装饰器。
对于基于类的视图(class-based views),我们可以使用APIView类。
这些封装提供了一些实用的功能。比如可以确保在我们的视图函数中接受到Request对象实例,并且添加context到Response对象实例中。
这些封装也提供其它一些行为,比如:在恰当的时候会返回205 Method Not Allowed的Response;当通过request.data获取的数据中含有错误格式的信息时,会处理任何的ParseError的异常信息。
使用组件编写API视图
至此,我们使用以上的这些组件,写一些视图函数。
我们现在不需要views.py文件中的JSONResponse类了。把这些内容删掉,重构一下我们的视图函数。
from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from .models import Snippet from .serializers import SnippetSerializer @api_view(['GET', 'POST'])def snippet_list(request): """ 列出所有的snippet,或者创建一个新的snippet :param request: :return: """ 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(status=status.HTTP_201_CREATED) else: return Response(status=status.HTTP_400_BAD_REQUEST)
上面这个视图函数相比上一篇文章的例子,代码更加简介易懂了。而且代码跟之前的Django Form中的也非常相似。在上面代码中,我们还使用了框架定义的状态码,使代码更加清楚明了。
下面是views.py文件中,对于单个snippet实例的视图函数:
@api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk, format=None): """ Retrieve, update or delete a code snippet. :param request: :return: HttpResponse or JsonResponse """ 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, status=status.HTTP_200_OK) elif request.method == 'PUT': serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return SnippetSerializer(serializer.data) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT)
上面的这个视图和上次的常规的Django视图函数很相像。
在上面的视图函数中,request.data可以处理传进来的json类型的request。当然,它也可以处理其它的类型。
与此类似,我们返回的response对象中也有data数据,REST framework可以将response对象为我们转成正确的content type。
URLs中添加可选的format后缀
现在,我们的response对象并不是固定的某个content type。我们可以在我们的API后加上format后缀,这样我们的API就可以使用给定的format了。这样的化,我们的API就可以处理类似http://example.com/api/items/4.json 了。
我可以在我们的视图函数中添加format关键字。
def snippet_list(request, format=None)
def snippet_detail(request, pk, format=None)
然后,我们可以更新一下snippets/urls.py文件,将rest_framework.urlpatterns中的format_suffix_pattern添加到URLs中。
from django.url import path from rest_framework.urlspatterns import formate_suffix_patterns from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets/<int:pk>', views.snippet_detail), ] urlpatterns = format_suffix_patterns(urlpatterns)
在上面的代码中,我们不需要手动添加另外的path路径,直接调用format_suffix_patterns方法就行了。
测试API
现在,我们在命令行中继续测试我们的API。
测试发现,返回的信息和上节测试中的表现相似。
获取所有的snippets的列表
HTTP/1.1 200 OK Allow: OPTIONS, POST, GET Content-Length: 211 Content-Type: application/json Date: Mon, 15 Jul 2019 10:59:48 GMT Server: WSGIServer/0.2 CPython/3.6.0 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN [ { "code": "foo = \"bar\"\n", "id": 1, "language": "python", "linenos": false, "style": "friendly", "title": "" }, { "code": "print(\"hello, world\")\n", "id": 2, "language": "python", "linenos": false, "style": "friendly", "title": "" } ]
我们可以通过format参数控制response返回的格式,有两种方法:
一种是在请求时使用Accept头:
http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
另一种在请求时候添加格式后缀:
http http://127.0.0.1:8000/snippets.json # JSON suffix http http://127.0.0.1:8000/snippets.api # Browsable API suffix
与此类似,我们也可以控制我们发出的request的格式。方法是添加Content-Type头。
# POST using form data http --form POST http://127.0.0.1:8000/snippets/ code="print(123456)" HTTP/1.1 201 Created Allow: OPTIONS, POST, GET Content-Length: 97 Content-Type: application/json Date: Mon, 15 Jul 2019 11:07:14 GMT Server: WSGIServer/0.2 CPython/3.6.0 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "code": "print(123456)", "id": 4, "language": "python", "linenos": false, "style": "friendly", "title": "" }
# POST using JSON http --json POST http://127.0.0.1:8000/snippets/ code="print('abcdef')" HTTP/1.1 201 Created Allow: OPTIONS, POST, GET Content-Length: 99 Content-Type: application/json Date: Mon, 15 Jul 2019 11:08:53 GMT Server: WSGIServer/0.2 CPython/3.6.0 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "code": "print('abcdef')", "id": 4, "language": "python", "linenos": false, "style": "friendly", "title": "" }
在上面的请求中,如果我们添加--debug参数,可以看到在request的头部有request类型:
http --json POST http://127.0.0.1:8000/snippets/ code="print('abcdef')" --debug HTTPie 1.0.2 Requests 2.22.0 Pygments 2.4.2 Python 3.6.0 (default, Jan 23 2017, 20:01:14) [MSC v.1900 64 bit (AMD64)]c:\python36\python.exe Windows 10<Environment { "colors": 256, "config": { "__meta__": { "about": "HTTPie configuration file", "help": "https://httpie.org/doc#config", "httpie": "1.0.2" }, "default_options": "[]" }, "config_dir": "C:\\Users\\zhang\\AppData\\Roaming\\\\httpie", "is_windows": true, "stderr": "<colorama.ansitowin32.StreamWrapper object at 0x000001F878B54470>", "stderr_isatty": true, "stdin": "<_io.TextIOWrapper name='<stdin>' mode='r' encoding='utf-8'>", "stdin_encoding": "utf-8", "stdin_isatty": true, "stdout": "<colorama.ansitowin32.StreamWrapper object at 0x000001F878B54320>", "stdout_encoding": "utf-8", "stdout_isatty": true} >>>> requests.request(**{ "allow_redirects": false, "auth": "None", "cert": "None", "data": "{\"code\": \"print('abcdef')\"}", "files": {}, "headers": { "Accept": "application/json, */*", "Content-Type": "application/json", "User-Agent": "HTTPie/1.0.2" }, "method": "post", "params": {}, "proxies": {}, "stream": true, "timeout": 30, "url": "http://127.0.0.1:8000/snippets/", "verify": true}) HTTP/1.1 201 Created Allow: OPTIONS, POST, GET Content-Length: 99 Content-Type: application/json Date: Mon, 15 Jul 2019 11:10:33 GMT Server: WSGIServer/0.2 CPython/3.6.0 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "code": "print('abcdef')", "id": 6, "language": "python", "linenos": false, "style": "friendly", "title": "" }
现在我们可以在浏览器中访问 http://127.0.0.1:8000/snippets/来查看API。
由于API通过客户端的要求来选择发送的内容类型(content-type)。在默认情况下,当我们使用浏览器访问时,返回的是HTML格式。
现在有了一个可以使用浏览器访问的可视化的API,我们的开发就变得更加简单轻松了。而且它也可以让其它想使用你的API的开发人员工作更加简便。
另外,我们还可以通过下面的链接了解更多关于可视化的API的功能,以及如何去客制化它。
本文中的源码可以在Github上查看。点我查看本文源码