DRF使用超链接API实现真正RESTful

很多API并不是真正的实现了RESTful,而应该叫做RPC (Remote Procedure Call 远程过程调用),Roy Fielding曾经提到了它们的区别,原文如下:

I am getting frustrated by the number of people calling any HTTP-based interface a REST API. Today’s example is the SocialSite REST API. That is RPC. It screams RPC. There is so much coupling on display that it should be given an X rating. What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint? In other words, if the engine of application state (and hence the API) is not being driven by hypertext, then it cannot be RESTful and cannot be a REST API. Period. Is there some broken manual somewhere that needs to be fixed? — Roy Fielding https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

大概意思是,如果应用状态引擎(API)不是超文本驱动的,那么就不是RESTful。

我的理解是,像超文本一样携带一个地址,可以寻址定位信息,如超文本的link属性。

超链接(Hypermedia)API

Hypermedia指的是,返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档:

{"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

摘取自:

创建api_root的Endpoint

回到教程的例子。在前面我们已经为snippets和users创建了Endpoint,现在来创建根目录的Endpoint,编辑snippets/views.py:

from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.reverse import reverse @api_view(['GET']) def api_root(request, format=None): return Response({ 'users': reverse('user-list', request=request, format=format), 'snippets': reverse('snippet-list', request=request, format=format) })

reverse()函数用来返回snippets/urls.py中viewname对应的url,如path('users/', views.UserList.as_view(),)。

然后添加到snippets/urls.py中:

path('', views.api_root), 创建SnippetHighlight的Endpoint

还记得在上篇文章中提到的Snippet.highlighted字段么:

image-20201220210707722

我们现在为它创建Endpoint,继续编辑snippets/views.py:

from rest_framework import renderers from rest_framework.response import Response class SnippetHighlight(generics.GenericAPIView): queryset = Snippet.objects.all() renderer_classes = [renderers.StaticHTMLRenderer] def get(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted)

然后添加到snippets/urls.py中:

path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view()),

因为snippet.highlighted不是JSON而是HTML,所以用[renderers.StaticHTMLRenderer]返回预渲染的(pre-rendered)HTML。

HyperlinkedModelSerializer

在Web API设计中,一般有以下几种方式来表示实体之间的关系:

主键

超链接

关系实体(the related entity),唯一标识符字段(a unique identifying slug field)

关系实体,默认字符串(the default string representation)

关系实体,嵌入到父类中(the parent representation)

其他自定义

前2个比较熟悉,后面几个有点不太懂,我理解是类似于数据库的关联关系表。

DRF支持以上所有方式,这里我们用DRF的HyperlinkedModelSerializer来实现真正的RESTful。在snippets/serializers.py中把我们之前的代码:

class SnippetSerializer(serializers.ModelSerializer): owner = serializers.ReadOnlyField(source='owner.username') class Meta: model = Snippet fields = ['id', 'title', 'code', 'linenos', 'language', 'style', 'owner'] class UserSerializer(serializers.ModelSerializer): snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all()) class Meta: model = User fields = ['id', 'username', 'snippets']

修改为:

class SnippetSerializer(serializers.HyperlinkedModelSerializer): owner = serializers.ReadOnlyField(source='owner.username') highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html') class Meta: model = Snippet fields = ['url', 'id', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style'] class UserSerializer(serializers.HyperlinkedModelSerializer): snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True) class Meta: model = User fields = ['url', 'id', 'username', 'snippets']

其中ModelSerializer换成了HyperlinkedModelSerializer,后者的区别如下:

默认不包含id字段

包含url字段,用HyperlinkedIdentityField表示

源码:serializer_url_field = HyperlinkedIdentityField

关系用HyperlinkedRelatedField表示,而不是PrimaryKeyRelatedField

源码:serializer_related_field = HyperlinkedRelatedField

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsxss.html