Django-REST-framework使用技巧(三)

  • 1. 关系和超链接 API(Relationships & hyperlinked APIs)
  • 2. 视图集合和路由(ViewSets & Routers)
  • 3. 架构和客户端库(Schemas & client libraries)

1. 关系和超级链接API

目前我们的API中的关系的通过主键来表示。我们下面将改进API的内聚力和可现性,而不是使用超链接来进来进行关系。

为我们的API的根地址创建端点

现在我们有snippetsusers的端点,但是我们没有一个指向我们API的入口。我们需要创建一个,我们将使用基于函数的常规视图和我们前面介绍的@api_view装饰器。在你的quickstart/view.py中添加:

# quickstart/view.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 frameworkreverse函数来返回完全限定的URL;
其次,URL模式通过我们下面的quickstart/urls.py中声明的便利名称进行表示。

为高亮显示snippets创建端点

另一个明显的事情是我们的pastebin API仍然缺少高亮的显示代码端点。

与其他API端点不同,我们不想使用JSON,而是只呈现HTML表示。REST framework提供了两种HTML渲染器,一种是使用模板来处理渲染HTML,另一种渲染器是我们要用于此端点的渲染器。

在创建代码高亮显示视图时候,我们需要考虑的另一件事是:不存在我们可以使用的通用视图。我们不是返回一个对象实例,而是返回宇哥对象实例属性。

我们将使用基类来表示实例,并创建我们自己的.get()方法,而不是使用具体的通用视图。在你的quickstart/view.py中添加:

# quickstart/view.py

from rest_framework import permissions, renderers

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)

像往常一样,我们需要将我们新创建的视图添加到URLconf中,我们将在quickstart/urls.py中添加一个新的url模式:

url('^$', api_root),

然后为高亮代码snippet添加一个url模式:

 url('^snippets/(?P<pk>[0-9]+)/highlight/$',SnippetHighlight.as_view()),

为我们API加上超链接

处理实体之间的关系是我们Web API设计中更具挑战性的方面之一,这里有一些我们选择代表关系的不同方法:

  • 使用主键
  • 在实体之间使用超链接
  • 在相关实体上使用唯一的标识字段
  • 使用默认的字符串代表相关实体
  • 将相关的实体嵌套在父代表中
  • 一切其他自定义的表示

REST framework支持这些样式,并且可在正式或者反向关系中应用他们。或者通过自定义管理器(如通用外键)应用他们。

在这种情况下,我们希望在实体之间使用超链接样式,为了做到这一点,我们将修改我们的序列化器来扩展HyperlinkedModelSerializer,而不是现有的ModelSerializer

HyperLinkedModelSerializerModelSerializer有一下区别:

  • 它不包含id字段
  • 它包含一个url字段,使用HyperlinkedIdentityField .
  • 关系使用HyperlinkedRelatedField,而不是PrimaryKeyRelatedField

我们可以轻松的重写现有的序列化器来使用超链接:

# quickstart/serialzers.py


from rest_framework import serializers
from .models import Snippet
from django.contrib.auth.models import User

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-highlighturl模式,而不是snippet-detailurl模式。

因为我们已经包含了格式后缀的Url。例如.json,我们还需要在highlight字段上指出,任何格式后缀的超链接返回都应该使用.html后缀。

确保我们的URL模式被命名

如果我们要有超链接的API,我们需要确保命名URL模式。我们看看我们需要命名的URL模式。

  • 我们的API根地址是指user-listsnippet-list
  • 我们的snippet序列化器包含一个指向snippet-highlight的字段。
  • 我们的user序列化器包含一个指向'snippet-list'的字段
  • 我们的snippet和user序列化器包含url字段,默认情况下将指向{model_name}-detail,在这个例子中就是snippet-detailuser-detail

我们将所有这些名称添加到我们的URLconf后,我们最后在quickstart/urls.py文件应该如下:

quickstart/urls.py

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from .views import SnippetList, SnippetDetail, UserList, UserDetail, api_root,SnippetHighlight
from django.conf.urls import include

urlpatterns = format_suffix_patterns([
    url('^api-auth/', include('rest_framework.urls')),
    url('^$',api_root),
    url('snippets/$',SnippetList.as_view(), name='snippet-list'),
    url('^snippets/(?P<pk>[0-9]+)/$', SnippetDetail.as_view(), name='snippet-detail'),
    url('^snippets/(?P<pk>[0-9])/highlight/$', SnippetHighlight.as_view(), name='snippet-highlight'),
    url('^users/$', UserList.as_view(), name='user-list'),
    url('^users/(?P<pk>[0-9]+)/$',view=UserDetail.as_view(), name='user-detail'),
])

添加分页

users和snippets的列表视图最终会返回很多实例,所以我们真的要确保结果进行分页。并允许API客户端遍历每个单独页面。
我们可以通过稍微修改tutorial/setting.py,文件来更改默认列表样式可以使用分页。添加一下设置:

# tutorial/setting.py

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10
}

