教程5–关系和超链接API
到目前为止,我们的API中的关系主要是靠主键维持的。在这一章节我们主要通过超链接来提高我们API的凝聚力和可被发现的能力。
创建API的root节点
现在我们已经有了’snippets’和’users’的节点了,但是我们的API还没有一个单一的入口。我们使用基于函数的视图来创建一个。添加如下代码到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)
})
这里有两点是我们需要注意的。第一,我们使用了REST框架的reverse
函数,以返回一个全限定名的URL。第二,reverse方法的第一个参数要求填入view_name,这里我们填写的是user-list
和snippet-list
,你一定在想这两个值从哪儿来的?实际上我在这里也被坑了很久,直接运行会报错
Reverse for 'xxx' with arguments '()' and keyword arguments '{}' not found. 0 pattern(s) tried: []
寻找问题的原因,无非是没有找到名字叫做xxx的视图,一开始我觉得既然是view_name,那么应该是view函数或者类可以设置一个name的属性,然而事实上并没有。后来经过多方查找,发现原来是可以给urls.py
中配置的url起一个别名:
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view(), name='snippet-list'),
url(r'^snippet/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
url(r'^users/$', views.UserList.as_view(), name='user-list'),
url(r'^user/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
url(r'^root/$', views.api_root),
]
注意上面代码中两个添加了name参数的url。加入name之后,问题解决,最终返回的结果确实为URL的全限定名:
{
"users": "http://localhost:8000/users/",
"snippets": "http://localhost:8000/snippets/"
}
后来我又重复阅读了该篇教程,才发现原来人家对于这个name是有说明的,只不过我当时并没有理解它所说的URL patterns的意思。原文中提到,有关URL patterns简称的配置,将在下文中提到。
这里就不多说了,继续往下吧。
为高亮的代码段创建一个节点
目前我们的引擎收录API还有一个显而易见的东西没有收录进来,那就是代码高亮节点。不像其他的API,对于代码高亮,我们并不适用JSON,毕竟JSON只有数据,没有格式信息,更谈不上高亮了。所以我们就是用HTML来表示代码高亮。REST框架提供了两种HTML渲染风格。一种是使用模板来渲染HTML,另一种是使用预渲染HTML。在这里,我们使用第二种方式。
另一件需要我们在创建代码高亮视图时考虑的事情是现在没有现成的通用视图可以使用。我们不返回对象实例,而是返回对象实例的属性。
抛开具体的通用视图,我们用鸡肋来代表实例,并且创建我们自己的.get()
方法。在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)
然后,我们需要配置urls.py:
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view(), name='snippet-list'),
url(r'^snippet/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
url(r'^users/$', views.UserList.as_view(), name='user-list'),
url(r'^user/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
url(r'^root/$', views.api_root),
# 注意,这里官网写的是snippets/...,但是我认为这里应该用单数,所以写成了snippet/...
url(r'^snippet/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
]
超链接化API
处理好实体之间的关系是WEB API设计中更具挑战性的一方面。我们通常会选用一下方式来表示这种关系:
- 使用主键
- 使用实体间的超链接
- 使用唯一识别字段
- 使用默认字符串表示
- 在父级中使用嵌套相关的实体来表示关系
- 其他的自定义表示
REST框架支持上述所有的形式,并且可以将它们应用于前向或者反向关系,或者将他们应用到自定义的管理器中(如通用外键)。
这里我们选用超链接样式。为了能够达到这个效果,我们使用HyperlinkedModelSerializer
来代替现有的ModelSerializer
:
- 默认不包含
id
- 包含
url
字段,使用的是HyperlinkedIdentityField
- 关系使用
PrimaryKeyRelatedField
代替HyperlinkedRelatedField
我们可以方便地重写现有的序列化器。在snippet/serializers.py
中添加以下代码:
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')
需要注意的是,我们也添加了新的highlight
字段。这个字段和url
是一样的类型。它是指向snippet-highlight
这个url pattern的。
因为我们包含了格式后缀(例如.json
),我们也需要声明highlight
字段对于任何格式后缀都应该返回html
格式的内容。
确保URL pattern已经被命名
如果我们打算使用hyperlinked API,我们需要确保我们的URL pattern已经被命名。让我们一起看下都有哪些URL pattern需要被命名。
- 我们的root api,之前提到的,需要引用到
user-list
和snippet-list
。 - snippet serializer引用到了
snippet-highlight
- user serializer引用到了
snippet-detail
- snippet和user serialzers的
url
字段默认引用了{model_name}-detail
,在这里就是snippet-detail
和user-detail
了。
最终我们的snippets/urls.py
长这样:
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
# API endpoints
urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
url(r'^snippets/$',
views.SnippetList.as_view(),
name='snippet-list'),
url(r'^snippets/(?P<pk>[0-9]+)/$',
views.SnippetDetail.as_view(),
name='snippet-detail'),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
views.SnippetHighlight.as_view(),
name='snippet-highlight'),
url(r'^users/$',
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$',
views.UserDetail.as_view(),
name='user-detail')
])
# Login and logout views for the browsable API
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
添加分页
在settings.py中添加配置:
REST_FRAMEWORK = {
'PAGE_SIZE': 10
}
注意,REST框架的设置都放到同一个命名空间的字典里,名字为:REST_FRAMEWORK
,这有助于保持它们与你的其他项目设置分离。
如果需要,你也可以自定义分页风格,但是这里我们只是用默认的。
浏览API
如果我们打开浏览器,并且浏览可被访问的API,你会发现你现在可以通过关系连接方便的浏览API了。
你也可以在snippet中看到highlight连接,点击它将会将你带到高亮代码的HTML页面。
在下一章节中,我们将一起探究如何使用ViewSets和Routers来减少代码量。
最后,到这里为止,我觉得是时候贴一下源码了:
CSDN下载地址:
http://download.csdn.net/detail/wjc133/9798926
GitHub地址:
https://github.com/wjc133/DRF-Tutorial