教程 5:Relationships 和 Hyperlinked
目前我们使用主键来表示模型之间的关系。在本章,我们将使用超链接关系来提高 API 的内聚性以及可读性。
# 为 API 创建一个根 URL
现在我们'snippets'和'users'创建了相应的 URL,但我们的 API 没有一个统一的入口。我们使用早些介绍的普通函数视图和@api_view
装饰器来创建一个,编辑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)
})
2
3
4
5
6
7
8
9
10
11
这里有两件事需要注意:首先我们使用的是 REST 框架提供的reverse
函数来返回完全限定的 URL;第二,URL 通过稍后定义在snippets/urls.py
中的名字来被识别。
# 为语法高亮功能创建 URL
很明显的一件事就是我们还没为语法高亮功能创建 URL。
与其它的 API 不同,这个接口我们想使用 HTML 来表示而不是 JSON。REST 框架提供了 2 种呈现 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)
2
3
4
5
6
7
8
9
10
就像往常一样,我们还需要为新的视图来创建 URL 配置。修改snippets/urls.py
,添加:
path('', views.api_root),
同时为 snippet highlights 添加 url 匹配:
path('snippets/<int:pk>/highlight/', views.SnippetHighlight.as_view()),
# 使用超链接
在 Web API 中处理实体之间的关系是一件非常头疼的事情。下面有几种不同的方法来表示关系:
- 使用主键
- 使用超链接
- 在相关实体间使用唯一的 slug 字段表示
- 在相关实体间使用默认的字符串表示
- 将相关的子实体嵌套到上级关系中
- 其他自定义方法
REST framework 支持上述所有方法,并且可以应用于正向关系、反向关系或类似通用外键这类自定义管理项中。
在这里我们在实体间使用超链接来进行关联,为了达到这个目的我们需要修改 serializers,使用HyperlinkedModelSerializer
来替代原先的ModelSerializer
:
HyperlinkedModelSerializer
默认不包含主键HyperlinkedModelSerializer
自动包含 URL 字段HyperlinkedIdentityField
- 使用
HyperlinkedRelatedField
来替代PrimaryKeyRelatedField
表示关系
我们可以很容易的改写代码,编辑snippets/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']
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这里新增了一个highlight
字段,这个字段和url
字段类型相同,区别就是它指向snippet-highlight
而非snippet-detail
。
由于我们有.json
格式的后缀,所以我们也要指明highlight
字段使用.html
来返回相应的格式。
# 为 URL 命名
如果我们创建了一个基于超链接的 API,我们需要确保每个 URL 都被命名了。让我们看看那些需要被命名的 URL:
- 根 URL 包含'user-list'和'snippet-list'
- snippet serializer 包含指向'snippet-highlight'的字段
- user serializer 包含指向'snippet-detail'的字段
- snippet serializers 和 user serializers 包含'url'字段,这个字段默认指向'{model_name}-detail',这里分别是'snippet-detail'和'user-detail'
最终,我们的snippets/urls.py
如下:
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
# API endpoints
urlpatterns = format_suffix_patterns([
path('', views.api_root),
path('snippets/',
views.SnippetList.as_view(),
name='snippet-list'),
path('snippets/<int:pk>/',
views.SnippetDetail.as_view(),
name='snippet-detail'),
path('snippets/<int:pk>/highlight/',
views.SnippetHighlight.as_view(),
name='snippet-highlight'),
path('users/',
views.UserList.as_view(),
name='user-list'),
path('users/<int:pk>/',
views.UserDetail.as_view(),
name='user-detail')
])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 分页
列表视图可能为用户返回很多代码片段,所以我们需要对结果进行分页,并且可以遍历每个单独的页面。
我们可以使用分页来修改默认的列表样式,修改tutorial/settings.py
添加:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
2
3
4
注意,所有关于 REST 框架的设定都在一个叫做'REST_FRAMEWORK'
的字典中,这帮助我们将设定信息和其他的库分离开来。
如果需要的话我们也可以自定义分页样式,但这里我们先使用默认选项。
# 测试
打开浏览器,就会发现你可以使用超链接来简单的浏览 API 了。你也会在 snippet 中看到'highlight'链接,这将返回高亮的 HTML 格式代码。
在本教程的第 6 部分中,我们将看看如何使用视图和路由器来减少构建 API 所需的代码量。