注意,REST framework中的所有设置都放在一个名为REST——FRMEWORK的字典中,这有助于他们与其他项目保持良好的分离。

如果需要,我们也可以自定义分页的样式,但是这里,我们会一直使用默认。

浏览API

启动服务·

python manage.py runserver

在浏览器输入http://127.0.0.1:8000/quickstart/snippets/,结果如下图所示:

hyper.png

你还可以在 snippet 实例上看到 “highlight” 链接,这会带您跳转到代码高亮显示的 HTML 页面。
还有url的链接

2.视图集合和路由(ViewSets & Routers)

REST framework 包括一个用于处理ViewSets的抽象,它允许开发人员集中精力对API的状态和交互进行建模,并保留URL结构,根据通用的约定自动处理。

ViewSet类与View类几乎相同。只是他们提供诸如readupdate操作。而不提供诸如GET或则PUT等方法处理程序。

一个ViewSet类最后时刻只能绑定一组方法处理程序,当它被实例化为一组视图的时候,通常通过使用一个Router类来处理定义复杂的URL。

使用ViewSet重构

我们来看看当前的一组视图,并将它们重构为视图集。
首先让我们将UserListUserDetail视图重构为单个UserViewSet。我们可以删除这两个视图,并用一个类替换它们:

# quickstart/view.py

from rest_framework import viewsets #新增导入

class UserViewSet(viewsets.ReadOnlyModelViewSet):
    # 这个视图集自动提供 list 和 detail 操作

    queryset = User.objects.all()
    serializer_class = UserSerializer

这里我们使用了ReadOnlyModelViewSet类来自动提供默认的”只读“操作。我们仍然像我们使用常规驶入时一样设置querysetserializer_class属性,但是我们不需要为两个单独的类,提供相同的信息。

接下来我们将替换SnippetList,SnippetDetailSnippetHihtlight视图类。我们可以删除这三个视图,并再次使用一个类去替换它们。

# quickstart/view.py

