1.Taking advantage of model serializers
在之前的篇幅中我们已经创建了Toy
模型以及它的序列化器ToySerializer
,并使用一些工具来验证序列化与反序列化功能。在ToySerializer序列化器中我们定义了许多与模型属性同名的属性并重写了create
与update
方法。
然而我们重复了许多已被包含在模型当中的代码和信息,比如字段类型、字段参数等。
现在,我们打算使用model serializers
来精简代码并避免信息重复,这由定义一个新的序列化器来实现,它将继承 rest_framework.serializers.ModelSerializers
而不是像之前那样继承rest_framework.serializers.Serializer
。
ModelSerializer
将会根据我们指定的模型来填充默认字段及validators。此外,它还提供默认的create
与update
接口,我们能利用它们来实现适当的create
与update
方法。
在应用的serializers.py
文件中创建序列化器:
from rest_framework import serializers
from toys.models import Toyclass ToySerializer(serializers.ModelSerializer):class Meta:model = Toyfields = ('id','name','description','release_date','toy_category','was_included_in_home')
这个新的序列化器ToySerializer继承了serializers.ModelSerializer,只定义了一个Meta内部类,该内部类中有两部分:
- model:关联的模型
- fields:所关联模型中需要序列化的字段
这里没有重写create
与update
方法,是因为ModelSerializer这个超类已经提供了这些方法的接口,直接使用就行。
接下来就可以使用RESTful Web Services: Creating API Views中提到的工具发送各种HTTP请求了,所达到的效果是一样的。
2.Understanding accepted and returned content types
在RESTful Web Services: Creating API Views的toys应用的views.py文件中我们已经创建了两个函数视图,这些函数会在必须返回JSON数据时返回JSONResponse以及一个HTTP状态码。无论它们就收到什么类型的HTTP请求,都会在响应体中返回同样的JSON数据类型。
使用postman发送一个指定请求头中Accept
为application/json
的GET请求:
将得到如下响应:
还是使用postman发送一个指定请求头中Accept
为text/html
的GET请求,也将得到Content-Type
为application/json
的响应。尽管前者application/json
指定接收JSON格式的响应,后者text/html
指定接收text/html
格式的响应。
虽然可以通过编写大量的代码转换响应中Content-Type
的格式,但我们不打算这样做,使用REST framework额外特征才是好主意。
3.Making unsupported HTTP OPTIONS requests with command-line tools
有时我们可能不知道一个资源所支持的HTTP方法是什么,这可以通过编写并发送带有OPTIONS
动作的HTTP请求来避免问题。
如果RESTful Web服务为资源或资源集合实现OPTIONS
动作,它将会在请求响应头中创建Allow
键。该键的值将包括它支持的HTTP动作或方法的逗号分隔列表。 此外,响应头将包含有关其他受支持选项的附加信息,例如它可以从请求中解析的内容类型以及它可以在响应中呈现的内容类型。
如果我们使用curl工具执行下面的命令:
这将不会获得任何返回HttpResponse的响应,因为这里的视图函数只能处理GET或POST请求。这会产生500错误,这显然不是我们需要的结果,我们想要的是一致的web服务以及在接收OPTIONS
动作后的准确响应。
4.Understanding decorators that work as wrappers
上面的问题可以通过使用REST framework提供的装饰器来解决。
我们将对两个视图函数使用@api_view
装饰器。它允许指定视图函数能处理的HTTP方法,通过这种方法就能很容易知道某个视图函数支持什么HTTP请求方法。如果请求被分配到某视图函数但该HTTP方法未被装饰器包含,会报405错误。
了解使用@api_view
装饰器时发生了什么是很重要的。它能将基于函数的视图转换为一个rest_framework.views.APIView
类的子类,这个APIView类是所有REST framework视图的基类——这与基于类的视图紧密相关,会在后面用到。
具体来说,我们需要将要支持的HTTP方法加入到装饰器的字符串列表里,它会自动产生对所包含HTTP方法的响应,以及相关的解释器、渲染器。换句话说, 这个响应包含了函数能理解的格式以及函数为这个响应所产生的格式。
到目前为止,函数只能产生JSON格式的响应,但是用了@api_view
装饰器,就能确保在视图被调用时从从请求中接收rest_framework.request.Request
类的实例。此外,该装饰器还能处理我们访问request.data属性时某些所需的 ParserError以及解析错误。
5.Using decorators to enable different parsers and renderers
我们将进行必要的更改,以使用先前引入的@api_view
装饰器,以利用通过APIView
类提供的通用行为,使其与不同的解析器和渲染器一起使用。
接下来需要重新编写views.py中的内容:
from django.shortcuts import render
from rest_framework import status
from toys.models import Toy
from toys.serializers import ToySerializer
from rest_framework.decorators import api_view
from rest_framework.response import Response@api_view(['GET', 'POST'])
def toy_list(request):if request.method == 'GET':toys = Toy.objects.all()toys_serializer = ToySerializer(toys, many=True)return Response(toys_serializer.data)elif request.method == 'POST':toy_serializer = ToySerializer(data=request.data)if toy_serializer.is_valid():toy_serializer.save()return Response(toy_serializer.data,status=status.HTTP_201_CREATED)return Response(toy_serializer.errors,status=status.HTTP_400_BAD_REQUEST)@api_view(['GET', 'PUT', 'DELETE'])
def toy_detail(request, pk):try:toy = Toy.objects.get(pk=pk)except Toy.DoesNotExist:return Response(status=status.HTTP_404_NOT_FOUND)if request.method == 'GET':toy_serializer = ToySerializer(toy)return Response(toy_serializer.data)elif request.method == 'PUT':toy_serializer = ToySerializer(toy, data=request.data)if toy_serializer.is_valid():toy_serializer.save()return Response(toy_serializer.data)return Response(toy_serializer.errors,status=status.HTTP_400_BAD_REQUEST)elif request.method == 'DELETE':toy.delete()return Response(status=status.HTTP_204_NO_CONTENT)
看见没有,@api_view
装饰器的使用还是很简单的。
移除:
class JSONResponse(HttpResponse):def __init__(self, data, **kwargs):content = JSONRenderer().render(data)kwargs['content_type'] = 'application/json'super(JSONResponse, self).__init__(content, **kwargs)
代码移除了之前的定义的JSONResponse
类,使用更加通用的rest_framework.response.Response
进行取代。
代码移除了对rest_framework.parsers.JSONParser
类的使用,这能支持更多格式解析而不只是能解析JSON格式,使用toy_serializer = ToySerializer(data=request.data)替代:
toy_data = JSONParser().parse(request)toy_serializer = ToySerializer(data=toy_data)
6.Taking advantage of content negotiation classes
APIView
类为每个视图定义了默认设置,我们可以通过在设置模块中指定所需的值来覆盖这些视图,也可以覆盖子类中的类属性。 在这种情况下,我们不会在设置模块中进行更改,但是我们必须了解APIView类使用的默认设置。 我们添加的@api_view
装饰器,它自动使用这些设置。
1,解析器
DEFAULT_PARSER_CLASSES
值指定一个字符串元组,其值指示我们要用于解析后端的默认类:
('rest_framework.parsers.JSONParser','rest_framework.parsers.FormParser','rest_framework.parsers.MultiPartParser'
)
当使用@api_view
装饰器时,RESTful Web Service能选择合适的解释器来处理不同的内容类型。因此我们就能通过 request.data
属性为每个内容类型找到键与值:
当在函数中访问 request.data
属性时,REST framework会检查请求头中Content-Type
的值并决定合适的解析器来解析请求内容,所以说必须指定请求头中的Content-Type。
2,渲染器
DEFAULT_RENDERER_CLASSES
值指定一个字符串元组,其值指示我们要用于渲染后端的默认类:
('rest_framework.renderers.JSONRenderer','rest_framework.renderers.BrowsableAPIRenderer',
)
当使用@api_view
装饰器时,RESTful Web Service能选择合适的渲染器来渲染不同的内容类型。我们需要通过 rest_framework.response.Response
实例来完成:
到目前为止,我们了解解析器和渲染器的默认设置。 难题的另一部分必须根据传入请求中指定的要求为响应选择适当的渲染器。
3,内容协商
默认情况下,DEFAULT_CONTENT_NEGOTIATION_CLASS
有一个默认值:
rest_framework.negotiation.DefaultContentNegotiation
类,所以会基于接收到的请求信息,由这个内容协商类为响应选择合适的渲染器。例如,在请求指定Accept
为text/html
,该内容协商类会选择rest_framework.renderers.BrowsableAPIRenderer
去渲染响应并生成text/html
而不是 application/json
。
在老版本代码中我们使用了JSONRenderer、JSONResponse、HttpResponse,而在新版本中使用 rest_framework.response.Response
类,通过内容协商特性,Response类会将被提供的数据渲染为合适的内容类型并将结果返回给提交请求的客户端,非常简洁。
7.Making supported HTTP OPTIONS requests with command-line tools
如果我们想知道toys集合支持哪些HTTP方法,也就是说我们想使用OPTIONS。使用CURL执行以下命令:
C:\Users\PC>curl -iX OPTIONS localhost:8000/toys/
由于使用了@api_view
装饰器,它使函数能够确定所支持的HTTP动作、所启动的解析与响应选项。结果如下:
HTTP/1.1 200 OK
Date: Wed, 21 Oct 2020 13:59:48 GMT
Server: WSGIServer/0.2 CPython/3.7.6
Content-Type: application/json
Vary: Accept, Cookie
Allow: OPTIONS, POST, GET
X-Frame-Options: SAMEORIGIN
Content-Length: 167{
"name":"Toy List","description":"","renders":["application/json","text/html"],"parses":["application/json","application/x-www-form-urlencoded","multipart/form-data"]}
- 如所见的那样,
Allow
的值是一个由请求资源集合所支持且使用逗号分割的HTTP动作表。 - 由于没有指定内容类型,所以视图函数使用默认的application/json来渲染响应的内容类型。
使用CURL工具执行:
curl -iX OPTIONS localhost:8000/toys/2
结果如下:
HTTP/1.1 200 OK
Date: Thu, 22 Oct 2020 00:07:28 GMT
Server: WSGIServer/0.2 CPython/3.7.6
Content-Type: application/json
Vary: Accept, Cookie
Allow: OPTIONS, DELETE, PUT, GET
X-Frame-Options: SAMEORIGIN
Content-Length: 169{
"name":"Toy Detail","description":"","renders":["application/json","text/html"],"parses":["application/json","application/x-www-form-urlencoded","multipart/form-data"]}
资源和资源集合可以解析和呈现相同的内容类型,因为所有内容都由装饰器和APIView类处理。
8.Working with different content types
在之前,我们在一些请求中使用了-H "Content-Type: application/json"来指定CURL工具发送application/json
格式的数据,之所以要这样,是因为CURL中默认的内容类型是application/x-www-form-urlencoded
。
当然,现在的版本已不再局限于使用JSON格式,它还支持在POST与PUT中使用application/x-www-form-urlencoded与multipart/form-data。
接下来我们要在HTTPPie工具中使用-f
选项来编写一条请求。-f将命令中的数据项序列化为表单字段并将请求头中的 Content-Type设置为application/x-www-form-urlencoded:
上面命令的作用就是创建一个新的toy实例,这是因为rest_framework.parsers.FormParser
类能够解析请求中的的数据,如果数据有效,还会在创建实例后进行保存。响应内容如下:
9.Sending HTTP requests with unsupported HTTP verbs
现在来发送一个资源集合不支持的HTTP请求:
C:\Users\PC>curl -iX PATCH localhost:8000/toys/
HTTP/1.1 405 Method Not Allowed
Date: Thu, 22 Oct 2020 00:27:24 GMT
Server: WSGIServer/0.2 CPython/3.7.6
Content-Type: application/json
Vary: Accept, Cookie
Allow: POST, OPTIONS, GET
X-Frame-Options: SAMEORIGIN
Content-Length: 42{
"detail":"Method \"PATCH\" not allowed."}
结果就是not allowed。