class SnippetViewSet(viewsets.ModelViewSet):

    """
    这个视图集自动提供 'list' 'create' 'retrieve' 'update' 和 'destroy'
    另外我们还提供一个额外的'highlight'操作
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)

    @action(detail=True, renderers_classed = [renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)
    
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

这次我们使用了ModelViewSet类来获得完整的默认读写操作集。

注意,我们还使用了@action装饰器来创建一个名为highlight的自定义操作。这个装饰器可以用来添加任何不符合标准create/update/delete样式的自定义端点。

使用@action装饰器的自定义操作默认会响应GET请求。如果我们想要响应POST请求的操作,我们可以使用methods参数。

自定义操作的URL默认取决于方法名称本身。如果需要更改URL的构造方式,可以包含url_path作为装饰器的关键字参数。

明确地将ViewSet绑定到URL

当我们定义URLConf时,处理程序方法只能绑定到操作上。为了看看到底发生了什么,让我们首先从我们的ViewSets中明确地创建一组视图。

quickstart/urls.py文件中,我们将ViewSet类绑定到一组具体视图中。

# quickstart/urls.py

from .views import api_root, SnippetViewSet, UserViewSet
from rest_framework import renderers

snippet_list = SnippetViewSet.as_view({
    'get': 'list',
    'post': 'create',
})

snippet_detail = SnippetViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy',
})

snippet_highlight = SnippetViewSet.as_view({
    'get': 'highlight'
}, renderers_class=[renderers.StaticHTMLRenderer])

user_list = UserViewSet.as_view({
    'get': 'list'
})

user_detail = UserViewSet.as_view({
    'get': 'retrieve'
})

注意我们是如何从每个ViewSet类创建多个视图,通过将HTTP方法绑定到每个视图所需的操作中。

现在我们已经将资源绑定到具体的视图中,我们可以像往常一样在URL conf中注册视图。

urlpatterns = format_suffix_patterns([
    url('^api-auth/', include('rest_framework.urls')),
    url('^$',api_root),
    url('snippets/$', snippet_list, name='snippet-list'),
    url('^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
    url('^snippets/(?P<pk>[0-9])/highlight/$', snippet_highlight, name='snippet-highlight'),
    url('^users/$', user_list, name='user-list'),
    url('^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail'),
])

使用Routers

因为我们使用ViewSet类而不是View类,所以实际上我们不需要自己设计URL。将资源链接到视图和URL的约定可以使用Router类自动处理,我们需要做的就使用路由器注册相应的视图集,然后让他执行其余操作。

这里我们重写quickstart/urls.py文件:

# quickstart/urls.py

from django.conf.urls import include, url
from rest_framework.routers import DefaultRouter

# 创建路由器并注册我们的视图。
router = DefaultRouter()
router.register('snippets', views.SnippetViewSet)
router.register('users', views.UserViewSet)

# API url 现在有路由器自动确定
urlpatterns = [
    url('^', include(router.urls))
]

用路由器注册视图集类似于提供urlpattern.我们包括两个参数--视图的URL前缀和视图集本身。

我们使用DefaultRouter类为我们自动创建了API根视图,所以我们现在可以从views模块中删除api_root方法。

视图与视图集之间的权衡

使用视图集可以是一个常用的抽象。它有助于确保URL约定在你的API中保存一致,最大限度地减少编写所需的代码量,并允许你专注于API提供的交互,而不是URL conf的细节。

这并不意味着它总是正确的做法。但是用基于类的视图而不是基于函数的视图的时候,也有类似的权衡考虑。使用视图集不像单独构建视图那样明确。

3.概要和客户端库(Schemas & client libraries)

概要是一种机器可阅读文档,用于描述可用API路径,器URLS以及他们支持的操作。
高腰可以是自动生成文档的有用工具,也可以是用于驱动,可以与API进行交互的动态客户端库。

Core API

为了提供概要支持REST框架使用Core API.

CoreAPI是用于描述API的文档规范,它用于提供可用路径的内部表示形式和API公开的可能的交互。他可以用于服务器端或者客户端。

当使用服务端时候,Core API 允许支持API支持呈现范围广泛的概要或者超媒体格式。

当使用客户端时,核心API允许动态驱动的客户端库,它可以与任何公开受支持的概要或者超媒体格式的API交互。

添加概要

REST框架支持明确定义的概要视图或者自动生成概要。由于我们使用的是视图集和路由,我们可以简单地使用自动概要生成。
你需要安装coreapi,python包才能包含API概要。

pip install coreapi

现在我们可以通过在url配置中包含一个主动生成的概要视图来为API添加概要。

# quickstart/urls.py

from rest_framework.schemas import get_schema_view

urlpatterns = [
    url('^schema/$', schema_view), #新增
    url('^', include(router.urls))
]

如果你在浏览器中访问API的根路径,那么你现在应该就可以看到core json表示形式是另一个可用选项。

coreapi.png

我们也可以通过在Accept标识头中指定所需的内容类型从从命令行请求概要。

$ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/coreapi+json

{
    "_meta": {
        "title": "Pastebin API"
    },
    "_type": "document",
    ...

默认输入样式是使用Core JSON编码。
还支持其他概要格式,如Open API(以前叫Swagger)。

使用命令行客户端

现在我们的API暴露了一个概要路径,我们可以使用一个动态的客户端库与API进行交互,为了演示这个,我们使用CoreAPI命令行客户端。

命令行客户端作为一个coreapi-cli包提供:

pip install coreapi-cli

现在检查他在命令行上是否可用...


client.png

首先我们使用命令行客户端加载API概要。

$ coreapi get http://127.0.0.1:8000/schema/
<Pastebin API "http://127.0.0.1:8000/schema/">
    snippets: {
        highlight(id)
        list()
        read(id)
    }
    users: {
        list()
        read(id)
    }

我们还没有认证,所以我们现在只能看到只读路径,这与我们设置的API权限是一致的。
我们使用命令行客户端,尝试列出现有代码片段:

$ coreapi action snippets list
[
    {
        "url": "http://127.0.0.1:8000/snippets/1/",
        "id": 1,
        "highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
        "owner": "lucy",
        "title": "Example",
        "code": "print('hello, world!')",
        "linenos": true,
        "language": "python",
        "style": "friendly"
    },
    ...

一些API路径需要命名参数。例如,要获取特定代码片段的高亮HTML表示,我们需要提供一个id。

$ coreapi action snippets highlight --param id=1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<html>
<head>
  <title>Example</title>
  ...
  

验证我们的客户端

如果我们想要创建,编辑,和删除代码片段,我们需要进行有效的用户身份验证,在这种情况下,我们只需要使用基本的auth。
请确保使用实际的用户名和密码替换下面的<username><password>.

$ coreapi credentials add 127.0.0.1 <username>:<password> --auth basic
Added credentials
127.0.0.1 "Basic <...>"

现在,如果我们再次提取概要,我么应该可以看到一组可用的交互。

$ coreapi reload
Pastebin API "http://127.0.0.1:8000/schema/">
    snippets: {
        create(code, [title], [linenos], [language], [style])
        delete(id)
        highlight(id)
        list()
        partial_update(id, [title], [code], [linenos], [language], [style])
        read(id)
        update(id, code, [title], [linenos], [language], [style])
    }
    users: {
        list()
        read(id)
    }

我们现在能够与这些路径行交互。例如,要创建一个新的代码片段:

$ coreapi action snippets create --param title="Example" --param code="print('hello, world')"
{
    "url": "http://127.0.0.1:8000/snippets/7/",
    "id": 7,
    "highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
    "owner": "lucy",
    "title": "Example",
    "code": "print('hello, world')",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

然后删除一个代码片段:

$ coreapi action snippets delete --param id=7

除了命令行客户端,开发人员还可以使用客户端库与你的API进行交互。Python客户端库是第一个可用的库,并且计划即将发布一个Javascript客户端库。

有关定制模式生成和使用Core API客户端库的更多详细信息,您需要参考完整的文档。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容