├── api ├── testing.md ├── exceptions.md ├── filtering.md ├── metadata.md ├── schemas.md ├── statuscodes.md ├── authentication.md ├── settings.md ├── urls.md ├── versioning.md ├── throttling.md ├── formatsuffixes.md ├── cnegotiation.md ├── serializersrelat.md ├── responses.md ├── requests.md ├── views.md ├── permissions.md ├── validators.md ├── serializersfield.md ├── pagination.md ├── parsers.md ├── viewsets.md ├── gviews.md ├── renderers.md └── routers.md ├── topics ├── docs.md ├── forms.md └── cors.md ├── .gitattributes ├── images ├── login.png ├── logo.png ├── apiroot.png ├── render1.png ├── render2.png ├── render3.png ├── usersput.png ├── userspost.png ├── quick-users.png └── usersreadonly.png ├── quickstart ├── settings.md ├── testing.md ├── serializers.md ├── views.md ├── urls.md └── project.md ├── .gitignore ├── book.json ├── home ├── install.md └── example.md ├── SUMMARY.md ├── README.md ├── tutorial ├── routers.md ├── coreapi.md ├── classview.md ├── hyperlink.md ├── req-resp.md ├── auth-perms.md └── serialization.md └── LICENSE /api/testing.md: -------------------------------------------------------------------------------- 1 | # 测试 2 | 3 | -------------------------------------------------------------------------------- /api/exceptions.md: -------------------------------------------------------------------------------- 1 | # 异常处理 2 | 3 | -------------------------------------------------------------------------------- /api/filtering.md: -------------------------------------------------------------------------------- 1 | # 过滤 2 | 3 | -------------------------------------------------------------------------------- /api/metadata.md: -------------------------------------------------------------------------------- 1 | # 元数据 2 | 3 | -------------------------------------------------------------------------------- /api/schemas.md: -------------------------------------------------------------------------------- 1 | # Schemas 2 | 3 | -------------------------------------------------------------------------------- /api/statuscodes.md: -------------------------------------------------------------------------------- 1 | # 状态码 2 | 3 | -------------------------------------------------------------------------------- /topics/docs.md: -------------------------------------------------------------------------------- 1 | # API文档生成 2 | 3 | -------------------------------------------------------------------------------- /api/authentication.md: -------------------------------------------------------------------------------- 1 | # 身份验证 2 | 3 | -------------------------------------------------------------------------------- /api/settings.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | 3 | -------------------------------------------------------------------------------- /api/urls.md: -------------------------------------------------------------------------------- 1 | # Returning URLs 2 | 3 | -------------------------------------------------------------------------------- /api/versioning.md: -------------------------------------------------------------------------------- 1 | # API 版本化 2 | 3 | -------------------------------------------------------------------------------- /api/throttling.md: -------------------------------------------------------------------------------- 1 | # Throttling 2 | 3 | -------------------------------------------------------------------------------- /topics/forms.md: -------------------------------------------------------------------------------- 1 | # HTML & Forms 2 | 3 | -------------------------------------------------------------------------------- /api/formatsuffixes.md: -------------------------------------------------------------------------------- 1 | # Format suffixes 2 | 3 | -------------------------------------------------------------------------------- /topics/cors.md: -------------------------------------------------------------------------------- 1 | # AJAX & CSRF & CORS 2 | 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-language=javascript 2 | -------------------------------------------------------------------------------- /api/cnegotiation.md: -------------------------------------------------------------------------------- 1 | # Content negotiation 2 | 3 | -------------------------------------------------------------------------------- /api/serializersrelat.md: -------------------------------------------------------------------------------- 1 | # Serializer relations 2 | 3 | -------------------------------------------------------------------------------- /images/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/login.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/logo.png -------------------------------------------------------------------------------- /images/apiroot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/apiroot.png -------------------------------------------------------------------------------- /images/render1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/render1.png -------------------------------------------------------------------------------- /images/render2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/render2.png -------------------------------------------------------------------------------- /images/render3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/render3.png -------------------------------------------------------------------------------- /images/usersput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/usersput.png -------------------------------------------------------------------------------- /images/userspost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/userspost.png -------------------------------------------------------------------------------- /images/quick-users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/quick-users.png -------------------------------------------------------------------------------- /images/usersreadonly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spider-yamet/djangorestframework-book/master/images/usersreadonly.png -------------------------------------------------------------------------------- /quickstart/settings.md: -------------------------------------------------------------------------------- 1 | ## Settings 2 | 3 | 增加```'rest_framework'```到```INSTALLED_APPS```。文件位于```tutorial/settings.py``` 4 | ```python 5 | # Application definition 6 | 7 | INSTALLED_APPS = [ 8 | …… 9 | 'rest_framework', 10 | ] 11 | ``` 12 | 13 | OK, 一切就绪。 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | 16 | .grunt 17 | 18 | # Book build output 19 | _book 20 | 21 | # eBook build output 22 | *.epub 23 | *.mobi 24 | *.pdf 25 | -------------------------------------------------------------------------------- /quickstart/testing.md: -------------------------------------------------------------------------------- 1 | ## 测试API 2 | 3 | 现在,让我们来测试我们构建的API, 首先启动服务器 4 | ```shell 5 | python manage.py runserver 6 | ``` 7 | 8 | 使用```curl```来访问我们的API 9 | ```shell 10 | curl -H 'Accept: application/json; indent=4' -u admin:password123 http://127.0.0.1:8000/users/ 11 | [ 12 | { 13 | "url": "http://127.0.0.1:8000/users/1/", 14 | "username": "admin", 15 | "email": "admin@example.com", 16 | "groups": [] 17 | } 18 | ] 19 | ``` 20 | 21 | 或者直接通过浏览器来访问 22 | 23 | ![](../images/quick-users.png) 24 | 25 | 注意,右上角的登录按钮。 26 | 27 | Great,如此简单快捷。 28 | 29 | 想要了解更深入的内容,请继续以下的内容。 -------------------------------------------------------------------------------- /quickstart/serializers.md: -------------------------------------------------------------------------------- 1 | ## 序列化 2 | 3 | 首先我们要定义序列化器。让我们创建一个新的模块```tutorial/quickstart/serializers.py``` 4 | ```python 5 | #!/usr/bin/env python 6 | 7 | from django.contrib.auth.models import User, Group 8 | from rest_framework import serializers 9 | 10 | class UserSerializer(serializers.HyperlinkedModelSerializer): 11 | class Meta: 12 | model = User 13 | fields = ('url', 'username', 'email', 'groups') 14 | 15 | 16 | class GroupSerializer(serializers.HyperlinkedModelSerializer): 17 | class Meta: 18 | model = Group 19 | fields = ('url', 'name') 20 | ``` 21 | 22 | 注意:在本例中我们使用```HyperlinkedModelSerializer```来实现一个超链接关系。当然你还可以使用主键关系或者其他各种各样的关系,不过在RESTful API设计中超链接是最好的方式。 23 | -------------------------------------------------------------------------------- /quickstart/views.md: -------------------------------------------------------------------------------- 1 | ## 视图 2 | 3 | 现在,我们该写一些视图了,打开```tutorial/quickstart/views.py``` 4 | ```python 5 | #!/usr/bin/env python 6 | 7 | from django.contrib.auth.models import User, Group 8 | from rest_framework import viewsets 9 | from tutorial.quickstart.serializers import UserSerializer, GroupSerializer 10 | 11 | class UserViewSet(viewsets.ModelViewSet): 12 | """ 13 | API endpoint 允许 查看 或者 编辑 用户 14 | """ 15 | queryset = User.objects.all().order_by('-date_joined') 16 | serializer_class = UserSerializer 17 | 18 | 19 | class GroupViewSet(viewsets.ModelViewSet): 20 | """ 21 | API endpoint 允许 查看 或者 编辑 用户组 22 | """ 23 | queryset = Group.objects.all() 24 | serializer_class = GroupSerializer 25 | ``` 26 | 27 | 和传统的做法不一样,我们不是将多个视图写在一起,而是将所有公共行为集合```viewsets```类中。 28 | 29 | 如果有需求,可以很轻松的将各个功能拆解到单独的视图中,但是使用```viewsets```可以保证我们的视图非常的干净整洁。 30 | -------------------------------------------------------------------------------- /quickstart/urls.md: -------------------------------------------------------------------------------- 1 | ## 路由 2 | 3 | 现在让我们连接API到路由中,在```tutorial/urls.py``` 4 | ```python 5 | from django.conf.urls import url 6 | from django.conf.urls import include 7 | from rest_framework import routers 8 | from tutorial.quickstart import views 9 | 10 | 11 | router = routers.DefaultRouter() 12 | router.register(r'users', views.UserViewSet) 13 | router.register(r'groups', views.GroupViewSet) 14 | 15 | # 将自动生成的URL 配置 放置在真正的路由配置中 16 | # 此外, 我们还配置登录 登出的 路由 17 | urlpatterns = [ 18 | url(r'^', include(router.urls)), 19 | url(r'^api-auth', include('rest_framework.urls', namespace='rest_framework')) 20 | ] 21 | ``` 22 | 23 | 因为我们使用的是```viewsets(视图集合)```而不是多个视图```views```。所以我们可以通过将```viewsets```注册到```router```类中来自动的生成API相关的URL。 24 | 25 | 如果,我们需要对API URL进行更多的控制,那么可以降低到使用常规的```class-based```基于类的视图的,然后对每个URL 进行详细的控制。 26 | 27 | 最后,我们为可视化的API添加了登录/登出视图。当然,这是可选的,但是这对于可视化API和一些需要认证的API这是非常有必要的。 -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "GitBook学习笔记", 3 | "author": "nick", 4 | "description": "Django REST framework 3 中文文档", 5 | "links": { 6 | "sidebar": { 7 | "Author's Blog": "http://ibash.cc" 8 | } 9 | }, 10 | "plugins": ["-sharing", "sharing-plus", "search-pro"], 11 | "pluginsConfig": { 12 | "theme-default": { 13 | "showLevel": true 14 | }, 15 | "sharing": { 16 | "douban": false, 17 | "facebook": false, 18 | "google": false, 19 | "hatenaBookmark": false, 20 | "instapaper": false, 21 | "line": false, 22 | "linkedin": false, 23 | "messenger": false, 24 | "pocket": false, 25 | "qq": true, 26 | "qzone": true, 27 | "stumbleupon": false, 28 | "twitter": false, 29 | "viber": false, 30 | "vk": false, 31 | "weibo": true, 32 | "whatsapp": false, 33 | "all": [ 34 | "facebook", "google", "twitter", 35 | "weibo", "qq", "linkedin", 36 | "qzone", "douban" 37 | ] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /home/install.md: -------------------------------------------------------------------------------- 1 | ## 依赖环境 2 | 3 | REST framework 有以下依赖: 4 | - Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6) 5 | - Django (1.10, 1.11, 2.0) 6 | 7 | 下面的包是可选的: 8 | - coreapi (1.32.0+) - 生成schema支持 9 | - Markdown (2.1.0+) - 可视化的API支持 10 | - django-filter (1.0.1+) - 过滤支持 11 | - django-crispy-forms - 为过滤提供更好的HTML显示 12 | - django-guardian (1.1.1+) - 对象级别的权限支持 13 | 14 | ## 安装 15 | 16 | 使用```pip```可以安装REST framework和其他任何的可选安装包: 17 | ```shell 18 | pip install django==1.11.10 19 | 20 | pip install djangorestframework==3.7.7 21 | 22 | pip install markdown # 为可视化API 提供支持 23 | 24 | pip install django-filter # 过滤支持 25 | ``` 26 | 27 | 或者是从github克隆项目: 28 | ```shell 29 | git clone git@github.com:encode/django-rest-framework.git 30 | ``` 31 | 32 | 添加```'rest_framework'```到Django的```INSTALLED_APPS```设置中: 33 | ```python 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | …… 38 | 'rest_framework', 39 | ] 40 | ``` 41 | 42 | 如果你打算使用可视化的API,那么你可能需要REST framework的登录和登出功能。在你的```root urls.py```中增加如下内容: 43 | ```python 44 | urlpatterns = [ 45 | …… 46 | url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) 47 | ] 48 | ``` 49 | 注意:url的路径可以随便定义,但是必须```include('rest_framework.urls')``` 50 | 51 | ![](../images/login.png) 52 | -------------------------------------------------------------------------------- /quickstart/project.md: -------------------------------------------------------------------------------- 1 | ## 介绍 2 | 3 | 在本章我们会创建一个简单的API来允许管理员账号可以查看和编辑用户和用户组。 4 | 5 | ## 项目设置 6 | 7 | 创建一个新的项目,命名为```tutorial```, 然后开启一个新的app,命名为```quickstart```。 8 | ```shell 9 | # 创建项目目录 10 | mkdir tutorial 11 | cd tutorial 12 | 13 | # 创建虚拟的环境 14 | mkvirtualenv --no-site-packages -p /usr/local/bin/python3 env 15 | 16 | # 在虚拟环境中安装Django和 Django REST framework 17 | pip install django==1.11.10 18 | pip install djangorestframework==3.7.7 19 | 20 | # 创建一个新的项目并启动一个APP 21 | django-admin.py startproject tutorial . # 注意最后的 . 字符 22 | cd tutorial/ 23 | django-admin.py startapp quickstart 24 | cd .. 25 | ``` 26 | 27 | 此时项目的目录结构如下: 28 | ```shell 29 | tree ./ 30 | ./ 31 | ├── manage.py 32 | └── tutorial 33 | ├── __init__.py 34 | ├── quickstart 35 | │   ├── __init__.py 36 | │   ├── admin.py 37 | │   ├── apps.py 38 | │   ├── migrations 39 | │   │   └── __init__.py 40 | │   ├── models.py 41 | │   ├── tests.py 42 | │   └── views.py 43 | ├── settings.py 44 | ├── urls.py 45 | └── wsgi.py 46 | 47 | 3 directories, 12 files 48 | ``` 49 | 在项目的目录中创建应用,这种方式看起来和常规的方式不同。这里的目的主要为为了利用项目的命名空间,来避免和外部的其他模块造成冲突。 50 | 51 | 现在,进行首次的数据库同步: 52 | ```shell 53 | python manage.py migrate 54 | ``` 55 | 56 | 接下来我们创建一个初始的用户,用户名为```admin```, 密码为```password123```。 57 | ```shell 58 | python manage.py createsuperuser --email admin@example.com --username admin 59 | ``` 60 | 61 | 现在一切就绪,进入app目录,开始编码吧... 62 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | ## 安装介绍 4 | * [介绍](README.md) 5 | * [安装](home/install.md) 6 | * [举例](home/example.md) 7 | 8 | ## 快速上手 9 | * [创建项目](quickstart/project.md) 10 | * [序列化](quickstart/serializers.md) 11 | * [视图](quickstart/views.md) 12 | * [路由](quickstart/urls.md) 13 | * [Settings](quickstart/settings.md) 14 | * [测试API](quickstart/testing.md) 15 | 16 | ## 教程 17 | * [序列化](tutorial/serialization.md) 18 | * [Requests & Responses](tutorial/req-resp.md) 19 | * [类视图](tutorial/classview.md) 20 | * [认证和权限](tutorial/auth-perms.md) 21 | * [关系和超链接](tutorial/hyperlink.md) 22 | * [视图集合和路由](tutorial/routers.md) 23 | * [coreapi](tutorial/coreapi.md) 24 | 25 | ## API 指南 26 | * [Requests](api/requests.md) 27 | * [Responses](api/responses.md) 28 | * [视图Views](api/views.md) 29 | * [通用视图Generic views](api/gviews.md) 30 | * [视图组Viewsets](api/viewsets.md) 31 | * [路由器Routers](api/routers.md) 32 | * [解析器Parsers](api/parsers.md) 33 | * [渲染器Renderers](api/renderers.md) 34 | * [序列化器Serializers](api/serializers.md) 35 | * [序列化器字段Serializer fields](api/serializersfield.md) 36 | * [Serializer relations](api/serializersrelat.md) 37 | * [校验器Validators](api/validators.md) 38 | * [身份验证](api/authentication.md) 39 | * [权限](api/permissions.md) 40 | * [限流Throttling](api/throttling.md) 41 | * [过滤](api/filtering.md) 42 | * [分页](api/pagination.md) 43 | * [API 版本化](api/versioning.md) 44 | * [内容协商Content negotiation](api/cnegotiation.md) 45 | * [元数据](api/metadata.md) 46 | * [Schemas](api/schemas.md) 47 | * [Format suffixes](api/formatsuffixes.md) 48 | * [Returning URLs](api/urls.md) 49 | * [异常处理](api/exceptions.md) 50 | * [状态码](api/statuscodes.md) 51 | * [测试](api/testing.md) 52 | * [Settings](api/settings.md) 53 | 54 | ## 最佳实践 55 | * [API文档生成](topics/docs.md) 56 | * [HTML & Forms](topics/forms.md) 57 | * [AJAX & CSRF & CORS](topics/cors.md) 58 | 59 | -------------------------------------------------------------------------------- /home/example.md: -------------------------------------------------------------------------------- 1 | ## 举例 2 | 3 | 让我们看一个用REST framework来构建一个简单的基于model的API。 4 | 5 | 我们将创建一个可读写的API来访问我们项目的用户信息。 6 | 7 | 所有关于REST framework的全局配置都放置在一个名为```REST_FRAMEWORK```的字典中。首先在```settings.py```中增加如下内容: 8 | ```python 9 | REST_FRAMEWORK = { 10 | # 使用Django 标准的 `django.contrib.auth` 权限, 11 | # 或者 对于 未认证的用户提供只读权限. 12 | 'DEFAULT_PERMISSION_CLASSES': [ 13 | 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' 14 | ] 15 | } 16 | ``` 17 | 提示: 不要忘记在```INSTALLED_APPS```中添加```rest_framework```。 18 | 19 | 现在,我们准备创建API。下面是我们项目的```root urls.py```模块: 20 | ```python 21 | from django.conf.urls import url 22 | from django.contrib import admin 23 | from django.conf.urls import include 24 | from django.contrib.auth.models import User 25 | from rest_framework import routers 26 | from rest_framework import serializers 27 | from rest_framework import viewsets 28 | 29 | 30 | # Serializers 定义了API 数据的表现形式 31 | class UserSerializer(serializers.HyperlinkedModelSerializer): 32 | class Meta: 33 | model = User 34 | fields = ('url', 'username', 'email', 'is_staff') 35 | 36 | 37 | # Viewsets 定义了视图行为 38 | class UserViewset(viewsets.ModelViewSet): 39 | queryset = User.objects.all() 40 | serializer_class = UserSerializer 41 | 42 | 43 | # Routers 提供了一个非常便捷的方式 自动生成 URL 配置 44 | router = routers.DefaultRouter() 45 | router.register(r'user', UserViewset) 46 | 47 | # 将自动生成的URL 配置 放置在真正的路由配置中 48 | # 此外, 我们还配置登录 登出的 路由 49 | urlpatterns = [ 50 | url(r'^admin/', admin.site.urls), 51 | url(r'^api/', include(router.urls)), 52 | url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) 53 | ] 54 | ``` 55 | 56 | 下面让我们创建一个Django用户: 57 | ```shell 58 | python manage.py makemigrations 59 | 60 | python manage.py migrate 61 | 62 | python manage.py createsuperuser 63 | ``` 64 | 65 | 现在你可以在浏览器输入```http://127.0.0.1:8001/api/(地址和端口根据自己启动的情况而定)```来查看```users```API。 66 | 67 | ![](../images/apiroot.png) 68 | 69 | 点击```users```的API链接,进入如下界面: 70 | 71 | ![](../images/usersreadonly.png) 72 | 73 | 右上角有登录按钮,如果登录, 那么就可以创建或者删除用户, 登录之后,界面如下: 74 | 75 | ![](../images/userspost.png) 76 | 注意, 这个界面只能进行```users```的创建。```POST```方法。 77 | 78 | 点击某个用户的URL,这里是```http://127.0.0.1:8001/api/user/1/```,就会进入某个对象的界面,如下: 79 | 80 | ![](../images/usersput.png) 81 | 注意,这个界面可以对某个具体的对象(这里是用户), 进行更新或者删除的操作。 ```PUT``` 和 ```DELETE```方法。 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 介绍 2 | 3 | --- 4 | 5 | 注意: 本文档针对的是REST framework的版本3。 6 | 7 | 编写本文档时,所用的版本号: 8 | - Django(1.11.10) 9 | - djangorestframework(3.7.7) 10 | - Python(3.6.4) 11 | 12 | --- 13 | 14 | ![](./images/logo.png) 15 | 16 | 17 | Django REST framework 是一个功能强大的灵活的构建Web APIs的工具包。 18 | 19 | 为什么要使用REST framework ? 20 | - 基于Web 浏览器的API 可视化,对于你的开发将会有很大的帮助 21 | - 身份认证策略包含```OAuth1a```和```OAuth2``` 22 | - 同时支持ORM和非ORM的数据源的序列化 23 | - 完整的REST API 功能支持, 包括认证、权限、限流、分页等 24 | - 可定制化 - 如果不需要功能强大的特性,那么可以基于基础的功能类(```function-based```)进行开发 25 | - 文档完善,社区活跃 26 | - Mozilla, Red Hat 等公司正在使用REST framework 27 | 28 | --- 29 | 30 | #### 什么人适合本文档? 31 | 32 | 阅读本文档之前,至少要对Django有一定的了解。 33 | 34 | --- 35 | 36 | --- 37 | 38 | #### 在线阅读 39 | 40 | 可以通过[GitBook]()或者 41 | 42 | --- 43 | 44 | ## 目录 45 | 46 | - 安装 47 | 1. [介绍](./README.md) 48 | 2. [安装](./home/install.md) 49 | 3. [举例](./home/example.md) 50 | - 快速上手 51 | 1. [创建项目](./quickstart/project.md) 52 | 2. [序列化](./quickstart/serializers.md) 53 | 3. [视图](./quickstart/views.md) 54 | 4. [路由](./quickstart/urls.md) 55 | 5. [Settings](./quickstart/settings.md) 56 | 6. [测试API](./quickstart/testing.md) 57 | - 完整教程 58 | 1. [序列化](./tutorial/serialization.md) 59 | 2. [Requests & Responses](./tutorial/req-resp.md) 60 | 3. [类视图](./tutorial/classview.md) 61 | 4. [认证和权限](./tutorial/auth-perms.md) 62 | 5. [关系和超链接](./tutorial/hyperlink.md) 63 | 6. [视图集合和路由](./tutorial/routers.md) 64 | 7. [coreapi](./tutorial/coreapi.md) 65 | - API指南 66 | 1. [Requests](./api/requests.md) 67 | 2. [Responses](./api/responses.md) 68 | 3. [视图Views](./api/views.md) 69 | 4. [通用视图Generic views](./api/gviews.md) 70 | 5. [视图组Viewsets](./api/viewsets.md) 71 | 6. [路由器Routers](./api/routers.md) 72 | 7. [解析器Parsers](./api/parsers.md) 73 | 8. [渲染器Renderers](./api/renderers.md) 74 | 9. [序列化器Serializers](./api/serializers.md) 75 | 10. [序列化器字段Serializer fields](./api/serializersfield.md) 76 | 11. [Serializer relations](./api/serializersrelat.md) 77 | 12. [校验器Validators](./api/validators.md) 78 | 13. [身份验证](./api/authentication.md) 79 | 14. [权限](./api/permissions.md) 80 | 15. [限流Throttling](./api/throttling.md) 81 | 16. [过滤](./api/filtering.md) 82 | 17. [分页](./api/pagination.md) 83 | 18. [API 版本化](./api/versioning.md) 84 | 19. [内容协商Content negotiation](./api/cnegotiation.md) 85 | 20. [元数据](./api/metadata.md) 86 | 21. [Schemas](./api/schemas.md) 87 | 22. [Format suffixes](./api/formatsuffixes.md) 88 | 23. [Returning URLs](./api/urls.md) 89 | 24. [异常处理](./api/exceptions.md) 90 | 25. [状态码](./api/statuscodes.md) 91 | 26. [测试](./api/testing.md) 92 | 27. [Settings](./api/settings.md) 93 | - 最佳实践 94 | 1. [API文档生成](./topics/docs.md) 95 | 2. [HTML & Forms](../topics/forms.md) 96 | 3. [AJAX & CSRF & CORS](topics/cors.md) 97 | -------------------------------------------------------------------------------- /api/responses.md: -------------------------------------------------------------------------------- 1 | ## Responses 2 | 3 | > 与基本的HttpResponse 对象不同,TemplateResponse 对象会记住视图提供的模板和上下文的详细信息来计算响应。 响应的最终结果在后来的响应处理过程中直到需要时才计算。 4 | ——Django 官方文档 5 | 6 | 点击[这里查看官方文档](https://docs.djangoproject.com/en/2.0/ref/template-response/) 7 | 8 | REST framework支持HTTP内存的协商通过给定的```Response```类,你返回的内容可以渲染成多种```content type```,这依赖于客户端的请求。 9 | 10 | 这里的```Response```是Django的```SimpleTemplateResponse```的子类。REST framework使用标准的HTTP内容协商来确定最终返回的内容应该如何被渲染。 11 | 12 | 对于```Response```类并不是强制使用的,如果需要的话,你可以从你的视图中返回一个常规的```HttpResponse```或者是```StreamingHttpResponse```对象。使用```Response```仅是提供了一个方便的接口用于返回多种多样的格式的响应数据。 13 | 14 | 除非你出于某些原因想定制化开发REST framework,否则你应该总是使用```APIView```类或者是```@api_view```函数,为你的视图返回一个```Response```对象。这样做可以在你的视图返回结果之前,来确定视图执行内容协商并且选择恰当合适的渲染器来渲染响应结果。 15 | 16 | 关于```SimpleTemplateResponse```的参考链接,点[这里](http://python.usyiyi.cn/documents/django_182/ref/template-response.html)。 17 | 18 | --- 19 | 20 | ### 创建Response 21 | 22 | #### ```Response()``` 23 | 24 | **初始化语法:** ```Response(self, data=None, status=None, template_name=None, headers=None, exception=False, content_type=None)``` 25 | 26 | 和常规的```HttpResponse```对象不同,你不需要传入一个已经渲染的内容来实例化```Response```对象。相反你传递的是一个未渲染的数据。 27 | 28 | 即使```Response```类使用渲染器也并不能处理复杂的数据类型,比如Django Model实例类型。所以在创建```Response```对象之前,你需要序列化数据为原始的数据类型。 29 | 30 | 你可以使用REST framework的```Serializer```类来处理数据的序列化,或者使用你自定义的序列化器。 31 | 32 | 参数: 33 | 34 | - ```data```: 用于response的已经序列的数据 35 | - ```status```: 用于response的状态码 36 | - ```template_name```: 如果```HTMLRenderer```被选择,那么使用这个模板来渲染数据 37 | - ```headers```: 一个字典,用于在response中的HTTP头 38 | - ```content_type```: 用于response的content type。通常,这是有渲染器通过内容协商自动设定。有一些特殊的情况可以显示的指定内容类型。 39 | 40 | --- 41 | 42 | ### 属性 43 | 44 | #### ```.data``` 45 | 46 | 未渲染的,已经序列化的响应数据。 47 | 48 | #### ```.status_code``` 49 | 50 | HTTP响应代码。 51 | 52 | #### ```.content``` 53 | 54 | 已经渲染的内容。如果要调用```.render()```方法,那么```.content```必须要可以被访问。 55 | 56 | #### ```.template_name``` 57 | 58 | 如果指定了,这显示指定的模板名。 59 | 60 | #### ```.accepted_renderer``` 61 | 62 | 渲染器(```renderer```)实例,用于渲染响应结果。 63 | 64 | 由```APIView```或者是```@api_view```自动设置。 65 | 66 | #### ```.accepted_media_type``` 67 | 68 | 在内容协商阶段确定的媒体类型(```media type```)。 69 | 70 | 由```APIView```或者是```@api_view```自动设置。 71 | 72 | #### ```.renderer_context``` 73 | 74 | 将要传递给渲染器的```.render()```方法的一个额外的上下文信息。是一个字典。 75 | 76 | 由```APIView```或者是```@api_view```自动设置。 77 | 78 | --- 79 | 80 | ### 标准的```HttpResponse```属性 81 | 82 | REST framework中的```Response```是Django原生的```SimpleTemplateResponse```的扩展。所以标准的属性和方法都可以使用。比如你可以使用标准的方法设置响应头: 83 | 84 | ```python 85 | response = Response() 86 | response['Cache-Control'] = 'no-cache' 87 | ``` 88 | 89 | #### ```.render()``` 90 | 91 | **初始化语法:** ```.render()``` 92 | 93 | 这个方法被调用用于将响应的序列化数据渲染到最终的响应内容中。当```.render()```被调用,响应内容被设置,调用```.render(data, accepted_media_type, renderer_context)```方法,在```accepted_renderer```实例中。 94 | 95 | 通常你不需要自己去调用```.render()```。 96 | 97 | --- 98 | 99 | 参考链接: 100 | - https://docs.djangoproject.com/en/2.0/ref/template-response/ 101 | -------------------------------------------------------------------------------- /api/requests.md: -------------------------------------------------------------------------------- 1 | ## Requests 2 | 3 | > *如果你基于REST做一些web应用,那么你应该忽略```request.POST```.* 4 | —— Malcom Tredinnick 5 | 6 | REST framework的```Request```类是标准的```HttpRequest```的扩展(并不是类继承),增加了更加灵活的请求处理和请求认证。 7 | 8 | --- 9 | 10 | ### Request 解析 11 | 12 | REST framework的```Request```对象提供了非常灵活的请求解析处理,无论是JSON数据格式还是其他类型的数据格式,```Request```对象都会用同样的方式来处理所有的表单数据。 13 | 14 | #### ```.data``` 15 | 16 | ```request.data```返回的是经过处理后的request body的内容。这和标准的```request.POST```和```request.FILES```属性是类似的,除此之外,还有以下特性: 17 | 18 | - 包含所有解析的内容,包含*file*和*non-file*的输入 19 | - 它支持解析除了```POST```以外其他的HTTP方法的内容,这意味着你可以访问```PUT```和```PATCH```的请求 20 | - 它支持REST framework灵活的请求解析,不只是简简单单的表单处理。比如,处理JSON数据和处理表单数据是一样的。 21 | 22 | 更多的解析,查看文档 [解析处理](./parsers.md) 23 | 24 | #### ```.query_params``` 25 | 26 | ```request.query_params``` 对于标准的```request.GET```是一个更加正确的命名方式。 27 | 28 | 为了代码更加的清晰,我们建议您使用```request.query_params```来代替Django标准的```request.GET```方法。 29 | 30 | 这样做能保证代码更加正确且清晰,因为所有的HTTP方法都会包含有请求参数,而不仅仅是```GET```方法。 31 | 32 | #### ```.parsers``` 33 | 34 | REST中的```APIView```类或者是```@api_view```装饰器会确保```.parsers```属性正确的设置为一个```list```。这个```list```包含一组```Parser```实例。 35 | 36 | 这些```Parser```实例是基于```view```中的```parser_classes```或者全局的```DEFAULT_PARSER_CLASSES```设置。 37 | 38 | 通常情况下不会访问这个属性。 39 | 40 | 可以查看```rest_framework.view.APIView```类中的```get_parsers```方法。有多少个对象```list```中就会有多少个```Parser```实例。 41 | 42 | --- 43 | 44 | **注意:** 如果客户端发送的是一个畸形的内容,那么访问```request.data```可能会抛出```ParseError```异常。默认的REST framework的```APIView```类或者是```api_view```装饰器会捕捉错误,并返回一个```400 Bad Request```响应。 45 | 46 | 如果客户端发送请求并携带```content-type```,REST framework不能解析,那么会抛出```UnsupportedMediaType```异常,REST 会捕捉这个异常,并返回```415 Unsupported Media Type```响应。 47 | 48 | --- 49 | 50 | ### 内容协商 51 | 52 | REST framework中的```request```还暴露了一些方法允许通过协商来决定结果。也就是说,这允许你自己实现一些行为,比如给不同的数据类型一个不同的序列化方案。 53 | 54 | 源码位置: ```rest_framework.view.APIView```类中的```perform_content_negotiation```方法。 55 | 56 | #### ```.accepted_renderer``` 57 | 58 | 这是在内容协商过程中允许选择的渲染实例。 59 | 60 | #### ```accepted_media_type``` 61 | 62 | 这个字符串代表在内容协商过程中允许的类型。 63 | 64 | --- 65 | 66 | ### 认证 67 | 68 | REST framework为认证提供了非常灵活的需求: 69 | 70 | - 在不同API部分可以使用不同的认证策略 71 | - 支持使用多种认证策略 72 | - 对进来的请求提供关联的用户和Token信息 73 | 74 | #### ```.user``` 75 | 76 | REST framework中的```request.user```是```django.contrib.auth.models.User```返回的一个实例。这个行为取决于使用的认证策略。 77 | 78 | 如果请求是未认证的,那么```request.user```返回的是```django.contrib.auth.models.AnonymousUser```的实例。 79 | 80 | 更多详情查看 [认证中心](./authentication.md) 81 | 82 | #### ```.auth``` 83 | 84 | REST framework中的```request.auth``` 会返回任何附加的认证上下文。准确的说这依赖于所使用的认证策略。通常,可能会是已经认证的用户的Token实例。 85 | 86 | 如果请求未认证,或者说没有额外附加的上下文,那么```request.auth```将返回```None```。 87 | 88 | 更多详情查看 [认证中心](./authentication.md) 89 | 90 | #### ```.authenticators``` 91 | 92 | REST framework中的```APIView```类或者```api_view```装饰器会确保这个数据自动的被设置。这个属性是一个由```Authentication```实例组成的```list```。 93 | 94 | 这些```Authentication```实例基于```view```中定义的```authentication_classes```或者是全局的```DEFAULT_AUTHENTICATORS```的设置。 95 | 96 | 同样的,通常不需要访问这个属性。 97 | 98 | 源码位置:```rest_framework.view.APIView```类中的```get_authenticators```方法。 99 | 100 | --- 101 | 102 | **注意:** 当调用```.user```或者```.auth```属性的时候,你可能会看到```WrappedAttributeError```异常。 103 | 104 | --- 105 | 106 | ### 浏览器增强 107 | 108 | REST framework支持一些增强的浏览器,比如基于浏览器表单的```PUT```、```PATCH```和```DELETE```。 109 | 110 | #### ```.method``` 111 | 112 | REST framework中的```request.method```会返回请求的HTTP方法的大写字符串。 113 | 114 | 基于浏览器的```PUT```、```PATCH```和```DELETE```也支持。 115 | 116 | 更多信息查看 [浏览器增强]() 117 | 118 | #### ```.content_type``` 119 | 120 | REST framework中的```request.content_type```返回一个字符串对象,表示当前请求的内容主体的类型(media type of the HTTP request's body)。如果没有提供,那么返回空。 121 | 122 | 通常不需要直接访问请求内容的类型,大多数情况REST framework默认的行为就够用了。 123 | 124 | 如果你真的需要访问请求内容的类型,那么应该使用```.content_type```而不是```request.META.get('HTTP_CONTENT_TYPE')```。因为```.content_type```透明的支持基于浏览器的non-form内容。 125 | 126 | 更多信息查看 [浏览器增强]() 127 | 128 | 129 | #### ```.stream``` 130 | 131 | `REST framework中的``request.stream```返回一个stream代表请求主体的内容。 132 | 133 | 通常不需要直接访问请求内容的类型,大多数情况REST framework默认的行为就够用了。 134 | 135 | --- 136 | 137 | ### 标准的```HttpRequest```属性 138 | 139 | REST framework的```Request```作为标准的Django的```HttpRequest```扩展,肯定的一点是Django标准的属性和方法都可以正常使用。比如```request.META```和```request.session```都能正常的使用。 140 | 141 | 注意: REST framework的```Request```并不是Django的```HttpRequest```类的继承。 142 | -------------------------------------------------------------------------------- /tutorial/routers.md: -------------------------------------------------------------------------------- 1 | # 视图集合和路由 2 | 3 | REST框架包含一个用于处理的抽象概念```ViewSets```,它允许开发人员专注于对API的状态和交互进行建模,并根据通用约定自动处理URL构造。 4 | 5 | ```ViewSet```类与类几乎相同```View```,除了它们提供例如```read```或```update```的操作,而不是例如``get``` or ``put``的方法处理程序。 6 | 7 | 一个```视图集合```类只在最后时刻绑定到一组方法来处理程序,当它被实例化为一组视图时,通常通过使用一个```Router```类来为您处理定义URL conf的复杂性。 8 | 9 | ## 重构视图 10 | 我们来看看我们当前的一组视图,并将它们重构为视图集。 11 | 12 | 首先,让我们将```UserList```和```UserDetail```视图重构为单个```UserViewSet```。我们可以删除这两个视图,并用一个类替换它们: 13 | 14 | ```python 15 | from rest_framework import viewsets 16 | 17 | class UserViewSet(viewsets.ReadOnlyModelViewSet): 18 | """ 19 | 这个视图集合会自动提供“list”和“detail”操作。 20 | """ 21 | queryset = User.objects.all() 22 | serializer_class = UserSerializer 23 | ``` 24 | 25 | 这里我们使用这个```ReadOnlyModelViewSet```类来自动提供默认的“只读”操作。我们仍然像在使用常规视图时那样设置```queryset```和```serializer_class```属性,但是我们不再需要向两个单独的类提供相同的信息。 26 | 接下来我们要更换```SnippetList```,```SnippetDetail```和```SnippetHighlight```视图类。我们可以删除三个视图,并再次用一个类替换它们。 27 | 28 | ```python 29 | from rest_framework.decorators import detail_route 30 | from rest_framework.response import Response 31 | 32 | class SnippetViewSet(viewsets.ModelViewSet): 33 | """ 34 | 该视图自动提供 `list`, `create`, `retrieve`, 35 | `update` and `destroy` actions. 36 | 37 | Additionally we also provide an extra `highlight` action. 38 | """ 39 | queryset = Snippet.objects.all() 40 | serializer_class = SnippetSerializer 41 | permission_classes = (permissions.IsAuthenticatedOrReadOnly, 42 | IsOwnerOrReadOnly,) 43 | 44 | @detail_route(renderer_classes=[renderers.StaticHTMLRenderer]) 45 | def highlight(self, request, *args, **kwargs): 46 | snippet = self.get_object() 47 | return Response(snippet.highlighted) 48 | 49 | def perform_create(self, serializer): 50 | serializer.save(owner=self.request.user) 51 | ``` 52 | 53 | 这次我们使用这个```ModelViewSet```类来获得完整的默认读写操作。 54 | 55 | 请注意,我们也使用```@detail_route```装饰器来创建一个名为的自定义操作```highlight```。这个装饰器可以用来添加任何不符合标准```create/ update/ delete```风格的自定义端点。 56 | 57 | 使用```@detail_route``` 装饰器的自定义操作将在默认情况下响应GET请求。如果我们想要一个响应POST请求的操作,我们可以使用```methods```参数。 58 | 59 | 自定义操作的URL默认取决于方法名称本身。如果你想改变构造url的方式,你可以包含url_path作为装饰器关键字参数。 60 | 61 | 62 | ## 显式地将ViewSets绑定到URL 63 | 64 | 65 | 当我们定义URLConf时,处理程序方法只绑定到操作。为了查看引擎盖下发生了什么,让我们首先从我们的视图中显式地创建一组视图。 66 | 67 | 在```snippets/urls.py```文件中,我们将我们的```ViewSet```类绑定到一组具体的视图中。 68 | 69 | ```python 70 | from snippets.views import SnippetViewSet, UserViewSet, api_root 71 | from rest_framework import renderers 72 | 73 | snippet_list = SnippetViewSet.as_view({ 74 | 'get': 'list', 75 | 'post': 'create' 76 | }) 77 | snippet_detail = SnippetViewSet.as_view({ 78 | 'get': 'retrieve', 79 | 'put': 'update', 80 | 'patch': 'partial_update', 81 | 'delete': 'destroy' 82 | }) 83 | snippet_highlight = SnippetViewSet.as_view({ 84 | 'get': 'highlight' 85 | }, renderer_classes=[renderers.StaticHTMLRenderer]) 86 | user_list = UserViewSet.as_view({ 87 | 'get': 'list' 88 | }) 89 | user_detail = UserViewSet.as_view({ 90 | 'get': 'retrieve' 91 | }) 92 | ``` 93 | 94 | 95 | 请注意,我们是如何从每个```ViewSet```类创建多个视图,方法是将http方法绑定到每个视图所需的操作。 96 | 97 | 现在我们已经将资源绑定到具体的视图中,我们可以像往常一样从```URL conf```注册视图。 98 | 99 | ```python 100 | urlpatterns = format_suffix_patterns([ 101 | url(r'^$', api_root), 102 | url(r'^snippets/$', snippet_list, name='snippet-list'), 103 | url(r'^snippets/(?P[0-9]+)/$', snippet_detail, name='snippet-detail'), 104 | url(r'^snippets/(?P[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'), 105 | url(r'^users/$', user_list, name='user-list'), 106 | url(r'^users/(?P[0-9]+)/$', user_detail, name='user-detail') 107 | ]) 108 | ``` 109 | 110 | ## 使用路由器 111 | 因为我们使用的是```ViewSet```类而不是```View```类,所以我们实际上不需要自己设计URL。将资源连接到视图和URL可以使用```Router```类自动处理。我们所需要做的就是用路由器注册适当的视图集,然后让其完成。 112 | 113 | 这是我们重新连接的```snippets/urls.py```文件。 114 | 115 | ```python 116 | from django.conf.urls import url, include 117 | from rest_framework.routers import DefaultRouter 118 | from snippets import views 119 | 120 | # Create a router and register our viewsets with it. 121 | router = DefaultRouter() 122 | router.register(r'snippets', views.SnippetViewSet) 123 | router.register(r'users', views.UserViewSet) 124 | 125 | # The API URLs are now determined automatically by the router. 126 | urlpatterns = [ 127 | url(r'^', include(router.urls)) 128 | ] 129 | 130 | ``` 131 | 向路由器注册视图类似于提供urlpattern。我们包含两个参数 - 视图的URL前缀和视图本身。 132 | 133 | ```DefaultRouter```我们使用的类也为我们自动创建了API根视图,因此我们现在可以```api_root```从```views```模块中删除该方法。 134 | 135 | 136 | ## 视图与视图集之间的权衡 137 | 使用viewsets是一个非常有用的抽象。它有助于确保URL约定在您的API中是一致的,最小化您需要编写的代码量,并允许您集中精力于API提供的交互和表示,而不是URL conf的细节。 138 | 139 | 这并不意味着它总是正确的方法。在使用基于类的视图而不是基于功能的视图时,有一组类似的折衷方案。使用viewset比单独构建视图更不明确。 140 | 141 | 在本教程的第7部分中,我们将介绍如何添加API模式,以及如何使用客户端库或命令行工具与我们的API进行交互。 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /tutorial/coreapi.md: -------------------------------------------------------------------------------- 1 | # coreapi 2 | 3 | 模式是一个机器可读文档,描述可用的API端点,它们的URL以及它们支持的操作。 4 | 5 | 模式可以成为自动生成文档的工具,也可用于驱动可与API交互的动态客户端库。 6 | 7 | 8 | ## 核心API 9 | 为了提供模式支持,REST框架使用Core API。 10 | 11 | 核心API是用于描述API的文档规范。它用于提供可用端点的内部表示形式以及API公开的可能交互。它可以用于服务器端或客户端。 12 | 13 | 在服务器端使用时,```Core API```允许API支持以各种架构或超媒体格式进行渲染。 14 | 15 | 在使用客户端时,```Core API```允许动态驱动的客户端库,它们可以与任何公开受支持的模式或超媒体格式的API进行交互。 16 | 17 | 18 | ## 添加一个模式 19 | REST框架支持显式定义的模式视图或自动生成的模式。由于我们使用视图集和路由器,因此我们可以简单地使用自动模式生成。 20 | 21 | 您需要安装```coreapi``` python包。 22 | 23 | ```python 24 | $ pip install coreapi 25 | ``` 26 | 27 | 我们现在可以通过在我们的URL配置中包含一个自动生成的模式视图来为我们的API包含模式。 28 | 29 | ```python 30 | from rest_framework.schemas import get_schema_view 31 | 32 | schema_view = get_schema_view(title='Pastebin API') 33 | 34 | urlpatterns = [ 35 | url(r'^schema/$', schema_view), 36 | ... 37 | ] 38 | ``` 39 | 40 | 如果您在浏览器中访问API根,您现在应该可以看到corejson, 表示可以工作. 41 | 42 | ![](./corejson-format.png) 43 | 44 | 45 | 我们还可以通过在```Accept```标题中指定所需的内容类型来从命令行请求架构。 46 | 47 | ```python 48 | $ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json 49 | HTTP/1.0 200 OK 50 | Allow: GET, HEAD, OPTIONS 51 | Content-Type: application/coreapi+json 52 | 53 | { 54 | "_meta": { 55 | "title": "Pastebin API" 56 | }, 57 | "_type": "document", 58 | ... 59 | ``` 60 | 61 | 默认的输出风格是使用Core JSON编码。 62 | 63 | 其他架构格式,例如Open API(以前称为Swagger)也受支持。 64 | 65 | 66 | ## 使用命令行客户端 67 | 现在我们的API公开了一个模式端点,我们可以使用一个动态客户端库来与API进行交互。为了演示这一点,我们使用Core API命令行客户端。 68 | 69 | 命令行客户端可用```coreapi-cli```包: 70 | 71 | ```python 72 | $ pip install coreapi-cli 73 | ``` 74 | 75 | 现在检查它在命令行上是否可用... 76 | 77 | ```python 78 | $ coreapi 79 | Usage: coreapi [OPTIONS] COMMAND [ARGS]... 80 | 81 | Command line client for interacting with CoreAPI services. 82 | 83 | Visit http://www.coreapi.org for more information. 84 | 85 | Options: 86 | --version Display the package version number. 87 | --help Show this message and exit. 88 | 89 | Commands: 90 | ... 91 | ``` 92 | 93 | 首先,我们将使用命令行客户端加载API模式。 94 | 95 | ```python 96 | $ coreapi get http://127.0.0.1:8000/schema/ 97 | 98 | snippets: { 99 | highlight(id) 100 | list() 101 | read(id) 102 | } 103 | users: { 104 | list() 105 | read(id) 106 | } 107 | ``` 108 | 109 | 我们还没有进行身份验证,所以现在我们只能看到只读端点,与我们如何设置API的权限一致。 110 | 111 | 让我们尝试使用命令行客户端列出现有snippets: 112 | 113 | ```python 114 | $ coreapi action snippets list 115 | [ 116 | { 117 | "url": "http://127.0.0.1:8000/snippets/1/", 118 | "id": 1, 119 | "highlight": "http://127.0.0.1:8000/snippets/1/highlight/", 120 | "owner": "lucy", 121 | "title": "Example", 122 | "code": "print('hello, world!')", 123 | "linenos": true, 124 | "language": "python", 125 | "style": "friendly" 126 | }, 127 | ... 128 | 129 | ``` 130 | 131 | 一些API端点需要命名参数。例如,要获取特定代码段的高亮HTML,我们需要提供一个id。 132 | 133 | ```python 134 | $ coreapi action snippets highlight --param id=1 135 | 136 | 137 | 138 | 139 | Example 140 | ... 141 | ``` 142 | 143 | ## 验证我们的客户端 144 | 145 | 如果我们希望能够创建,编辑和删除snippets,我们需要以有效用户身份进行身份验证。在这种情况下,我们只使用基本身份验证。 146 | 147 | 确保使用您的实际用户名和密码替换在下面。 148 | 149 | ```python 150 | $ coreapi credentials add 127.0.0.1 : --auth basic 151 | Added credentials 152 | 127.0.0.1 "Basic <...>" 153 | ``` 154 | 155 | 现在,如果我们再次获取模式,我们应该能够看到完整的可用交互。 156 | 157 | ```python 158 | $ coreapi reload 159 | Pastebin API "http://127.0.0.1:8000/schema/"> 160 | snippets: { 161 | create(code, [title], [linenos], [language], [style]) 162 | delete(id) 163 | highlight(id) 164 | list() 165 | partial_update(id, [title], [code], [linenos], [language], [style]) 166 | read(id) 167 | update(id, code, [title], [linenos], [language], [style]) 168 | } 169 | users: { 170 | list() 171 | read(id) 172 | } 173 | 174 | ``` 175 | 176 | 我们现在可以与这些端点进行交互。例如,要创建一个新的片段: 177 | 178 | ```python 179 | $ coreapi action snippets create --param title="Example" --param code="print('hello, world')" 180 | { 181 | "url": "http://127.0.0.1:8000/snippets/7/", 182 | "id": 7, 183 | "highlight": "http://127.0.0.1:8000/snippets/7/highlight/", 184 | "owner": "lucy", 185 | "title": "Example", 186 | "code": "print('hello, world')", 187 | "linenos": false, 188 | "language": "python", 189 | "style": "friendly" 190 | } 191 | ``` 192 | 193 | 并删除一个片段: 194 | 195 | ```python 196 | $ coreapi action snippets delete --param id=7 197 | ``` 198 | 199 | 除了命令行客户端之外,开发人员还可以使用客户端库与您的API进行交互。Python客户端库是第一个可用的客户端库,计划很快发布Javascript客户端库。 200 | 201 | 有关自定义模式生成和使用Core API客户端库的更多详细信息,您需要参阅完整的文档。 202 | 203 | ## 回顾我们的工作 204 | 使用极少量的代码,我们现在已经有了一个完整的```pastebin Web API```,它完全可以浏览网页,包含一个模式驱动的客户端库,并且包含身份验证,每对象权限和多个呈现器格式。 205 | 206 | 我们已经完成了设计过程的每一步,并且看到如果我们需要定制任何可以逐步使用常规Django视图的方法。 207 | 208 | 您可以在GitHub上查看最终的教程代码,或者在沙箱中试用一个实例。 209 | 210 | ## 向上和向上 211 | 我们已经完成了我们的教程。如果您想更多地参与REST框架项目,可以从以下几个地方开始: 212 | 213 | 通过审查和提交问题,并提出pull请求,为GitHub贡献力量。 214 | 加入REST框架讨论组,并帮助构建社区。 215 | 216 | 在Twitter上关注作者并点个赞。 217 | 218 | 现在去做很棒的事情。 -------------------------------------------------------------------------------- /tutorial/classview.md: -------------------------------------------------------------------------------- 1 | # 类视图 2 | 3 | 我们也可以使用基于类的视图来编写API视图,而不是基于函数的视图。正如我们将看到的,这是一个强大的模式,可以让我们复用常用功能,并帮助我们保持代码干爽。 4 | 5 | ## 使用基于类的视图重写我们的API 6 | 我们将首先将根视图重写为基于类的视图。这些都涉及到重构```views.py```。 7 | 8 | ```python 9 | from snippets.models import Snippet 10 | from snippets.serializers import SnippetSerializer 11 | from django.http import Http404 12 | from rest_framework.views import APIView 13 | from rest_framework.response import Response 14 | from rest_framework import status 15 | 16 | 17 | class SnippetList(APIView): 18 | """ 19 | List all snippets, or create a new snippet. 20 | """ 21 | def get(self, request, format=None): 22 | snippets = Snippet.objects.all() 23 | serializer = SnippetSerializer(snippets, many=True) 24 | return Response(serializer.data) 25 | 26 | def post(self, request, format=None): 27 | serializer = SnippetSerializer(data=request.data) 28 | if serializer.is_valid(): 29 | serializer.save() 30 | return Response(serializer.data, status=status.HTTP_201_CREATED) 31 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 32 | ``` 33 | 34 | 到现在为止还挺好。它看起来与之前的用法非常相似,但我们在不同的HTTP方法做了更好的分离。我们还需要更新实例视图```views.py```。 35 | 36 | ```python 37 | class SnippetDetail(APIView): 38 | """ 39 | Retrieve, update or delete a snippet instance. 40 | """ 41 | def get_object(self, pk): 42 | try: 43 | return Snippet.objects.get(pk=pk) 44 | except Snippet.DoesNotExist: 45 | raise Http404 46 | 47 | def get(self, request, pk, format=None): 48 | snippet = self.get_object(pk) 49 | serializer = SnippetSerializer(snippet) 50 | return Response(serializer.data) 51 | 52 | def put(self, request, pk, format=None): 53 | snippet = self.get_object(pk) 54 | serializer = SnippetSerializer(snippet, data=request.data) 55 | if serializer.is_valid(): 56 | serializer.save() 57 | return Response(serializer.data) 58 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 59 | 60 | def delete(self, request, pk, format=None): 61 | snippet = self.get_object(pk) 62 | snippet.delete() 63 | return Response(status=status.HTTP_204_NO_CONTENT) 64 | ``` 65 | 66 | 这看起来不错。同样,它现在仍然非常类似基于函数的视图。 67 | 68 | 我们现在还需要稍微重构我们使用基于类的视图的```snippets/urls.py```。 69 | 70 | ```python 71 | from django.conf.urls import url 72 | from rest_framework.urlpatterns import format_suffix_patterns 73 | from snippets import views 74 | 75 | urlpatterns = [ 76 | url(r'^snippets/$', views.SnippetList.as_view()), 77 | url(r'^snippets/(?P[0-9]+)/$', views.SnippetDetail.as_view()), 78 | ] 79 | 80 | urlpatterns = format_suffix_patterns(urlpatterns) 81 | ``` 82 | 83 | ## 使用mixins 84 | 85 | 使用基于类的视图的最大好处之一是,它允许我们轻松地编写可重用的行为片段。 86 | 87 | 到目前为止,我们使用的create/retrieve/update/delete操作对于我们创建的任何模型支持的API视图将非常相似。这些常见行为的某些部分在REST框架的mixin类中实现。 88 | 89 | 90 | 我们来看看如何使用mixin类组合视图。这是我们的```views.py```模块了。 91 | 92 | ```python 93 | from snippets.models import Snippet 94 | from snippets.serializers import SnippetSerializer 95 | from rest_framework import mixins 96 | from rest_framework import generics 97 | 98 | class SnippetList(mixins.ListModelMixin, 99 | mixins.CreateModelMixin, 100 | generics.GenericAPIView): 101 | queryset = Snippet.objects.all() 102 | serializer_class = SnippetSerializer 103 | 104 | def get(self, request, *args, **kwargs): 105 | return self.list(request, *args, **kwargs) 106 | 107 | def post(self, request, *args, **kwargs): 108 | return self.create(request, *args, **kwargs) 109 | ``` 110 | 111 | 我们将花一点时间仔细检查这里发生了什么。我们正在构建我们的视图```GenericAPIView```,并添加```ListModelMixin```和```CreateModelMixin```。 112 | 113 | 基类提供核心功能,mixin类提供```.list()```和```.create()```操作。然后,我们明确地将方法```get```和```post```方法绑定到适当的操作。迄今为止足够简单的东西。 114 | 115 | ```python 116 | class SnippetDetail(mixins.RetrieveModelMixin, 117 | mixins.UpdateModelMixin, 118 | mixins.DestroyModelMixin, 119 | generics.GenericAPIView): 120 | queryset = Snippet.objects.all() 121 | serializer_class = SnippetSerializer 122 | 123 | def get(self, request, *args, **kwargs): 124 | return self.retrieve(request, *args, **kwargs) 125 | 126 | def put(self, request, *args, **kwargs): 127 | return self.update(request, *args, **kwargs) 128 | 129 | def delete(self, request, *args, **kwargs): 130 | return self.destroy(request, *args, **kwargs) 131 | ``` 132 | 133 | 非常相似。同样,我们正在使用的```GenericAPIView```类来提供核心功能,并混入增加提供```.retrieve()```,```.update()```和```.destroy()```。 134 | 135 | 136 | ## 使用通用的基于类的视图 137 | 使用mixin类,我们重写了视图,使用比以前稍少的代码,但我们可以更进一步。REST框架提供了一组已经混合的通用视图,我们可以使用这些视图更多地修剪我们的```views.py```模块。 138 | 139 | ``` 140 | from snippets.models import Snippet 141 | from snippets.serializers import SnippetSerializer 142 | from rest_framework import generics 143 | 144 | 145 | class SnippetList(generics.ListCreateAPIView): 146 | queryset = Snippet.objects.all() 147 | serializer_class = SnippetSerializer 148 | 149 | 150 | class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): 151 | queryset = Snippet.objects.all() 152 | serializer_class = SnippetSerializer 153 | ``` 154 | 155 | Wow,这很简洁。我们免费获得了大量的代码,而且我们的代码看起来很好,干净,惯用的Django。 156 | 157 | 接下来,我们将转到本教程的第4部分,其中我们将介绍如何处理API的身份验证和权限。 158 | -------------------------------------------------------------------------------- /tutorial/hyperlink.md: -------------------------------------------------------------------------------- 1 | # 关系和超链接 2 | 3 | 目前,我们的API中的关系用主键表示。在本教程的这一部分中,我们将通过使用超链接来提高API的内聚性和可发现性 4 | 5 | # 为我们的API的根创建一个endpoint 6 | 7 | 现在我们有```'snippets'```和```'users'```的端点,但是我们没有一个入口指向我们的API。要创建一个,我们将使用一个常规的基于函数的视图和我们之前介绍的装饰器```@api_view```。在你的```snippets/views.py```添加中: 8 | 9 | ```python 10 | from rest_framework.decorators import api_view 11 | from rest_framework.response import Response 12 | from rest_framework.reverse import reverse 13 | 14 | 15 | @api_view(['GET']) 16 | def api_root(request, format=None): 17 | return Response({ 18 | 'users': reverse('user-list', request=request, format=format), 19 | 'snippets': reverse('snippet-list', request=request, format=format) 20 | }) 21 | ``` 22 | 23 | 这里应该注意两件事。首先,我们使用REST框架的```reverse```函数来返回完全限定的URL; 其次,URL模式通过方便的名称来标识,我们稍后会在我们的网站中声明```snippets/urls.py```。 24 | 25 | # 为高亮的snippets创建一个endpoint 26 | 27 | 我们的pastebin API仍然存在的另一个显而易见的问题是突出端点的代码。 28 | 29 | 与其他所有API端点不同,我们不想使用JSON,而只是呈现HTML表示。REST框架提供了两种HTML呈现器,一种用于处理使用模板展现的HTML,另一种用于处理预展现的HTML。第二个渲染器是我们希望用于此endpoint的渲染器。 30 | 31 | 在创建代码高亮显示视图时,我们需要考虑的另一件事是没有现有的具体的通用视图可以使用。我们不是返回一个对象实例,而是返回一个对象实例的属性。 32 | 33 | 我们不使用具体的通用视图,而是使用基类来表示实例,并创建我们自己的```.get()```方法。在你的```snippets/views.py```添加中: 34 | 35 | ```python 36 | from rest_framework import renderers 37 | from rest_framework.response import Response 38 | 39 | class SnippetHighlight(generics.GenericAPIView): 40 | queryset = Snippet.objects.all() 41 | renderer_classes = (renderers.StaticHTMLRenderer,) 42 | 43 | def get(self, request, *args, **kwargs): 44 | snippet = self.get_object() 45 | return Response(snippet.highlighted) 46 | ``` 47 | 48 | 49 | 像之前一样,我们需要将我们创建的新视图添加到我们的```URLconf```中。我们将在我们的新API根中添加一个网址格式```snippets/urls.py```: 50 | 51 | ```python 52 | url(r'^$', views.api_root), 53 | ``` 54 | 然后为代码高亮添加一个网址格式: 55 | 56 | ```python 57 | url(r'^snippets/(?P[0-9]+)/highlight/$', views.SnippetHighlight.as_view()), 58 | ``` 59 | 60 | 61 | 62 | ## 超链接我们的API 63 | 处理实体之间的关系是```Web API```设计中更具挑战性的方面之一。我们可以选择代表一种关系的方式有很多种: 64 | 65 | 1. 使用主键。 66 | 2. 在实体之间使用超链接。 67 | 3. 在相关实体上使用唯一标识段落字段。 68 | 4. 使用相关实体的默认字符串表示形式。 69 | 5. 将相关实体嵌套在父代表中。 70 | 6. 一些其他自定义表示。 71 | 72 | 73 | REST框架支持这些所有的样式,并且可以跨前向或反向关系应用它们,或者将其应用到自定义管理器(如通用外键)中。 74 | 在这种情况下,我们希望在实体之间使用超链接样式。为了做到这一点,我们将修改序列化器来扩展```HyperlinkedModelSerializer```而不是现有的序列化器```ModelSerializer```。 75 | 76 | ```HyperlinkedModelSerializer```和```ModelSerializer```有以下区别: 77 | 78 | * 它id默认不包含该字段。 79 | * 它包括一个url字段,使用HyperlinkedIdentityField。 80 | * 关系使用HyperlinkedRelatedField,而不是PrimaryKeyRelatedField。 81 | 82 | 83 | 我们可以轻松地重写我们现有的序列化程序来使用超链接。在你的```snippets/serializers.py```中添加: 84 | 85 | ```python 86 | class SnippetSerializer(serializers.HyperlinkedModelSerializer): 87 | owner = serializers.ReadOnlyField(source='owner.username') 88 | highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html') 89 | 90 | class Meta: 91 | model = Snippet 92 | fields = ('url', 'id', 'highlight', 'owner', 93 | 'title', 'code', 'linenos', 'language', 'style') 94 | 95 | 96 | class UserSerializer(serializers.HyperlinkedModelSerializer): 97 | snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True) 98 | 99 | class Meta: 100 | model = User 101 | fields = ('url', 'id', 'username', 'snippets') 102 | 103 | ``` 104 | 105 | 106 | 请注意,我们还添加了一个新```'highlight'```字段。该字段与```url```字段的类型相同,只是它指向的是```'snippet-highlight'```url模式,而不是```'snippet-detail'```url模式。 107 | 108 | 由于我们已经包含格式后缀的URL ```'.json'```,我们还需要在```highlight```字段上指明它返回的任何格式后缀超链接应使用```'.html'```后缀。 109 | 110 | 111 | 112 | ## 确保我们的URL模式被命名 113 | 如果我们要有超链接的API,我们需要确保我们命名我们的URL模式。我们来看看我们需要命名的URL模式。 114 | 115 | * 我们的API的根源是'user-list'和'snippet-list'。 116 | * 我们的片段序列化程序包含一个指向的字段'snippet-highlight'。 117 | * 我们的用户序列化程序包含一个指向的字段'snippet-detail'。 118 | * 我们的片段和用户序列化程序包含'url'默认情况下会引用的字段,'{model_name}-detail'在这种情况下将是'snippet-detail'和'user-detail'。 119 | * 将所有这些名称添加到我们的URLconf后,我们的最终snippets/urls.py文件应该如下所示: 120 | 121 | 122 | ```python 123 | from django.conf.urls import url, include 124 | from rest_framework.urlpatterns import format_suffix_patterns 125 | from snippets import views 126 | 127 | # API endpoints 128 | urlpatterns = format_suffix_patterns([ 129 | url(r'^$', views.api_root), 130 | url(r'^snippets/$', 131 | views.SnippetList.as_view(), 132 | name='snippet-list'), 133 | url(r'^snippets/(?P[0-9]+)/$', 134 | views.SnippetDetail.as_view(), 135 | name='snippet-detail'), 136 | url(r'^snippets/(?P[0-9]+)/highlight/$', 137 | views.SnippetHighlight.as_view(), 138 | name='snippet-highlight'), 139 | url(r'^users/$', 140 | views.UserList.as_view(), 141 | name='user-list'), 142 | url(r'^users/(?P[0-9]+)/$', 143 | views.UserDetail.as_view(), 144 | name='user-detail') 145 | ]) 146 | ``` 147 | 148 | 149 | ## 添加分页 150 | 用户和snippets的列表视图可能会返回很多实例,所以我们确实要确保对结果进行分页,并允许API客户端遍历每个单独的页面。 151 | 152 | 通过tutorial/settings.py稍微修改我们的文件,我们可以更改默认列表样式以使用分页。添加以下设置: 153 | 154 | ```python 155 | REST_FRAMEWORK = { 156 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 157 | 'PAGE_SIZE': 10 158 | } 159 | ``` 160 | 161 | 请注意,REST框架中的设置都被命名为一个单独的字典设置REST_FRAMEWORK,这有助于使它们与其他项目设置保持良好的分离。 162 | 163 | 如果我们也需要,我们也可以自定义分页样式,但在这种情况下,我们会坚持默认。 164 | 165 | ## 浏览API 166 | 如果我们打开一个浏览器并导航到可浏览的API,您会发现现在您可以通过简单的链接访问API。 167 | 168 | 您还可以看到片段实例上的“highlight”链接,这会将您带到突出显示的代码HTML表示。 169 | 170 | 在本教程的第```6```部分中,我们将介绍如何使用```ViewSets```和`routers`来减少构建API所需的代码量。 171 | -------------------------------------------------------------------------------- /tutorial/req-resp.md: -------------------------------------------------------------------------------- 1 | # Requests & Responses 2 | 3 | 从这一章,我们将真正开始涵盖REST框架的核心。我们先来介绍一些基本的构建块。 4 | 5 | ## 请求对象 6 | 7 | REST框架引入了一个```Request```扩展常规的对象```HttpRequest```,并提供更灵活的请求解析。```Request```对象的核心功能是```request.data```属性,与```request.POST```Web API 类似,但更加有用。 8 | 9 | ```python 10 | request.POST # 只处理form表单数据,只适用POST方法. 11 | request.data # 处理任意方法. 'POST', 'PUT' 和 'PATCH' 方法. 12 | ``` 13 | 14 | 15 | ## 响应对象 16 | 17 | REST框架还引入了一个```Response```对象,该对象是一种```TemplateResponse```类型,它使用未呈现的内容并使用内容协商来确定返回给客户端的正确内容类型。 18 | 19 | ``` 20 | return Response(data) # 根据用户请求返回的内容. 21 | ``` 22 | 23 | ## 状态码 24 | 25 | 在视图中使用数字HTTP状态代码并不总是明显的阅读,而且如果错误代码出现错误,会很不容易注意到。REST框架为每个状态代码提供更明确的标识符,例如```HTTP_400_BAD_REQUEST```在```status```模块中。这是一个好想法,而不是使用数字标识符。 26 | 27 | 28 | ## Wrapping API views 装饰 API 视图 29 | 30 | REST框架提供了两个可用于编写API视图的装饰器。 31 | 32 | 1. @api_view用于处理基于函数的视图的装饰器。 33 | 2. APIView 基于类的视图工作。 34 | 35 | 这些装饰器提供了一些功能,例如确保```Request```在视图中接收实例,并向```Response```对象添加上下文,以便可以执行内容协商。 36 | 37 | 这些装饰器还提供了一些功能,例如```405 Method Not Allowed```在适当的时候返回响应,以及处理```ParseError```在```request.data```访问输入错误的格式时发生的任何异常。 38 | 39 | 40 | ## Pulling it all together 整合到一起 41 | 42 | 好吧,让我们开始使用这些新组件来写几个视图。 43 | 44 | 我们不再需要我们的```JSONResponse```教程里的```views.py```,所以删除它。完成后,我们可以开始重构我们的views。 45 | 46 | ```python 47 | from rest_framework import status 48 | from rest_framework.decorators import api_view 49 | from rest_framework.response import Response 50 | from snippets.models import Snippet 51 | from snippets.serializers import SnippetSerializer 52 | 53 | 54 | @api_view(['GET', 'POST']) 55 | def snippet_list(request): 56 | """ 57 | 列出所有snippets, 或者创建一个新的snippet. 58 | """ 59 | if request.method == 'GET': 60 | snippets = Snippet.objects.all() 61 | serializer = SnippetSerializer(snippets, many=True) 62 | return Response(serializer.data) 63 | 64 | elif request.method == 'POST': 65 | serializer = SnippetSerializer(data=request.data) 66 | if serializer.is_valid(): 67 | serializer.save() 68 | return Response(serializer.data, status=status.HTTP_201_CREATED) 69 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 70 | ``` 71 | 72 | 73 | 我们的实例视图比前一个示例有所改进。它更简洁一些,如果我们使用Forms API,代码现在感觉非常相似。我们还使用了指定的状态代码,这使得响应的含义更加明显。 74 | 75 | 以下是views.py模块中单个snippet的视图。 76 | 77 | ```python 78 | @api_view(['GET', 'PUT', 'DELETE']) 79 | def snippet_detail(request, pk): 80 | """ 81 | Retrieve, update or delete a code snippet. 82 | """ 83 | try: 84 | snippet = Snippet.objects.get(pk=pk) 85 | except Snippet.DoesNotExist: 86 | return Response(status=status.HTTP_404_NOT_FOUND) 87 | 88 | if request.method == 'GET': 89 | serializer = SnippetSerializer(snippet) 90 | return Response(serializer.data) 91 | 92 | elif request.method == 'PUT': 93 | serializer = SnippetSerializer(snippet, data=request.data) 94 | if serializer.is_valid(): 95 | serializer.save() 96 | return Response(serializer.data) 97 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 98 | 99 | elif request.method == 'DELETE': 100 | snippet.delete() 101 | return Response(status=status.HTTP_204_NO_CONTENT) 102 | ``` 103 | 104 | 105 | 这应该都非常熟悉 - 这与使用常规Django视图没有多大区别。 106 | 107 | 请注意,我们不再明确地将我们的请求或响应绑定到给定的内容类型.```request.data```可以处理传入的json请求,但它也可以处理其他格式。同样,我们使用数据返回响应对象,但允许```REST```框架将响应展示给我们正确的内容类型。 108 | 109 | 110 | ## 为我们的URL添加可选的格式后缀 111 | 112 | 为了利用我们的响应不再被硬连接到单一内容类型的事实,让我们将格式后缀的支持添加到我们的API端点。使用格式后缀给了我们明确引用给定格式的URL,并且意味着我们的API将能够处理诸如http://example.com/api/items/4.json之类的 URL 。 113 | 114 | 首先,在两个视图中添加一个```format```格式关键字参数,就像这样。 115 | 116 | ``` 117 | def snippet_list(request, format=None): 118 | ``` 119 | 120 | and 121 | 122 | ``` 123 | def snippet_detail(request, pk, format=None): 124 | ``` 125 | 126 | 127 | 现在稍微更新snippets/urls.py文件,以附加一组format_suffix_patterns到现有的URL。 128 | 129 | ```python 130 | from django.conf.urls import url 131 | from rest_framework.urlpatterns import format_suffix_patterns 132 | from snippets import views 133 | 134 | urlpatterns = [ 135 | url(r'^snippets/$', views.snippet_list), 136 | url(r'^snippets/(?P[0-9]+)$', views.snippet_detail), 137 | ] 138 | 139 | urlpatterns = format_suffix_patterns(urlpatterns) 140 | ``` 141 | 142 | 我们不一定需要添加这些额外的url模式,但是它提供了一种简单、干净的方式来引用特定的格式。 143 | 144 | 145 | # 它看起来如何? 146 | 继续从命令行测试API,就像我们在教程1中所做的那样。这些工作都非常类似,尽管如果我们发送无效请求,我们已经有了一些更好的错误处理。 147 | 148 | 我们可以像以前一样获取所有snippets的列表。 149 | 150 | ```python 151 | http http://127.0.0.1:8000/snippets/ 152 | 153 | HTTP/1.1 200 OK 154 | ... 155 | [ 156 | { 157 | "id": 1, 158 | "title": "", 159 | "code": "foo = \"bar\"\n", 160 | "linenos": false, 161 | "language": "python", 162 | "style": "friendly" 163 | }, 164 | { 165 | "id": 2, 166 | "title": "", 167 | "code": "print \"hello, world\"\n", 168 | "linenos": false, 169 | "language": "python", 170 | "style": "friendly" 171 | } 172 | ] 173 | ``` 174 | 175 | 我们可以通过使用```Accept header```来控制返回的响应格式: 176 | 177 | ``` 178 | http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON 179 | http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML 180 | ``` 181 | 182 | 或者通过附加格式后缀访问: 183 | 184 | ``` 185 | http http://127.0.0.1:8000/snippets.json # JSON suffix 186 | http http://127.0.0.1:8000/snippets.api # Browsable API suffix 187 | ``` 188 | 189 | 同样,我们可以使用```Content-Type```标题来控制我们发送的请求的格式。 190 | 191 | ``` 192 | # POST using form data 193 | http --form POST http://127.0.0.1:8000/snippets/ code="print 123" 194 | 195 | { 196 | "id": 3, 197 | "title": "", 198 | "code": "print 123", 199 | "linenos": false, 200 | "language": "python", 201 | "style": "friendly" 202 | } 203 | 204 | # POST using JSON 205 | http --json POST http://127.0.0.1:8000/snippets/ code="print 456" 206 | 207 | { 208 | "id": 4, 209 | "title": "", 210 | "code": "print 456", 211 | "linenos": false, 212 | "language": "python", 213 | "style": "friendly" 214 | } 215 | ``` 216 | 217 | 218 | 如果您添加```--debug```向上述```http```请求,您将能够在请求标头中看到请求的类型。 219 | 220 | 现在,通过访问http://127.0.0.1:8000/snippets/,在Web浏览器中打开API 。 221 | 222 | ## 浏览功能 223 | 224 | 因为API根据客户端请求选择响应的内容类型,所以默认情况下,当Web浏览器请求该资源时,它将返回HTML格式的资源。这允许API返回完全可以浏览网页的HTML表示。 225 | 226 | 拥有可浏览网页的API是一个巨大的可用性胜利,并且使开发和使用您的API变得更加容易。它也大大降低了想要检查和使用API​​的其他开发人员的进入门槛。 227 | 228 | 有关可浏览的API功能以及如何对其进行定制的更多信息,请参阅可浏览的API主题。 229 | 230 | ## 下一步是什么? 231 | 232 | 在教程第3部分中,我们将开始使用基于类的视图,并了解通用视图如何减少编写的代码量。 -------------------------------------------------------------------------------- /api/views.md: -------------------------------------------------------------------------------- 1 | ## 视图```Views``` 2 | 3 | ### 基于类的视图 4 | 5 | > Django的基于类的视图和老式视图相比是更受欢迎的。 6 | ——Reinout van Rees 7 | 8 | REST framework提供了一个```APIView```类,是Django的```View```类的子类。 9 | 10 | 相比于常规的```View```类,REST framework的```APIView```有以下几点不同: 11 | 12 | - 传递给处理方法的请求是REST framework的```Request```实例,而不是Django的```HttpRequest```实例。 13 | - 处理方法可能返回REST framework的```Response```来代替Django的```HttpResponse```。该视图将会管理内容协商并给响应设置正确的渲染器。 14 | - 任何异常将会被```APIException```捕捉,并且返回一个合适的响应。(包含一个合适的错误提示) 15 | - 任何进入的请求,在派分到处理函数之前,都将是已经认证的,并且有适当的权限,或者还会有节流的检查。 16 | 17 | 使用```APIView```类基本上跟使用常规的```View```是基本一样的。像往常一样,进来的请求会被分派到适当的处理函数中,比如```.get()```或者是```.post()```。此外,还可以设置大量的属性,用来控制API策略的方方面面。 18 | 19 | ``` 20 | from rest_framework.views import APIView 21 | from rest_framework.response import Response 22 | from rest_framework import authentication, permissions 23 | from django.contrib.auth.models import User 24 | 25 | class ListUsers(APIView): 26 | """ 27 | 列出系统中所有的用户. 28 | 29 | * 需要进行Token验证. 30 | * 仅仅只有管理才可以访问这个视图. 31 | """ 32 | authentication_classes = (authentication.TokenAuthentication,) 33 | permission_classes = (permissions.IsAdminUser,) 34 | 35 | def get(self, request, format=None): 36 | """ 37 | Return a list of all users. 38 | """ 39 | usernames = [user.username for user in User.objects.all()] 40 | return Response(usernames) 41 | ``` 42 | 43 | --- 44 | 45 | **注意:** 刚开始可能会觉得Django REST Framework的```APIView```和```GenericAPIView```和各种各样的```Mixins```和```Viewsets```有非常多的方法,属性和关系。这里有一个在线的地址,[Classy Django REST Framework](http://www.cdrf.co/)提供了一个可以浏览的资源,包含完整的方法和属性,对于Django REST Framework的基于类的视图。 46 | 47 | --- 48 | 49 | #### API策略 属性 50 | 51 | 以下属性用于控制API视图的方方面面, 这些属性都是可插拔的。 52 | 53 | ##### ```.renderer_classes``` 54 | 55 | ##### ```.parser_classes``` 56 | 57 | ##### ```.authemtication_classes``` 58 | 59 | ##### ```.throttle_classes``` 60 | 61 | ##### ```.permission_classes``` 62 | 63 | ##### ```.content_negotiation_class``` 64 | 65 | 66 | #### API策略 实例方法 67 | 68 | REST framework 会使用下面的方法 去实例化 各种可插拔的 API 策略。 通常情况你不需要重写这些方法。 69 | 70 | ##### ```.get_renderers(self)``` 71 | 72 | ##### ```.get_parsers(self)``` 73 | 74 | ##### ```.get_authenticators(self)``` 75 | 76 | ##### ```.get_throttles(self)``` 77 | 78 | ##### ```.get_permissions(self)``` 79 | 80 | ##### ```.get_content_negotiator(self)``` 81 | 82 | ##### ```.get_exception_handler(self)``` 83 | 84 | #### API策略 实施方法 85 | 86 | 在REST framework 把请求分发到相应的处理函数之前,会调用下面的方法。 87 | 88 | ##### ```.check_permissions(self, request)``` 89 | 90 | ##### ```.check_throttles(self, request)``` 91 | 92 | ##### ```.perform_content_negotiation(self, request, force=False)``` 93 | 94 | #### Dispatch 分派方法 95 | 96 | 下面的这些方法会直接被视图的```.dispatch()```方法来调用。在调用处理函数(比如 ```.get()```, ```.post()```, ```.put()```, ```.patch()```)之前或者之后执行一些动作。 97 | 98 | ##### ```.initial(self, request, *args, **kwargs)``` 99 | 100 | 不管执行任何动作的处理函数之前,这个方法都会被首先调用。这个方法用来执行权限和限流功能,并且勇于处理内容协商。 101 | 102 | 你通常不需要修改这个方法。 103 | 104 | ##### ```.handle_exception(self, exc)``` 105 | 106 | 执行处理函数中抛出的任何异常都会被传递到这个方法中,该方法将会返回一个```Response```实例,或者重新```raise```一个异常。 107 | 108 | 默认处理异常的行为可以是任何```rest_framework.exceptions.APIException```的子类,和Django的```Http404```和```PermissionDenied```异常一样, 都会返回一个合理恰当的错误响应。 109 | 110 | 如果你需要在你的API中自定义错误响应,你应该在你继承的子类中重写该方法。 111 | 112 | ##### ```.initialize_request(self, request, *args, **kwargs)``` 113 | 114 | 初始化请求。 115 | 116 | 确保传递给处理方法的请求对象是```Request```的实例,而不是常规的Django的```HttpRequest```。 117 | 118 | 该方法实际上就是返回一个```Request```的实例。 119 | 120 | 你通常不需要修改这个方法。 121 | 122 | ##### ```.finalize_response(self, request, response, *args, **kwargs)``` 123 | 124 | 返回最终的响应对象。 125 | 126 | 该方法会在最后调用,确保处理函数返回的任何```Response```对象会被正确的渲染。 127 | 128 | 该方法会通过内容协商(```self.perform_content_negotiation(request, force=True)```),确定```response.accepted_renderer```和```response.accepted_media_type```。 129 | 130 | 你通常也不需要修改这个方法。 131 | 132 | --- 133 | 134 | ### 基于函数的视图 135 | 136 | > 基于类的视图是一个优越的解决方案,这是一个误解。 137 | ——Nick Coghlan 138 | 139 | REST framework对于基于函数的视图同样也能非常好的工作。REST framework提供了一组简单的装饰器,用来包装你的基于函数的视图;来保证你的视图接收到的请求是```Request```的实例(而不是Django的HttPRequest),并且返回的响应是```Response```的实例(而不是Django的HttpResponse),并且允许您配置如何处理请求。 140 | 141 | #### ```@api_view()``` 142 | 143 | **语法:** ```@api_view(http_method_names=['GET'])``` 144 | 145 | 核心功能就是```@api_view```装饰器,并且要把你视图应该接收处理的HTTP方法以列表的形式,写到装饰器的参数中。 146 | 147 | 比如,下面的例子是如何编写一个非常简单的视图,并且仅仅是手动返回一些数据: 148 | 149 | ```python 150 | from rest_framework.decorators import api_view 151 | 152 | @api_view() 153 | def hello_world(request): 154 | return Response({"message": "Hello, world!"}) 155 | ``` 156 | 157 | 这个视图将使用[```settings```](./settings.md)中指定的默认的渲染器,解析器和身份认证类。 158 | 159 | 默认情况只有```GET```方法会被允许。其他方法将会返回```"405 Method Not Allowed"```。如果要改变这种行为,需要指定视图允许哪些方法,比如下面这段代码: 160 | 161 | ```python 162 | @api_view(['GET', 'POST']) 163 | def hello_world(request): 164 | if request.method == 'POST': 165 | return Response({"message": "Got some data!", "data": request.data}) 166 | return Response({"message": "Hello, world!"}) 167 | ``` 168 | 169 | #### API策略 装饰器 170 | 171 | REST framework提供了其他的装饰器,用于给你的视图增加一些设置,用于覆盖默认的设置。 172 | 173 | 这必须放置在```@api_view```装饰器的下面。 174 | 175 | 比如,限制([限流](./throttling.md))的视图每天每个用户只可以访问一次,可以使用装饰器```@throttle_classes```,并把限制策略类以列表的形式传递给这个装饰器: 176 | 177 | ```python 178 | from rest_framework.decorators import api_view, throttle_classes 179 | from rest_framework.throttling import UserRateThrottle 180 | 181 | class OncePerDayUserThrottle(UserRateThrottle): 182 | rate = '1/day' 183 | 184 | @api_view(['GET']) 185 | @throttle_classes([OncePerDayUserThrottle]) 186 | def view(request): 187 | return Response({"message": "Hello for today! See you tomorrow!"}) 188 | ``` 189 | 190 | 这些装饰器对应```APIView```中设置的子类,他们功能是一样的。 191 | 192 | 可用的装饰器如下: 193 | - ```@renderer_classes(...)``` 194 | - ```@parser_classes(...)``` 195 | - ```@authentication_classes(...)``` 196 | - ```@throttle_classes(...)``` 197 | - ```@permission_classes(...)``` 198 | 199 | 每个装饰器,至少接收一个类型为list或者tuple的参数,并且元素是一个类。 200 | 201 | #### 视图schema 装饰器 202 | 203 | 如果要给基于函数的视图,覆盖默认的schema生成,你可以使用```@schema```装饰器,同样的,这个装饰器必须在```@api_view```装饰器的下面。 比如: 204 | 205 | ```python 206 | from rest_framework.decorators import api_view, schema 207 | from rest_framework.schemas import AutoSchema 208 | 209 | class CustomAutoSchema(AutoSchema): 210 | def get_link(self, path, method, base_url): 211 | # override view introspection here... 212 | 213 | @api_view(['GET']) 214 | @schema(CustomAutoSchema()) 215 | def view(request): 216 | return Response({"message": "Hello for today! See you tomorrow!"}) 217 | ``` 218 | 219 | 这个装饰器的参数是一个```AutoSchema```的实例。具体参考文档[schemas](./schemas.md)。当然你也可以传递一个```None```,比如: 220 | 221 | ```python 222 | @api_view(['GET']) 223 | @schema(None) 224 | def view(request): 225 | return Response({"message": "Will not appear in schema!"}) 226 | ``` 227 | 228 | --- 229 | 230 | 参考链接: 231 | - http://www.cdrf.co/ 232 | - -------------------------------------------------------------------------------- /tutorial/auth-perms.md: -------------------------------------------------------------------------------- 1 | # 认证和权限 2 | 3 | 目前我们的API没有任何限制对谁都可以编辑或删除代码。我们希望有一些更高级的行为来保证: 4 | 5 | * 代码始终与创建者关联。 6 | * 只有经过身份验证的用户才可以创建。 7 | * 只有创建者可能会更新或删除它。 8 | * 未经身份验证的请求应只具有完全只读访问权限。 9 | 10 | # 将信息添加到我们的model 11 | 12 | 我们将对```Snippet``` model类进行一些修改改。首先,我们添加几个字段。其中一个字段将用于表示创建Snippet的用户。其他字段将用于存储代码的突出显示的HTML表示。 13 | 将以下两个字段添加到```Snippet```模型中```models.py```。 14 | 15 | ```python 16 | owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE) 17 | highlighted = models.TextField() 18 | ``` 19 | 20 | 我们还需要确保保存模型时,我们使用pygments代码高亮库来填充突出显示的字段。 21 | 22 | 我们需要一些额外的import: 23 | 24 | from pygments.lexers import get_lexer_by_name 25 | from pygments.formatters.html import HtmlFormatter 26 | from pygments import highlight 27 | 28 | 29 | 现在我们可以在.save()模型类中添加一个方法: 30 | 31 | ```python 32 | def save(self, *args, **kwargs): 33 | """ 34 | 使用“pygments”库创建高亮的HTML。 35 | """ 36 | lexer = get_lexer_by_name(self.language) 37 | linenos = self.linenos and 'table' or False 38 | options = self.title and {'title': self.title} or {} 39 | formatter = HtmlFormatter(style=self.style, linenos=linenos, 40 | full=True, **options) 41 | self.highlighted = highlight(self.code, lexer, formatter) 42 | super(Snippet, self).save(*args, **kwargs) 43 | ``` 44 | 45 | 完成这些工作后,我们需要更新我们的数据库表。通常我们会创建一个数据库```migration```来完成这个任务,但为了本教程的目的,我们只需删除数据库并重新开始。 46 | 47 | ``` 48 | rm -f db.sqlite3 49 | rm -r snippets/migrations 50 | python manage.py makemigrations snippets 51 | python manage.py migrate 52 | ``` 53 | 54 | 您可能还想创建几个不同的用户,用于测试API。最快的方法是使用createsuperuser命令。 55 | 56 | ```python 57 | python manage.py createsuperuser 58 | ``` 59 | 60 | 61 | ## 为我们的USER model添加端点 62 | 63 | 现在我们有一些用户可以使用,我们最好将这些用户的描述添加到我们的API中。创建一个新的序列化器是很容易的。在```serializers.py```添加: 64 | 65 | ```python 66 | from django.contrib.auth.models import User 67 | 68 | class UserSerializer(serializers.ModelSerializer): 69 | snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all()) 70 | 71 | class Meta: 72 | model = User 73 | fields = ('id', 'username', 'snippets') 74 | ``` 75 | 76 | 77 | 因为在用户模型上```snippets```是外键关系,所以在使用```ModelSerializer```类时它不会被默认包含,所以我们需要为它添加一个显式字段。 78 | 79 | 我们还会添加几个视图到```views.py```。我们只想为用户使用只读视图,因此我们将使用```ListAPIView```和```RetrieveAPIView```基于类的通用视图。 80 | 81 | ```python 82 | from django.contrib.auth.models import User 83 | 84 | 85 | class UserList(generics.ListAPIView): 86 | queryset = User.objects.all() 87 | serializer_class = UserSerializer 88 | 89 | 90 | class UserDetail(generics.RetrieveAPIView): 91 | queryset = User.objects.all() 92 | serializer_class = UserSerializer 93 | ``` 94 | 95 | 确保导入UserSerializer 96 | 97 | ```python 98 | from snippets.serializers import UserSerializer 99 | ``` 100 | 101 | 最后,我们需要通过在URL conf中引用这些视图来将这些视图添加到API中。将以下内容添加到```urls.py```文件中。 102 | 103 | ```python 104 | url(r'^users/$', views.UserList.as_view()), 105 | url(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view()), 106 | ``` 107 | 108 | ## Snippets 与 Users 关联 109 | 现在,如果我们创建了一个snippets,那么会无法将创建snippets的用户与snippets实例关联起来。用户不是作为序列化描述的一部分发送的,而是作为传入请求的属性。 110 | 111 | 我们处理这个问题的方式是通过重写```.perform_create()```snippets视图上的方法,这允许我们修改实例保存的方式,并处理传入请求或请求URL中隐含的任何信息。 112 | 113 | 在SnippetList视图类中,添加以下方法: 114 | 115 | ``` 116 | def perform_create(self, serializer): 117 | serializer.save(owner=self.request.user) 118 | ``` 119 | 120 | 我们的序列化器的```create()```方法现在将传递一个额外的'owner'字段,以及来自请求的验证数据。 121 | 122 | 123 | ## 更新我们的序列化器 124 | 125 | 现在,snippet与创建它的用户相关联,让我们更新```SnippetSerializer```。将以下字段添加到序列化文件```serializers.py```中: 126 | 127 | ```python 128 | owner = serializers.ReadOnlyField(source='owner.username') 129 | ``` 130 | 131 | 注意:确保在内部元类的字段列表中添加```owner```。 132 | 133 | 这个领域正在做一件很有趣的事情。该```source```参数控制用于填充字段的属性,并且可以指向序列化实例上的任何属性。它也可以采用上面显示的虚线符号,在这种情况下,它将遍历给定的属性,就像使用Django的模板语言一样。 134 | 135 | 136 | 我们添加的字段是无类型的```ReadOnlyField```类,与其他类型的字段相比,例如```CharField```,```BooleanField```等等... untyped ```ReadOnlyField```是只读的,并且将用于序列化表示,但它们被反序列化时不会用于更新模型当。我们也可以```CharField(read_only=True)```在这里使用。 137 | 138 | ## 为视图添加必要的权限 139 | 既然snippets与用户相关联,我们希望确保只有经过身份验证的用户才能创建、更新和删除snippets。 140 | 141 | REST框架包含许多权限类,我们可以使用这些权限类来限制可以访问视图的用户。在这种情况下,我们正在寻找的是```IsAuthenticatedOrReadOnly```确保经过身份验证的请求获得读写访问权限,未经身份验证的请求获得只读访问权限。 142 | 143 | 首先在views文件中添加以下导入 144 | 145 | ```python 146 | from rest_framework import permissions 147 | ``` 148 | 149 | 接着,下面的属性添加到```SnippetList```和```SnippetDetail```视图类。 150 | 151 | ```python 152 | permission_classes = (permissions.IsAuthenticatedOrReadOnly,) 153 | ``` 154 | 155 | 156 | ## 添加登录到Browsable API 157 | 如果您现在打开浏览器并访问API,会发现您不能创建新的snippets。为了创建,我们需要能够以用户身份登录。 158 | 159 | 我们可以通过编辑项目级```urls.py```文件中的```URLconf```来添加API的登录视图。 160 | 161 | 在文件顶部添加以下导入: 162 | 163 | ```python 164 | from django.conf.urls import include 165 | ``` 166 | 并且,在文件末尾,添加一个包含可访问API的登录和注销视图。 167 | 168 | ```python 169 | urlpatterns += [ 170 | url(r'^api-auth/', include('rest_framework.urls')), 171 | ] 172 | ``` 173 | ```r'^api-auth/'```模式的部分实际上可以是您想要使用的任何URL。 174 | 175 | 现在,如果您再打开浏览器并刷新页面,则会在页面右上方看到一个“login”链接。如果您以前创建的用户之一登录,则可以再次创建新的snippets。 176 | 177 | 一旦创建了几个代码片段,请访问```'/ users /'```url,并注意每个用户的“snippets”字段中与每个用户关联的代码段ID列表。 178 | 179 | 180 | ## 对象级权限 181 | 实际上,我们希望所有人都可以看到所有snippets,但也要确保只有创建snippets的用户才能更新或删除它。 182 | 183 | 为此,我们需要创建一个自定义权限。 184 | 185 | 在snippets应用程序中,创建一个新文件, ```permissions.py``` 186 | 187 | ```python 188 | from rest_framework import permissions 189 | 190 | 191 | class IsOwnerOrReadOnly(permissions.BasePermission): 192 | """ 193 | Custom permission to only allow owners of an object to edit it. 194 | """ 195 | 196 | def has_object_permission(self, request, view, obj): 197 | # Read permissions are allowed to any request, 198 | # so we'll always allow GET, HEAD or OPTIONS requests. 199 | if request.method in permissions.SAFE_METHODS: 200 | return True 201 | 202 | # Write permissions are only allowed to the owner of the snippet. 203 | return obj.owner == request.user 204 | ``` 205 | 206 | 现在我们可以通过编辑views类的```permission_classes```属性来将该自定义权限添加到我们的snippets实例```SnippetDetail```: 207 | 208 | ```python 209 | permission_classes = (permissions.IsAuthenticatedOrReadOnly, 210 | IsOwnerOrReadOnly,) 211 | ``` 212 | 213 | 确保也要导入```IsOwnerOrReadOnly```。 214 | 215 | ```python 216 | from snippets.permissions import IsOwnerOrReadOnly 217 | ``` 218 | 219 | 现在,如果您再次打开浏览器,如果您以创建snippets的相同用户身份登录,则会发现'DELETE'和'PUT'操作仅出现在代码片段实例端点上。 220 | 221 | 222 | ## 使用API​​进行身份验证 223 | 因为我们现在对API有一组权限,如果我们想要编辑任何snippets,我们需要验证对它的请求。我们还没有设置任何认证类,默认是当前应用的,它们是```SessionAuthentication```和```BasicAuthentication```。 224 | 225 | 当我们通过网络浏览器访问API时,然后浏览器会话将为请求提供所需的身份验证,我们就可以登录了。 226 | 227 | 如果我们正在以编程方式与API进行交互,那么我们需要在每个请求上明确提供身份验证凭据。 228 | 229 | 如果我们尝试创建一个没有进行身份验证的snippets,我们会得到一个错误: 230 | 231 | ```python 232 | http POST http://127.0.0.1:8000/snippets/ code="print 123" 233 | 234 | { 235 | "detail": "Authentication credentials were not provided." 236 | } 237 | ``` 238 | 239 | 我们可以通过添加我们之前创建的用户和密码来验证成功的请求。 240 | 241 | ```python 242 | http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789" 243 | 244 | { 245 | "id": 1, 246 | "owner": "admin", 247 | "title": "foo", 248 | "code": "print 789", 249 | "linenos": false, 250 | "language": "python", 251 | "style": "friendly" 252 | } 253 | ``` 254 | 255 | ## 概要 256 | 现在我们已经在我们的Web API上获得了相当细致的权限集合,以及系统用户和他们创建的snippets的终点。 257 | 258 | 在本教程的第5部分中,我们将研究如何通过为突出显示的片段创建HTML端点来将所有内容绑定在一起,并通过对系统内的关系使用超链接来提高API的凝聚力。 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /api/permissions.md: -------------------------------------------------------------------------------- 1 | # 权限 2 | 验证或识别本身通常不足以获取信息或代码。为此,请求访问的实体必须具有授权。 3 | 4 | - Apple开发者文档 5 | 6 | -------- 7 | 8 | 与身份验证和限制一起,权限决定是应该授予还是拒绝访问请求。 9 | 10 | 在任何其他代码被允许继续之前,权限检查总是在视图的开始处运行。权限检查通常会使用```request.user```和```request.auth```属性中的身份验证信息来确定是否允许传入请求。 11 | 12 | 权限用于授予或拒绝不同类别的用户访问API的不同部分。 13 | 14 | 最简单的权限方式是允许访问任何经过身份验证的用户,并拒绝任何未经身份验证的用户的访问。这对应```IsAuthenticated```于REST框架中的类。 15 | 16 | 稍微不太严格的权限将允许完全访问经过身份验证的用户,但允许对未经身份验证的用户进行只读访问。这对应```IsAuthenticatedOrReadOnly```于REST框架中的类。 17 | 18 | 19 | ## 如何确定权限 20 | REST框架中的权限总是被定义为权限类的列表。 21 | 22 | 在运行视图的主体之前,检查列表中的每个权限。如果有任何权限检查失败的情况```exceptions.PermissionDenied```或```exceptions.NotAuthenticated```将引发异常,并且视图的主体将无法运行。 23 | 24 | 当权限检查失败时,根据以下规则,将返回“403 Forbidden”或“401 Unauthorized”响应: 25 | 26 | 1. 该请求已成功通过身份验证,但权限被拒绝。 - 将返回HTTP 403 Forbidden响应。 27 | 2. 该请求未成功通过身份验证,并且最高优先级的身份验证类不使用```WWW-Authenticate```标头。 - 将返回HTTP 403 Forbidden响应。 28 | 3. 请求未成功验证,并且最高优先级认证类不使用```WWW-Authenticate```头。- HTTP 401未经授权的响应,并WWW-Authenticate返回一个适当的头文件。 29 | 30 | ## 对象级权限 31 | REST框架权限还支持对象级权限。对象级权限用于确定是否允许用户对特定对象进行操作,该特定对象通常是模型实例。 32 | 33 | 对象级权限在```.get_object()```调用时由REST框架的通用视图运行。与视图级权限一样,```exceptions.PermissionDenied```如果不允许用户对给定对象进行操作,则会引发异常。 34 | 35 | 如果您正在编写自己的视图并希望强制执行对象级权限,或者如果您```get_object```在通用视图上重写该方法,则需要```.check_object_permissions(request, obj)```在检索到视图的位置显式调用该方法目的。 36 | 37 | 这要么提出一个```PermissionDenied```或```NotAuthenticated```异常,或者简单地返回如果该视图有相应的权限。 38 | 39 | 例如: 40 | 41 | ```python 42 | def get_object(self): 43 | obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"]) 44 | self.check_object_permissions(self.request, obj) 45 | return obj 46 | ``` 47 | 48 | ### 对象级权限的限制 49 | 出于性能原因,通用视图在返回对象列表时不会自动将对象级权限应用于查询集中的每个实例。 50 | 51 | 通常,当您使用对象级权限时,您还需要适当地过滤查询集,以确保用户只能看到他们被允许查看的实例。 52 | 53 | 54 | 55 | ## 设置权限策略 56 | 使用该DEFAULT_PERMISSION_CLASSES设置可以全局设置默认权限策略。例如。 57 | 58 | ```python 59 | REST_FRAMEWORK = { 60 | 'DEFAULT_PERMISSION_CLASSES': ( 61 | 'rest_framework.permissions.IsAuthenticated', 62 | ) 63 | } 64 | ``` 65 | 如果未指定,则此设置默认为允许无限制访问: 66 | 67 | ```python 68 | 'DEFAULT_PERMISSION_CLASSES': ( 69 | 'rest_framework.permissions.AllowAny', 70 | ) 71 | ``` 72 | 您还可以使用APIView基于类的视图在每个视图或每个视图的基础上设置身份验证策略。 73 | 74 | ```python 75 | from rest_framework.permissions import IsAuthenticated 76 | from rest_framework.response import Response 77 | from rest_framework.views import APIView 78 | 79 | class ExampleView(APIView): 80 | permission_classes = (IsAuthenticated,) 81 | 82 | def get(self, request, format=None): 83 | content = { 84 | 'status': 'request was permitted' 85 | } 86 | return Response(content) 87 | ``` 88 | 89 | 或者,如果您将@api_view装饰器与基于功能的视图一起使用。 90 | 91 | ```python 92 | from rest_framework.decorators import api_view, permission_classes 93 | from rest_framework.permissions import IsAuthenticated 94 | from rest_framework.response import Response 95 | 96 | @api_view(['GET']) 97 | @permission_classes((IsAuthenticated, )) 98 | def example_view(request, format=None): 99 | content = { 100 | 'status': 'request was permitted' 101 | } 102 | return Response(content) 103 | 104 | ``` 105 | 106 | 注意:当您通过类属性或装饰器设置新的权限类时,您要通知视图忽略settings.py文件中设置的默认列表。 107 | 108 | 109 | -------- 110 | 111 | # API参考 112 | ## AllowAny 113 | 该AllowAny许可类将允许不受限制的访问,不管请求被认证或未认证的。 114 | 115 | 此权限并非严格要求,因为您可以通过为权限设置使用空列表或元组来获得相同的结果,但是您可能会发现指定此类是有用的,因为它会使意图变得明确。 116 | 117 | ## IsAuthenticated 118 | 该```IsAuthenticated```许可类将拒绝允许任何未认证用户,并允许许可,否则。 119 | 120 | 如果您希望您的API只能由注册用户访问,则此权限是适合的。 121 | 122 | ## IsAdminUser 123 | 所述```IsAdminUser```许可类将拒绝许可给任何用户,除非```user.is_staff```是```True```在这种情况下的许可将被允许。 124 | 125 | 如果您希望您的API只能被部分受信任的管理员访问,则此权限是适合的。 126 | 127 | ## IsAuthenticatedOrReadOnly 128 | 在```IsAuthenticatedOrReadOnly```将允许被授权的用户进行任何请求。只有当请求方法是“安全”方法之一时,才允许未经授权的用户请求; ```GET```,```HEAD```或者```OPTIONS```。 129 | 130 | 如果您希望您的API允许匿名用户读取权限,并且只允许对已通过身份验证的用户进行写入权限,则此权限是适合的。 131 | 132 | ## DjangoModelPermissions 133 | 此权限类与Django的标准```django.contrib.auth``` 模型权限相关联。此权限只能应用于具有```.queryset```属性设置的视图。只有在用户通过身份验证并分配了相关模型权限的情况下,授权才会被授予。 134 | 135 | ```POST```请求要求用户拥有add模型的权限。 136 | ```PUT```并且```PATCH```请求要求用户```change```获得模型的许可。 137 | ```DELETE```请求要求用户拥有```delete```模型的权限。 138 | 默认行为也可以被重写以支持自定义模型权限。例如,您可能想要包含请求的view模型权限GET。 139 | 140 | 要使用自定义模型权限,请覆盖```DjangoModelPermissions```并设置```.perms_map```属性。有关详细信息,请参阅源代码。 141 | 142 | 使用不包含```queryset```属性的视图。 143 | 如果您将此权限与使用重写```get_queryset()```方法的视图一起使用,则该视图上可能没有```queryset```属性。在这种情况下,我们建议还使用标记查询集标记视图,以便此类可以确定所需的权限。例如: 144 | 145 | ```python 146 | queryset = User.objects.none() # Required for DjangoModelPermissions 147 | DjangoModelPermissionsOrAnonReadOnly 148 | ``` 149 | 150 | 与此类似```DjangoModelPermissions```,但也允许未经身份验证的用户拥有对该API的只读访问权限。 151 | 152 | ## DjangoObjectPermissions 153 | 此权限类绑定到Django的标准对象权限框架中,该框架允许对模型执行每对象权限。为了使用此权限类,您还需要添加支持对象级权限的权限后端,例如```django-guardian```。 154 | 155 | 同样```DjangoModelPermissions```,此权限只能应用于具有```.queryset```属性或```.get_queryset()```方法的视图。只有在用户通过身份验证并且具有相关的每个对象权限和相关的模型权限后,授权才会被授予。 156 | 157 | 1. POST请求要求用户拥有add模型实例的权限。 158 | 2. PUT并且PATCH请求要求用户拥有change模型实例的权限。 159 | 3. DELETE请求要求用户拥有delete模型实例的权限。 160 | 161 | 请注意,```DjangoObjectPermissions``` 不要求```django-guardian```包,并且应该支持其他对象级后端同样出色。 162 | 163 | 与```DjangoModelPermissions```您一样,您可以通过覆盖```DjangoObjectPermissions```和设置```.perms_map```属性来使用自定义模型权限。有关详细信息,请参阅源代码。 164 | 165 | 注意:如果你需要的对象级别```view```的权限```GET```,```HEAD```并```OPTIONS```请求,你要还考虑增加的```DjangoObjectPermissionsFilter```类,以确保该列表只端点返回结果包括用户拥有适当的查看权限的对象。 166 | 167 | 168 | 169 | -------- 170 | 171 | # 自定义权限 172 | 要实现自定义权限,请覆盖BasePermission并实现以下方法之一或两者: 173 | 174 | 1. ```.has_permission(self, request, view)``` 175 | 2. ```.has_object_permission(self, request, view, obj)``` 176 | ```True```如果请求应该被授予访问权限,则方法应该返回,False否则返回。 177 | 178 | 如果您需要测试如果请求是读操作或写操作,你应该检查对常量的请求方法```SAFE_METHODS```,这是一个包含一个元组```'GET'```,```'OPTIONS'```和```'HEAD'```。例如: 179 | 180 | ```python 181 | if request.method in permissions.SAFE_METHODS: 182 | # Check permissions for read-only request 183 | else: 184 | # Check permissions for write request 185 | ``` 186 | 187 | ***注意***:```has_object_permission```只有在视图级```has_permission```检查已通过时才会调用实例级方法。另请注意,为了运行实例级检查,视图代码应该显式调用```.check_object_permissions(request, obj)```。如果您使用的是通用视图,那么默认情况下会为您处理。(基于函数的视图将需要明确检查对象权限,从而提高```PermissionDenied```失败。) 188 | 189 | ```PermissionDenied```如果测试失败,自定义权限将引发异常。要更改与异常相关的错误消息,请```message```直接在您的自定义权限上实施属性。否则```default_detail```,```PermissionDenied```将使用来自该属性的属性。 190 | 191 | ```python 192 | from rest_framework import permissions 193 | 194 | class CustomerAccessPermission(permissions.BasePermission): 195 | message = 'Adding customers not allowed.' 196 | 197 | def has_permission(self, request, view): 198 | ... 199 | ``` 200 | 201 | # 例子 202 | 203 | 以下是一个权限类的示例,该权限类将入局请求的IP地址与黑名单进行比对,并在IP被列入黑名单时拒绝该请求。 204 | 205 | ```python 206 | from rest_framework import permissions 207 | 208 | class BlacklistPermission(permissions.BasePermission): 209 | """ 210 | Global permission check for blacklisted IPs. 211 | """ 212 | 213 | def has_permission(self, request, view): 214 | ip_addr = request.META['REMOTE_ADDR'] 215 | blacklisted = Blacklist.objects.filter(ip_addr=ip_addr).exists() 216 | return not blacklisted 217 | ``` 218 | 219 | 除了针对所有传入请求运行的全局权限外,还可以创建对象级权限,这些权限仅针对影响特定对象实例的操作运行。例如: 220 | 221 | ```python 222 | class IsOwnerOrReadOnly(permissions.BasePermission): 223 | """ 224 | Object-level permission to only allow owners of an object to edit it. 225 | Assumes the model instance has an `owner` attribute. 226 | """ 227 | 228 | def has_object_permission(self, request, view, obj): 229 | # Read permissions are allowed to any request, 230 | # so we'll always allow GET, HEAD or OPTIONS requests. 231 | if request.method in permissions.SAFE_METHODS: 232 | return True 233 | 234 | # Instance must have an attribute named `owner`. 235 | return obj.owner == request.user 236 | ``` 237 | 238 | 请注意,通用视图将检查适当的对象级权限,但如果您正在编写自己的自定义视图,则需要确保检查自己的对象级权限检查。```self.check_object_permissions(request, obj)```一旦你拥有对象实例,你可以通过从视图中调用。APIException如果任何对象级权限检查失败,此调用将引发适当的,否则将简单地返回。 239 | 240 | 另请注意,通用视图将仅检查检索单个模型实例的视图的对象级权限。如果您需要列表视图的对象级过滤,则需要单独过滤查询集。查看过滤文档以获取更多详细信息。 241 | 242 | -------- 243 | 244 | # 第三方软件包 245 | 以下第三方包也可用。 246 | 247 | ## 组成权限 248 | 的组成权限包提供了一种简单的方式来定义复杂和多深度(与逻辑运算符)权限对象,使用小的和可重复使用的部件。 249 | 250 | ## REST条件 251 | 在静止状态下包装的另一个扩展简单和方便的方式构建复杂的权限。该扩展允许您将权限与逻辑运算符组合在一起。 252 | 253 | ## DRY Rest权限 254 | 该DRY休息权限包提供定义单个默认和自定义操作不同的权限的能力。该软件包是为具有从应用程序的数据模型中定义的关系派生的权限的应用程序生成的。它还支持通过API的序列化程序返回到客户端应用程序的权限检查。此外,它还支持向默认和自定义列表操作添加权限,以限制每个用户检索的数据。 255 | 256 | ## Django Rest框架角色 257 | 在Django的REST框架角色包使得它更容易在多个类型的用户参数的API。 258 | 259 | ## Django Rest框架API密钥 260 | 在Django的REST框架API密钥包,您可以确保服务器的每个请求需要一个API密钥头。您可以从django管理界面生成一个。 261 | 262 | ## Django Rest框架角色过滤器 263 | 的Django的休息框架中的作用的过滤器包提供在多个类型的角色简单滤波。 -------------------------------------------------------------------------------- /api/validators.md: -------------------------------------------------------------------------------- 1 | # 校验器Validators 2 | 3 | 校验器可以用于在不同类型的字段之间重新使用校验逻辑。 4 | 5 | 大多数情况下,您在REST框架中处理校验时,只需依赖缺省字段校验,或者在序列化程序或字段类上编写显式校验方法。 6 | 7 | 但是,有时您会希望将校验逻辑放置到可重用组件中,以便在整个代码库中轻松地重用它。这可以通过使用校验器函数和校验器类来实现。 8 | 9 | ## 在REST框架中校验 10 | ```Django REST```框架序列化器中的校验与```Django ModelForm```类中校验的工作方式有点不同。 11 | 12 | 使用```ModelForm```,校验部分地在窗体上执行,部分地在模型实例上执行。使用REST框架,校验完全在序列化程序类上执行。有利原因如下: 13 | 14 | 它引入了一个适当的问题分离,使您的代码行为更加明显。 15 | 使用快捷```ModelSerializer```类和使用显式```Serializer```类很容易切换。任何正在使用的校验行为```ModelSerializer```都很容易复制。 16 | 打印```repr```一个序列化器实例将显示它到底应用了哪些校验规则。在模型实例上没有额外的隐藏校验行为。 17 | 当你使用```ModelSerializer```所有这些都是为你自动处理的。如果您想要改为使用```Serializer```类,那么您需要明确定义校验规则。 18 | 19 | ###例 20 | 21 | 作为REST框架如何使用显式校验的示例,我们将采用一个简单的模型类,该类具有唯一性约束的字段。 22 | 23 | ```python 24 | class CustomerReportRecord(models.Model): 25 | time_raised = models.DateTimeField(default=timezone.now, editable=False) 26 | reference = models.CharField(unique=True, max_length=20) 27 | description = models.TextField() 28 | ``` 29 | 30 | 以下```ModelSerializer```是我们可用于创建或更新实例的基本信息```CustomerReportRecord```: 31 | 32 | ```python 33 | class CustomerReportSerializer(serializers.ModelSerializer): 34 | class Meta: 35 | model = CustomerReportRecord 36 | ``` 37 | 38 | 如果我们manage.py shell现在使用我们可以打开Django shell 39 | 40 | ```python 41 | >>> from project.example.serializers import CustomerReportSerializer 42 | >>> serializer = CustomerReportSerializer() 43 | >>> print(repr(serializer)) 44 | CustomerReportSerializer(): 45 | id = IntegerField(label='ID', read_only=True) 46 | time_raised = DateTimeField(read_only=True) 47 | reference = CharField(max_length=20, validators=[]) 48 | description = CharField(style={'type': 'textarea'}) 49 | ``` 50 | 51 | 这里有趣的是该reference领域。我们可以看到唯一性约束由序列化程序字段上的校验程序明确执行。 52 | 53 | 由于这种更明确的风格,REST框架包含一些在核心Django中不可用的校验器类。这些类在下面详述。 54 | 55 | 56 | 57 | ## UniqueValidator 58 | 这个校验器可以用来强制unique=True模型字段的约束。它需要一个必需的参数和一个可选的```messages```参数: 59 | 60 | 1. queryset 必需 - 这是查询集应对其执行唯一性。 61 | 2. message - 校验失败时应使用的错误消息。 62 | 3. lookup - 用于查找具有正在校验的值的现有实例的查找。默认为'exact'。 63 | 64 | 65 | 这个校验器应该应用于序列化程序字段,如下所示: 66 | 67 | ```python 68 | from rest_framework.validators import UniqueValidator 69 | 70 | slug = SlugField( 71 | max_length=100, 72 | validators=[UniqueValidator(queryset=BlogPost.objects.all())] 73 | ) 74 | ``` 75 | 76 | 77 | ## UniqueTogetherValidator 78 | 这个校验器可以用来强制```unique_together```对模型实例进行约束。它有两个必需的参数和一个可选```messages```参数: 79 | 80 | 1. queryset 必需 - 这是查询集应对其执行唯一性。 81 | 2. fields 必需 - 应该创建唯一集合的字段名称列表或元组。这些必须作为序列化程序类中的字段存在。 82 | 3. message - 校验失败时应使用的错误消息。 83 | 84 | 校验器应该应用于序列化器类,如下所示: 85 | 86 | ```python 87 | from rest_framework.validators import UniqueTogetherValidator 88 | 89 | class ExampleSerializer(serializers.Serializer): 90 | # ... 91 | class Meta: 92 | # ToDo items belong to a parent list, and have an ordering defined 93 | # by the 'position' field. No two items in a given list may share 94 | # the same position. 95 | validators = [ 96 | UniqueTogetherValidator( 97 | queryset=ToDoItem.objects.all(), 98 | fields=('list', 'position') 99 | ) 100 | ] 101 | ``` 102 | 103 | 104 | **注意:***UniqueTogetherValidation类总是施加一个隐含的约束,即它所应用的所有字段总是按需要处理。有default值的字段是一个例外,因为它们总是提供一个值,即使从用户输入中省略也是如此。* 105 | 106 | 107 | ## UniqueForDateValidator 108 | ## UniqueForMonthValidator 109 | ## UniqueForYearValidator 110 | 这些校验器可用于强制实施模型实例```unique_for_date```,```unique_for_month```并```unique_for_year```约束模型实例。他们采取以下论点: 111 | 112 | 1. queryset 必需 - 这是查询集应对其执行唯一性。 113 | 2. field 必需 - 在给定日期范围内唯一性将被校验的字段名称。这必须作为序列化程序类中的字段存在。 114 | 3. date_field required - 将用于确定唯一性约束的日期范围的字段名称。这必须作为序列化程序类中的字段存在。 115 | 4. message - 校验失败时应使用的错误消息。 116 | 117 | 校验器应该应用于序列化器类,如下所示: 118 | 119 | ```python 120 | from rest_framework.validators import UniqueForYearValidator 121 | 122 | class ExampleSerializer(serializers.Serializer): 123 | # ... 124 | class Meta: 125 | # Blog posts should have a slug that is unique for the current year. 126 | validators = [ 127 | UniqueForYearValidator( 128 | queryset=BlogPostItem.objects.all(), 129 | field='slug', 130 | date_field='published' 131 | ) 132 | ] 133 | ``` 134 | 135 | 序列化程序类中始终需要用于校验的日期字段。您不能简单地依赖模型类default=...,因为用于默认值的值直到校验运行后才会生成。 136 | 137 | 您可能需要使用几种样式,具体取决于您希望API的行为方式。如果您使用```ModelSerializer```的是REST框架,那么您可能仅仅依靠REST框架为您生成的默认值,但如果您正在使用```Serializer```或者只是想要更明确的控制,请使用下面演示的样式。 138 | 139 | #### 与可写日期字段一起使用。 140 | 如果您希望日期字段是可写的,唯一值得注意的是您应确保它始终可用于输入数据中,可以通过设置```default```参数或通过设置```required=True```。 141 | 142 | ```python 143 | published = serializers.DateTimeField(required=True) 144 | ``` 145 | #### 与只读日期字段一起使用。 146 | 如果您希望日期字段可见,但用户无法编辑,请设置```read_only=True```并另外设置```default=...```参数。 147 | 148 | ```python 149 | published = serializers.DateTimeField(read_only=True, default=timezone.now) 150 | ``` 151 | 该字段对用户不可写,但默认值仍将传递给该用户```validated_data```。 152 | 153 | #### 与隐藏的日期字段一起使用。 154 | 如果您希望日期字段对用户完全隐藏,请使用```HiddenField```。该字段类型不接受用户输入,而是始终将其默认值返回给```validated_data```序列化程序。 155 | 156 | ```python 157 | published = serializers.HiddenField(default=timezone.now) 158 | ``` 159 | 160 | **注意**:*这些UniqueForValidation类强加一个隐式约束,即它们应用于的字段总是按照需要处理。有default值的字段是一个例外,因为它们总是提供一个值,即使从用户输入中省略也是如此。* 161 | 162 | 163 | ## 高级字段默认值 164 | 了在串行化器在多个领域得到应用校验有时可以要求不应该由API客户端被提供一个字段输入,但是可作为输入提供给校验器。 165 | 166 | 您可能想要用于这种校验的两种模式包括: 167 | 168 | * 使用HiddenField。该字段将出现在序列化器输出表示中,validated_data但不会被使用。 169 | * 使用标准字段read_only=True,但也包括default=…参数。该字段将用于串行器输出表示中,但不能由用户直接设置。 170 | * 171 | REST框架包含一些在这种情况下可能有用的默认值。 172 | 173 | #### CurrentUserDefault 174 | 175 | 可用于表示当前用户的默认类。为了使用它,在实例化序列化程序时,'request'必须作为上下文字典的一部分提供。 176 | 177 | ```python 178 | owner = serializers.HiddenField( 179 | default=serializers.CurrentUserDefault() 180 | ) 181 | ``` 182 | 183 | #### CreateOnlyDefault 184 | 可用于在创建操作期间仅设置默认参数的默认类。在更新期间,该字段被省略。 185 | 186 | 它采用一个参数,这是在创建操作期间应该使用的默认值或可调用参数。 187 | 188 | ```python 189 | created_at = serializers.DateTimeField( 190 | default=serializers.CreateOnlyDefault(timezone.now) 191 | ) 192 | ``` 193 | 194 | 195 | ## 校验器的限制 196 | 有一些不明确的情况,您需要明确处理校验,而不是依赖ModelSerializer生成的默认序列化程序类 。 197 | 198 | 在这些情况下,您可能希望通过为序列化程序Meta.validators属性指定一个空列表来禁用自动生成的校验程序。 199 | 200 | ### 可选字段 201 | 默认情况下,“独一无二”校验会强制所有字段都是 required=True。在某些情况下,您可能希望显式应用 required=False其中一个字段,在这种情况下,校验所需的行为是不明确的。 202 | 203 | 在这种情况下,您通常需要从序列化程序类中排除校验程序,而是在.validate()方法中或在视图中显式编写任何校验逻辑。 204 | 205 | 例如: 206 | 207 | ```python 208 | class BillingRecordSerializer(serializers.ModelSerializer): 209 | def validate(self, data): 210 | # Apply custom validation either here, or in the view. 211 | 212 | class Meta: 213 | fields = ('client', 'date', 'amount') 214 | extra_kwargs = {'client': {'required': False}} 215 | validators = [] # Remove a default "unique together" constraint. 216 | ``` 217 | 218 | ### 更新嵌套序列化器 219 | 将更新应用于现有实例时,唯一性校验程序将从唯一性检查中排除当前实例。当前实例在唯一性检查的上下文中可用,因为它作为序列化程序中的一个属性存在,最初instance=...在实例化序列化程序时已通过使用 。 220 | 221 | 在嵌套序列化器上进行更新操作的情况下,无法应用此排除,因为该实例不可用。 222 | 223 | 再一次,你可能想要从序列化类中明确地移除校验器,并将校验约束的代码明确地写入.validate()方法或视图中。 224 | 225 | ### 调试复杂的案例 226 | 如果您不确定ModelSerializer类会产生什么行为,通常是运行一个好主意manage.py shell,并打印序列化程序的一个实例,以便您可以检查它自动为您生成的字段和校验程序。 227 | 228 | ```python 229 | >>> serializer = MyComplexModelSerializer() 230 | >>> print(serializer) 231 | class MyComplexModelSerializer: 232 | my_fields = ... 233 | ``` 234 | 235 | 还要记住,在复杂情况下,明确定义序列化程序类通常会更好,而不是依赖于默认 ModelSerializer行为。这涉及更多的代码,但确保了最终的行为更加透明。 236 | 237 | 238 | 239 | ## 编写自定义校验器 240 | 您可以使用任何Django现有的校验器,或编写您自己的自定义校验器。 241 | 242 | ### 基于功能 243 | 校验器可以是任何可引发serializers.ValidationError失败的可调用函数。 244 | 245 | ```python 246 | def even_number(value): 247 | if value % 2 != 0: 248 | raise serializers.ValidationError('This field must be an even number.') 249 | ``` 250 | ### 现场级校验 251 | 您可以通过向子类添加.validate_方法来指定自定义字段级校验Serializer。这在 Serializer文档中有记录 252 | 253 | ### 基于类的 254 | 要编写一个基于类的校验器,请使用该__call__方法。基于类的校验器很有用,因为它们允许您参数化和重用行为。 255 | 256 | ```python 257 | class MultipleOf(object): 258 | def __init__(self, base): 259 | self.base = base 260 | 261 | def __call__(self, value): 262 | if value % self.base != 0: 263 | message = 'This field must be a multiple of %d.' % self.base 264 | raise serializers.ValidationError(message) 265 | ``` 266 | 267 | ### 运用 set_context() 268 | 在一些高级的情况下,你可能想要一个校验器被传递到序列化器字段,它被用作额外的上下文。你可以通过set_context在基于类的校验器上声明一个方法来实现。 269 | 270 | ```python 271 | def set_context(self, serializer_field): 272 | # Determine if this is an update or a create operation. 273 | # In `__call__` we can then use that information to modify the validation behavior. 274 | self.is_update = serializer_field.parent.instance is not None 275 | 276 | ``` 277 | -------------------------------------------------------------------------------- /api/serializersfield.md: -------------------------------------------------------------------------------- 1 | ## 序列化器字段Serializer fields 2 | 3 | > 表单类中的每个字段不仅负责验证数据,还负责“cleaning”它们 —— 将它们转换为正确的格式。这是个非常好用的功能,因为它允许字段以多种方式输入数据,并总能得到一致的输出。 4 | —— [Django 官方文档(Form API)](https://docs.djangoproject.com/en/2.0/ref/forms/api/#django.forms.Form.cleaned_data) 5 | 6 | 序列化器字段负责处理原始数据和内部数据类型的互相转换。还负责处理验证输入的数据,以及从父级对象中检索和设置值。 7 | 8 | --- 9 | 10 | 注意: 这些序列化器字段是定义在```fields.py```文件中的,但是按照惯例,我们应该通过```from rest_framework import serializers```并且通过```serializers.```来使用这些字段。 11 | 12 | --- 13 | 14 | #### 核心参数 15 | 16 | 每个序列化器字段类的构造至少需要下面的几个参数。一些字段类可能需要额外的,字段特定的参数,但是无论如何都需要下面的几个参数: 17 | 18 | **```read_only```** 19 | 20 | 只读字段被包含在API输出中,但是不应该在创建或者更新期间包含在输入中。任何不正确包含在序列化器输入中的```read_onpy```字段将会被忽略。 21 | 22 | 设置字段的这个参数为```True```确保在序列化过程中能正确显示,但是在创建或者更新一个实例的反序列化期间不会被使用。 23 | 24 | 默认是```False```。 25 | 26 | **```write_only```** 27 | 28 | 跟上面的正好相反。设置字段的这个参数为```True```,确保在创建或者更新一个实例的反序列化期间可以使用该字段, 但是在序列化的时候不会包含该字段。 29 | 30 | 默认是```False```。 31 | 32 | **```required```** 33 | 34 | 如果在反序列化期间没有提供该字段,那么会触发一个异常。如果该字段在反序列化期间不是必须的,那么应该设置这个参数为```False```。 35 | 36 | 当序列化实例的时候,设置为```False```允许对象的属性或者是字典的key可以被忽略。如果不包含key,那么在输出表示中将不会被包含。 37 | 38 | 默认是```True```。 39 | 40 | **```default```** 41 | 42 | 如果在输入的值中没有被提供,那么该字段的值将会使用默认值。如果没有设置的话,默认行为是什么都不填充。 43 | 44 | 在```局部更新PATCH```的期间,不会应用```default```参数。在局部更新期间,只有在传入数据中提供的值才会被验证返回。 45 | 46 | 可以设置为一个函数或者其他可以调用的任何对象,在这种场景下,每次使用该值的时候都会进行评估。当被调用的时候,它将不会接收任何参数。如果可调用的对象有```set_context```方法,那么在每次获取字段实例的值之前都会调用该方法。这个和```[validators](./validators.md)```的工作方式一样。 47 | 48 | 当序列化实例的时候,如果实例的对象属性或者字典中的关键字没有被提供的时候,将会使用该值。 49 | 50 | 注意,设置默认值意味着该字段并不是必须的。如果一个字段同时包含```default```和```required```关键字参数,那么是无效的会抛出异常。 51 | 52 | **```allow_null```** 53 | 54 | 默认情况,当一个```None```传递给序列化字段的时候会触发异常。如果设置该属性为```True```,那么会认为```None```是有效的。 55 | 56 | 注意,在序列化的时候如果没有明确的指定```default```,如果设置该参数为```True```,那么意味着该字段```default```为```null```, 但是这并不是意味着在反序列化的时候的默认值。 57 | 58 | 默认为```False``` 59 | 60 | **```source```** 61 | 62 | 这个参数用于填充该字段的属性的名称。可能是一个方法,比如```URLField(source='get_absolute_url')```,也可能是一个属性```EmailField(source='user.email')```。如果在属性遍历的期间任何对象不存在,或者为空,那么可能需要提供一个默认值```default```。 63 | 64 | 注意```source='*'```是由特殊的表示意义的,他表示应该把对象传进来。这对创建嵌套表示或对于需要访问完整对象以确定输出表示的字段非常有用。 65 | 66 | 默认为该字段的名称。 67 | 68 | **```validators```** 69 | 70 | 是一个验证器函数列表,被用于传进来的字段输入,最终要么触发验证错误的异常,要么只是简单的返回。验证器函数通常应该触发```serializers.ValidationError```异常。 71 | 72 | **```error_messages```** 73 | 74 | 包含错误信息的字典。 75 | 76 | **```label```** 77 | 78 | 一个简短的文本字符串,可用作html表单字段或其他描述性元素中字段的名称。 79 | 80 | **```help_text```** 81 | 82 | 一个文本字符串,可用作html表单字段或其他描述性元素中字段的描述。 83 | 84 | **```initial```** 85 | 86 | 一个应该用于预填充html表单字段值的值。你可以传递一个可调用对象,就像你对任何常规Django字段可以做的一样: 87 | 88 | ```python 89 | import datetime 90 | from rest_framework import serializers 91 | class ExampleSerializer(serializers.Serializer): 92 | day = serializers.DateField(initial=datetime.date.today) 93 | ``` 94 | 95 | **```style```** 96 | 97 | 一个键值对的字典,可用于控制渲染器如何渲染字段。 98 | 99 | ```python 100 | # Use for the input. 101 | password = serializers.CharField( 102 | style={'input_type': 'password'} 103 | ) 104 | 105 | # Use a radio input instead of a select input. 106 | color_channel = serializers.ChoiceField( 107 | choices=['red', 'green', 'blue'], 108 | style={'base_template': 'radio.html'} 109 | ) 110 | ``` 111 | 112 |
113 |
114 |
115 |
116 | 117 | --- 118 | 119 | ### Boolean字段 120 | 121 | #### BooleanField 122 | 123 | 表示一个布尔值。 124 | 125 | 注意,会使用```required=True```选项生成默认的```BooleanField```实例。因为Django的```models.BooleanField```总是```blank=True```。如果想改变这个行为,需要明确的指定```BooleanField```在序列化类中。 126 | 127 | 对应```django.db.models.fields.BooleanField``` 128 | 129 | #### NullBooleanField 130 | 131 | 可以接受```None```的布尔字段。 132 | 133 | 对应```django.db.models.fields.NullBooleanField``` 134 | 135 |
136 |
137 |
138 |
139 | 140 | --- 141 | 142 | ### String 字段 143 | 144 | #### CharField 145 | 146 | 一个文本表示。可选的验证参数```max_length```和```min_length```。 147 | 148 | 对应```django.db.models.fields.CharField```或者```django.db.models.fields.TextField``` 149 | 150 | 原型:```CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)``` 151 | 152 | #### EmailField 153 | 154 | 一个文本表示。但是默认会验证是否是Email。 155 | 156 | 对应```django.db.models.fields.EmailField``` 157 | 158 | #### RegexField 159 | 160 | 一个文本表示,用于验证给定值是否与某个正则表达式匹配。 161 | 162 | 对应```django.forms.fields.RegexField``` 163 | 164 | 原型:```RegexField(regex, max_length=None, min_length=None, allow_blank=False)``` 165 | 166 | #### SlugField 167 | 168 | 一个```RegexField```,但是他默认只会匹配```[a-zA-Z0-9_-]+``` 169 | 170 | 对应```django.db.models.fields.SlugField``` 171 | 172 | 原型: ```SlugField(max_length=50, min_length=None, allow_blank=False)``` 173 | 174 | #### URLField 175 | 176 | 一个```RegexField```,但他只会匹配URL格式。有效的格式是```http:///```。 177 | 178 | 对应```django.db.models.fields.URLField```使用Django的校验器```django.core.validators.URLValidator``` 179 | 180 | 原型: ```URLField(max_length=200, min_length=None, allow_blank=False)``` 181 | 182 | #### UUIDField 183 | 184 | 一个确保输入是有效的uuid字符串的字段。```to_internal_value```方法将会返回一个```uuid.UUID```实例。输出格式如下: 185 | 186 | ```python 187 | "de305d54-75b4-431b-adb2-eb6b9e546013" 188 | ``` 189 | 190 | 原型: ```UUIDField(format='hex_verbose')``` 191 | 192 | 注意:```format```是用来确定UUID的样式: 193 | 194 | - ```hex_verbose```: 十六进制,包含连字符。```"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"``` 195 | - ```'hex'```: 紧凑的十六进制,不包含连字符。```"5ce0e9a55ffa654bcee01238041fb31a"``` 196 | - ```'int'```: 一个128位的uuid整数表示。 ```"123456789012312313134124512351145145114"``` 197 | - ```'urn'```: 遵循RFC 4122 URN的UUID。```"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"``` 198 | 199 | #### FilePathField 200 | 201 | 一个字段,其选项仅限于文件系统上某个目录中的文件名。 202 | 203 | 对应```django.forms.fields.FilePathField``` 204 | 205 | 原型: ```FilePathField(path, match=None, recursive=False, allow_files=True, allow_folders=False, required=None, **kwargs)``` 206 | 207 | - ```path```: 这个```FilePathField ```应该得到其选择的目录的绝对文件系统路径。例如: ```"/home/images"``` 208 | - ```match```: 将会作为一个正则表达式来匹配文件名。但请注意正则表达式将将被作用于基本文件名,而不是完整路径。 209 | - ```recursive```: 是否递归子目录。 210 | - ```allow_files```: 指定是否应该包含指定位置的文件。该参数或```allow_folders``` 中必须有一个为 True 211 | - ```allow_folders```: 声明是否包含指定位置的文件夹。该参数或 allow_files 中必须有一个为 True 212 | 213 | #### IPAddressField 214 | 215 | 一个确保输入是有效的ipv4或ipv6字符串的字段。 216 | 217 | 对应```django.forms.fields.IPAddressField```或者```django.forms.fields.GenericIPAddressField``` 218 | 219 | 原型: ```IPAddressField(protocol='both', unpack_ipv4=False, **options)``` 220 | 221 |
222 |
223 |
224 |
225 | 226 | --- 227 | 228 | ### Numeric 字段 229 | 230 | #### IntegerField 231 | 232 | 表示一个整数。 233 | 234 | 对应```django.db.models.fields.IntegerField```或者```django.db.models.fields.SmallIntegerField```或者```django.db.models.fields.PositiveIntegerField```或者```django.db.models.fields.PositiveSmallIntegerField``` 235 | 236 | 原型:```IntegerField(max_value=None, min_value=None)``` 237 | 238 | #### FloatField 239 | 240 | 表示一个浮点数。 241 | 242 | 对应```django.db.models.fields.FloatField``` 243 | 244 | 原型:```FloatField(max_value=None, min_value=None)``` 245 | 246 | #### DecimalField 247 | 248 | 表示一个十进制。他是Python的```Decimal```实例。 249 | 250 | 对应```django.db.models.fields.DecimalField``` 251 | 252 | 原型:```DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)``` 253 | 254 | 最大5位数,包含2位小数: 255 | 256 | ```python 257 | serializers.DecimalField(max_digits=5, decimal_places=2) 258 | ``` 259 | 260 | 小数点后10位的分辨率验证小于10亿的数字: 261 | 262 | ```python 263 | serializers.DecimalField(max_digits=19, decimal_places=10) 264 | ``` 265 | 266 |
267 |
268 |
269 |
270 | 271 | --- 272 | 273 | ### 日期和时间字段 274 | 275 | #### DateTimeField 276 | 277 | 表示日期和时间。 278 | 279 | 对应```django.db.models.fields.DateTimeField``` 280 | 281 | 原型: ```DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)``` 282 | 283 | 注意,如果在使用```ModelSerializer```或者是```HyperlinkedModelSerializer```的时候,如果你的模型字段有```auto_now=True```或者是```auto_now_add=True```,那么该序列化器字段默认会是```read_only=True``` 284 | 285 | 如果你想要重写这个行为,你需要显式的声明序列化器字段: 286 | 287 | ```python 288 | class CommentSerializer(serializers.ModelSerializer): 289 | created = serializers.DateTimeField() 290 | 291 | class Meta: 292 | model = Comment 293 | ``` 294 | 295 | #### DateField 296 | 297 | 表示一个日期。 298 | 299 | 对应```django.db.models.fields.DateField``` 300 | 301 | 原型: ```DateField(format=api_settings.DATE_FORMAT, input_formats=None)``` 302 | 303 | #### TimeField 304 | 305 | 表示一个时间。 306 | 307 | 对应```django.db.models.fields.TimeField``` 308 | 309 | 原型: ```TimeField(format=api_settings.TIME_FORMAT, input_formats=None)``` 310 | 311 | #### DurationField 312 | 313 | 表示一个持续时间。 314 | 315 | 对应```django.db.models.fields.DurationField``` 316 | 317 |
318 |
319 |
320 |
321 | 322 | --- 323 | 324 | ### 选择字段 325 | 326 | #### ChoiceField 327 | 328 | #### MultipleChoiceField 329 | 330 |
331 |
332 |
333 |
334 | 335 | --- 336 | 337 | ### 文件上传字段 338 | 339 | #### FileField 340 | 341 | #### ImageField 342 | 343 |
344 |
345 |
346 |
347 | 348 | --- 349 | 350 | ### 复杂类型字段 351 | 352 | #### ListField 353 | 354 | #### DictField 355 | 356 | #### HStoreField 357 | 358 | #### JSONField 359 | 360 |
361 |
362 |
363 |
364 | 365 | --- 366 | 367 | ### 其他字段 368 | 369 | #### ReadOnlyField 370 | 371 | #### HiddenField 372 | 373 | #### ModelField 374 | 375 | #### SerializerMethodField 376 | 377 |
378 |
379 |
380 |
381 | 382 | --- 383 | 384 | ### 自定义字段 385 | 386 | #### 案例 387 | 388 |
389 |
390 |
391 |
392 | 393 | --- 394 | 395 | ### 第三方模块 396 | 397 | #### DRF Compound Fields 398 | 399 | #### DRF Extra Fields 400 | 401 | #### djangorestframework-recursive 402 | 403 | #### django-rest-framework-gis 404 | 405 | #### django-rest-framework-hstore 406 | 407 | --- 408 | 409 | 参考链接: 410 | 411 | - https://docs.djangoproject.com/en/2.0/ref/forms/api/#django.forms.Form.cleaned_data -------------------------------------------------------------------------------- /api/pagination.md: -------------------------------------------------------------------------------- 1 | # 分页 2 | 3 | Django提供了几个类,可以帮助您管理分页数据 - 也就是说,通过“上一页/下一页”链接分割多个页面的数据。 4 | 5 | -- Django文档 6 | 7 | REST框架包含对可定制分页样式的支持。这允许您修改多大的结果集被分成单独的数据页面。 8 | 9 | 分页API可以支持: 10 | 11 | 作为响应内容的一部分提供的分页链接。 12 | 分页链接包含在响应标题中,例如Content-Range或Link。 13 | 内置的样式当前都使用作为响应内容的一部分的链接。使用可浏览的API时,此样式更易于访问。 14 | 15 | 分页仅在您使用通用视图或视图集时自动执行。如果您使用的是常规APIView,则需要自己调用分页API以确保您返回分页响应。查看示例的```mixins.ListModelMixin```和```generics.GenericAPIView```类的源代码。 16 | 17 | 分页可以通过设置分页类来关闭None。 18 | 19 | 20 | ## 设置分页样式 21 | 22 | 分页样式可以使用DEFAULT_PAGINATION_CLASS和PAGE_SIZE设置键全局设置。例如,要使用内置的限制/偏移分页,你可以这样做: 23 | ``` 24 | REST_FRAMEWORK = { 25 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 26 | 'PAGE_SIZE': 100 27 | } 28 | ``` 29 | 请注意,您需要设置分页类和应使用的页面大小。双方```DEFAULT_PAGINATION_CLASS```并```PAGE_SIZE```都默认None。 30 | 31 | 您还可以使用该```pagination_class```属性在单个视图上设置分页类。通常,您希望在整个API中使用相同的分页样式,但您可能希望在每个视图基础上更改分页的各个方面,例如默认或最大页面大小。 32 | 33 | ## 修改分页样式 34 | 如果要修改分页样式的特定方面,则需要重写其中一个分页类,并设置要更改的属性。 35 | 36 | ``` 37 | class LargeResultsSetPagination(PageNumberPagination): 38 | page_size = 1000 39 | page_size_query_param = 'page_size' 40 | max_page_size = 10000 41 | 42 | class StandardResultsSetPagination(PageNumberPagination): 43 | page_size = 100 44 | page_size_query_param = 'page_size' 45 | max_page_size = 1000 46 | ``` 47 | 然后,您可以使用.pagination_class属性将新样式应用于视图: 48 | ``` 49 | class BillingRecordsView(generics.ListAPIView): 50 | queryset = Billing.objects.all() 51 | serializer_class = BillingRecordsSerializer 52 | pagination_class = LargeResultsSetPagination 53 | ``` 54 | 55 | 或者使用```DEFAULT_PAGINATION_CLASS```设置键全局应用样式。例如: 56 | 57 | ``` 58 | REST_FRAMEWORK = { 59 | 'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination' 60 | } 61 | ``` 62 | 63 | 64 | ------------------ 65 | 66 | ## API参考 67 | ### PageNumberPagination 68 | 69 | 此分页样式在请求查询参数中接受单个号码页码。 70 | 71 | 要求: 72 | 73 | ``` 74 | GET https://api.example.org/accounts/?page=4 75 | ``` 76 | 77 | 回应: 78 | 79 | ``` 80 | HTTP 200 OK 81 | { 82 | "count": 1023 83 | "next": "https://api.example.org/accounts/?page=5", 84 | "previous": "https://api.example.org/accounts/?page=3", 85 | "results": [ 86 | … 87 | ] 88 | } 89 | ``` 90 | 91 | #### 建立 92 | 要```PageNumberPagination```全局启用样式,请使用以下配置,并PAGE_SIZE根据需要进行设置: 93 | 94 | ``` 95 | REST_FRAMEWORK = { 96 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 97 | 'PAGE_SIZE': 100 98 | } 99 | ``` 100 | 101 | 在```GenericAPIView```子类上,您还可以将该```pagination_class```属性设置```PageNumberPagination```为按照每个视图进行选择。 102 | 103 | 组态 104 | 所述```PageNumberPagination```类包括多个可重写修改分页样式属性。 105 | 106 | 要设置这些属性,您应该重写PageNumberPagination该类,然后像上面那样启用您的自定义分页类。 107 | 108 | ```django_paginator_class``` - 要使用的Django Paginator类。默认值是```django.core.paginator.Paginator```,对大多数用例来说应该没问题。 109 | ```page_size``` - 指示页面大小的数值。如果设置,则会覆盖PAGE_SIZE设置。默认值与PAGE_SIZE设置键相同。 110 | ```page_query_param``` - 一个字符串值,指示用于分页控件的查询参数的名称。 111 | ```page_size_query_param``` - 如果设置,这是一个字符串值,指示查询参数的名称,允许客户端根据每个请求设置页面大小。默认为None,表示客户端可能无法控制所请求的页面大小。 112 | ```max_page_size``` - 如果设置,这是一个数字值,表示允许的最大页面大小。该属性仅在```page_size_query_param```设置时才有效。 113 | ```last_page_strings```- 字符串值的列表或元组值,指示可用于```page_query_param```请求集合中最终页面的值。默认为('last',) 114 | ```template``` - 在可浏览API中呈现分页控件时使用的模板的名称。可能会被覆盖以修改渲染样式,或者设置为None完全禁用HTML分页控件。默认为```"rest_framework/pagination/numbers.html"```。 115 | 116 | 117 | ------- 118 | 119 | ### LimitOffsetPagination 120 | 这种分页样式反映了查找多个数据库记录时使用的语法。客户端包含“限制”和“偏移量”查询参数。该限制表示要返回的项目的最大数量,并且等同page_size于其他样式。偏移量指示查询的起始位置与完整的未分类项目集的关系。 121 | 122 | 要求: 123 | 124 | ``` 125 | GET https://api.example.org/accounts/?limit=100&offset=400 126 | ``` 127 | 128 | 回应: 129 | 130 | ``` 131 | HTTP 200 OK 132 | { 133 | "count": 1023 134 | "next": "https://api.example.org/accounts/?limit=100&offset=500", 135 | "previous": "https://api.example.org/accounts/?limit=100&offset=300", 136 | "results": [ 137 | … 138 | ] 139 | } 140 | ``` 141 | 142 | #### 建立 143 | 要```LimitOffsetPagination```全局启用样式,请使用以下配置: 144 | 145 | ``` 146 | REST_FRAMEWORK = { 147 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination' 148 | } 149 | ``` 150 | 或者,您也可以设置一个PAGE_SIZE密钥。如果该PAGE_SIZE参数也被使用,则limit查询参数将是可选的,并且可以被客户端省略。 151 | 152 | 在```GenericAPIView```子类上,您还可以将该```pagination_class```属性设置```LimitOffsetPagination```为按照每个视图进行选择。 153 | 154 | 组态 155 | 所述```LimitOffsetPagination```类包括多个可重写修改分页样式属性。 156 | 157 | 要设置这些属性,您应该重写```LimitOffsetPagination```该类,然后像上面那样启用您的自定义分页类。 158 | 159 | ```default_limit``` - 一个数字值,指示客户端在查询参数中未提供的限制。默认值与```PAGE_SIZE```设置键相同。 160 | ```limit_query_param``` - 一个字符串值,指示“限制”查询参数的名称。默认为'limit'。 161 | ```offset_query_param - 一个字符串值,指示“偏移量”查询参数的名称。默认为'offset'。 162 | ```max_limit``` - 如果设置,这是一个数字值,表示客户可能要求的最大允许限制。默认为None。 163 | ```template``` - 在可浏览API中呈现分页控件时使用的模板的名称。可能会被覆盖以修改渲染样式,或者设置为None完全禁用HTML分页控件。默认为```"rest_framework/pagination/numbers.html"```。 164 | 165 | 166 | ----- 167 | 168 | 169 | ### CursorPagination 170 | 基于光标的分页提供了一个不透明的“光标”指示器,客户端可以使用该指示器来遍历结果集。此分页样式仅提供前向和反向控件,并且不允许客户端导航到任意位置。 171 | 172 | 基于游标的分页需要在结果集中存在唯一的,不变的项目顺序。这种排序通常可能是记录上的创建时间戳,因为这提供了一致的排序。 173 | 174 | 基于光标的分页比其他方案更复杂。它还要求结果集呈现固定顺序,并且不允许客户端任意索引结果集。但它确实提供了以下好处: 175 | 176 | 提供一致的分页视图。正确使用```CursorPagination```时,即使在分页过程中,其他客户端正在插入新项目时,客户端也不会在查看记录时看到相同的项目两次。 177 | 支持使用非常大的数据集。使用极大数据集分页时,使用基于偏移量的分页样式可能会变得效率低下或无法使用。基于游标的分页方案具有固定时间属性,并且不会随着数据集大小的增加而减慢。 178 | 细节和限制 179 | 正确使用基于光标的分页需要稍微注意细节。您需要考虑您希望将该方案应用于何种顺序。默认是按顺序排列"-created"。这假设在模型实例上必须有一个“创建的”时间戳字段,并且会呈现一个“时间轴”样式分页视图,其中最近添加的项目是第一个。 180 | 181 | 您可以通过重写'ordering'分页类上的属性或者使用```OrderingFilter```过滤器类来修改排序```CursorPagination```。您与```OrderingFilter```一起使用时,应强烈考虑限制用户可以自定义的字段。 182 | 183 | 正确使用游标分页应该有一个满足以下条件的排序字段: 184 | 185 | 在创建时应该是一个不变的值,例如时间戳,slu,或其他只设置一次的字段。 186 | 应该是独特的,或几乎独一无二的。毫秒精度时间戳就是一个很好的例子。这种游标分页的实现使用了一种智能的“位置加偏移”风格,允许它正确地支持非严格唯一的值作为排序。 187 | 应该是可以强制为字符串的非空值。 188 | 不应该是一个浮动。精度错误很容易导致错误的结果。提示:改用小数。(如果您已经有一个浮点型字段并且必须对其进行分页,则可以在此处找到一个使用小数来限制精度的 示例CursorPagination子类。) 189 | 该字段应该有一个数据库索引。 190 | 使用不满足这些约束条件的排序字段通常仍然有效,但是您将失去光标分页的一些好处。 191 | 192 | 有关用于光标分页的实现的更多技术细节,“为Disqus API构建游标”博客文章对基本方法进行了很好的概述。 193 | 194 | #### 建立 195 | 196 | 要```CursorPagination```全局启用样式,请使用以下配置,```PAGE_SIZE```根据需要修改: 197 | ``` 198 | REST_FRAMEWORK = { 199 | 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination', 200 | 'PAGE_SIZE': 100 201 | } 202 | ``` 203 | 204 | 在```GenericAPIView```子类上,您还可以将该```pagination_class```属性设置```CursorPagination```为按照每个视图进行选择。 205 | 206 | 207 | #### 组态 208 | 所述```CursorPagination```类包括多个可重写修改分页样式属性。 209 | 210 | 要设置这些属性,您应该重写```CursorPagination```该类,然后像上面那样启用您的自定义分页类。 211 | 212 | ```page_size```=表示页面大小的数值。如果设置,则会覆盖PAGE_SIZE设置。默认值与PAGE_SIZE设置键相同。 213 | ```cursor_query_param=```一个字符串值,指示“游标”查询参数的名称。默认为```'cursor'```。 214 | ```ordering```=这应该是一个字符串或字符串列表,指示将应用基于光标的分页的字段。例如:```ordering = 'slug'```。默认为-created。该值也可以通过OrderingFilter在视图上使用来覆盖。 215 | ```template```=在可浏览API中呈现分页控件时使用的模板的名称。可能会被覆盖以修改渲染样式,或者设置为None完全禁用HTML分页控件。默认为```"rest_framework/pagination/previous_and_next.html"```。 216 | 217 | ---- 218 | 219 | ## 自定义分页样式 220 | 要创建自定义分页序列化程序类,您应该继承```pagination.BasePagination```并覆盖```paginate_queryset(self, queryset, request, view=None)和get_paginated_response(self, data)```方法: 221 | 222 | 该```paginate_queryset```方法传递给初始查询集,并返回一个只包含请求页面中数据的可迭代对象。 223 | 该```get_paginated_response```方法传递序列化的页面数据并返回一个Response实例。 224 | 请注意,该```paginate_queryset```方法可能会在分页实例上设置状态,稍后可能会使用该```get_paginated_response```方法。 225 | 226 | #### 例 227 | 假设我们想用一个修改后的格式替换默认的分页输出样式,该样式包含嵌套的“链接”键下的下一个和前一个链接。我们可以像这样指定一个自定义分页类: 228 | 229 | ``` 230 | class CustomPagination(pagination.PageNumberPagination): 231 | def get_paginated_response(self, data): 232 | return Response({ 233 | 'links': { 234 | 'next': self.get_next_link(), 235 | 'previous': self.get_previous_link() 236 | }, 237 | 'count': self.page.paginator.count, 238 | 'results': data 239 | }) 240 | ``` 241 | 242 | 然后我们需要在我们的配置中设置自定义类: 243 | 244 | ``` 245 | REST_FRAMEWORK = { 246 | 'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.CustomPagination', 247 | 'PAGE_SIZE': 100 248 | } 249 | ``` 250 | 251 | 请注意,如果您关心如何在可浏览的API中响应中显示键的顺序,则可以选择OrderedDict在构建分页响应的主体时使用该顺序,但这是可选的。 252 | 253 | ### 使用您的自定义分页类 254 | 255 | 要默认使用您的自定义分页类,请使用以下DEFAULT_PAGINATION_CLASS设置: 256 | 257 | ``` 258 | REST_FRAMEWORK = { 259 | 'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.LinkHeaderPagination', 260 | 'PAGE_SIZE': 100 261 | } 262 | ``` 263 | 列表端点的API响应现在将包含一个Link标题,而不是将分页链接包含为响应正文的一部分,例如: 264 | 265 | ### 分页和模式 266 | 通过实现一种```get_schema_fields()```方法,您还可以将分页控件提供给REST框架提供的模式自动生成。此方法应具有以下签名: 267 | ``` 268 | get_schema_fields(self, view) 269 | ``` 270 | 该方法应该返回一个```coreapi.Field```实例列表。 271 | 272 | 链接标题 273 | 274 | 自定义分页样式,使用'链接'标题' 275 | 276 | 277 | --- 278 | ## HTML分页控件 279 | 默认情况下,使用分页类将导致HTML分页控件显示在可浏览的API中。有两种内置显示样式。在```PageNumberPagination```与```LimitOffsetPagination```类显示页码与前一个和下一个列表控件。本```CursorPagination```类显示一个简单的风格,只显示前面和后面的控制。 280 | 281 | ### 自定义控件 282 | 您可以覆盖呈现HTML分页控件的模板。这两种内置式样是: 283 | 284 | ``` 285 | rest_framework/pagination/numbers.html 286 | rest_framework/pagination/previous_and_next.html 287 | ``` 288 | 在全局模板目录中提供具有这些路径的模板将覆盖相关分页类的默认呈现。 289 | 290 | 或者,您可以通过对现有类进行子类化来完全禁用HTML分页控件,并将其设置template = None为该类的属性。然后您需要配置您的DEFAULT_PAGINATION_CLASS设置键以将您的自定义类用作默认分页样式。 291 | 292 | ### 低级API 293 | 用于确定分页类是否应显示控件的低级API作为display_page_controls分页实例上的属性公开。如果自定义分页类需要显示HTML分页控件,则应True在该paginate_queryset方法中设置自定义分页类。 294 | 295 | 该```.to_html()```和```.get_html_context()```方法也可以在自定义分页类,以进一步定制控件的呈现方式覆盖。 296 | 297 | 298 | --- 299 | 300 | ## 第三方软件包 301 | 以下第三方包也可用。 302 | 303 | ### DRF-扩展 304 | 该[DRF-extensions](https://chibisov.github.io/drf-extensions/docs/)软件包包含一个```PaginateByMaxMixinmixin```类,允许您的API客户端指定?page_size=max获取允许的最大页面大小。 305 | 306 | ### DRF-代理分页 307 | 该[drf-proxy-pagination](https://github.com/tuffnatty/drf-proxy-pagination)软件包包含一个```ProxyPagination```允许使用查询参数选择分页类的类。 308 | 309 | ### 链路报头分页 310 | 该[django-rest-framework-link-header-pagination](https://github.com/tbeadle/django-rest-framework-link-header-pagination)软件包包含一个LinkHeaderPagination类,该类通过Github的开发人员文档中描述的HTTP Link头提供分页。 311 | 312 | -------------------------------------------------------------------------------- /api/parsers.md: -------------------------------------------------------------------------------- 1 | ## 解析器Parsers 2 | 3 | > 机器交互式Web服务更倾向于使用更多的结构化格式来发送数据,而不是简单的表单格式。这是因为他们会发送比表单更复杂的数据。 4 | —— Malcom Tredinnick 5 | 6 | REST framework包含了许多内置的解析器类,允许您使用各种```Media Types```来接收请求。当然,也可以支持定义你自己的解析器,这使得你可以灵活的设计你的API所接收的```Media Types```。 7 | 8 | #### 解析器是如何确定的 9 | 10 | 视图中有效的解析器总是被定义为一个包含类的列表。当```request.data```中的数据被访问的时候,REST framework将会检查传进来的请求中的HTTP 头中的```Content-Type```来确定使用哪种解析器来解析请求数据(```request data```)。 11 | 12 | --- 13 | 14 | 注意: 在开发HTTP 客户端应用程序的时候,应该始终记住在请求中设置```Content-Type```头。 15 | 16 | 如果你不设置content type,大多数客户端默认将会使用```'application/x-www-form-urlencoded'```, 这可能不是你想要的。 17 | 18 | 举个例子,如果你要用```JQuery```的```.ajax()```方法发送一个使用JSON编码的数据,那么你应该始终设置```contentType: 'application/json'```。 19 | 20 | --- 21 | 22 | #### 设置解析器 23 | 24 | 默认情况下解析器的设置可能是通过```DEFAULT_PARSER_CLASSES```设置的全局的。比如,下面的设置将仅仅允许```JSON```的请求,代替了默认的```JSON or form data```的设置。 25 | 26 | ```python 27 | REST_FRAMEWORK = { 28 | 'DEFAULT_PARSER_CLASSES': ( 29 | 'rest_framework.parsers.JSONParser', 30 | ) 31 | } 32 | ``` 33 | 34 | 你还可以为基于```APIView```的单个视图或者视图集合(```viewset```)设置解析器: 35 | 36 | ```python 37 | from rest_framework.parsers import JSONParser 38 | from rest_framework.response import Response 39 | from rest_framework.views import APIView 40 | 41 | class ExampleView(APIView): 42 | """ 43 | A view that can accept POST requests with JSON content. 44 | """ 45 | parser_classes = (JSONParser,) 46 | 47 | def post(self, request, format=None): 48 | return Response({'received data': request.data}) 49 | ``` 50 | 51 | 或者为基于```@api_view```装饰器的函数视图设置解析器: 52 | 53 | ```python 54 | from rest_framework.decorators import api_view 55 | from rest_framework.decorators import parser_classes 56 | from rest_framework.parsers import JSONParser 57 | 58 | @api_view(['POST']) 59 | @parser_classes((JSONParser,)) 60 | def example_view(request, format=None): 61 | """ 62 | A view that can accept POST requests with JSON content. 63 | """ 64 | return Response({'received data': request.data}) 65 | ``` 66 | 67 | --- 68 | 69 |
70 |
71 |
72 |
73 | 74 | ### API 参考 75 | 76 | 关于解析器的源码位置: ```rest_framework.parsers``` 77 | 78 | #### ```JSONParser```解析器 79 | 80 | 解析请求内容是```JSON```格式的数据 81 | 82 | HTTP请求中的```.media_type```为 ```application/json``` 83 | 84 | #### ```FormParser```解析器 85 | 86 | 解析请求内容是HTML表单的数据。```request.data```的内容将被```QueryDict```填充。 87 | 88 | 源代码: 89 | 90 | ```python 91 | class FormParser(BaseParser): 92 | """ 93 | Parser for form data. 94 | """ 95 | media_type = 'application/x-www-form-urlencoded' 96 | 97 | def parse(self, stream, media_type=None, parser_context=None): 98 | """ 99 | Parses the incoming bytestream as a URL encoded form, 100 | and returns the resulting QueryDict. 101 | """ 102 | parser_context = parser_context or {} 103 | encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 104 | data = QueryDict(stream.read(), encoding=encoding) ### 注意这里返回的 QueryDict 105 | return data 106 | ``` 107 | 108 | 通常情况下,会将```FormParser```和```MultiPartParser```来一起使用,以便完美的支持HTML表单数据。 109 | 110 | HTTP请求中的```.media_type```为 ```application/x-www-form-urlencoded``` 111 | 112 | #### ```MultiPartParser```解析器 113 | 114 | 解析支持文件上传的多部分HTML表单内容。 115 | 116 | 通常情况下,会将```FormParser```和```MultiPartParser```来一起使用,以便完美的支持HTML表单数据。 117 | 118 | HTTP请求中的```.media_type```为 ```multipart/form-data``` 119 | 120 | #### ```FileUploadParser```解析器 121 | 122 | 解析原始文件上传的内容。```request.data```的属性将是一个包含上传文件的单个关键字```'file'```的字典。 123 | 124 | 相关源代码: 125 | 126 | ```python 127 | class FileUploadParser(BaseParser): 128 | """ 129 | Parser for file upload data. 130 | """ 131 | media_type = '*/*' 132 | errors = { 133 | 'unhandled': 'FileUpload parse error - none of upload handlers can handle the stream', 134 | 'no_filename': 'Missing filename. Request should include a Content-Disposition header with a filename parameter.', 135 | } 136 | 137 | def parse(self, stream, media_type=None, parser_context=None): 138 | """ 139 | Treats the incoming bytestream as a raw file upload and returns 140 | a `DataAndFiles` object. 141 | 142 | `.data` will be None (we expect request body to be a file content). 143 | `.files` will be a `QueryDict` containing one 'file' element. 144 | """ 145 | parser_context = parser_context or {} 146 | request = parser_context['request'] 147 | encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 148 | meta = request.META 149 | upload_handlers = request.upload_handlers 150 | filename = self.get_filename(stream, media_type, parser_context) 151 | 152 | if not filename: 153 | raise ParseError(self.errors['no_filename']) 154 | 155 | # Note that this code is extracted from Django's handling of 156 | # file uploads in MultiPartParser. 157 | content_type = meta.get('HTTP_CONTENT_TYPE', 158 | meta.get('CONTENT_TYPE', '')) 159 | try: 160 | content_length = int(meta.get('HTTP_CONTENT_LENGTH', 161 | meta.get('CONTENT_LENGTH', 0))) 162 | except (ValueError, TypeError): 163 | content_length = None 164 | 165 | # See if the handler will want to take care of the parsing. 166 | for handler in upload_handlers: 167 | result = handler.handle_raw_input(stream, 168 | meta, 169 | content_length, 170 | None, 171 | encoding) 172 | if result is not None: 173 | return DataAndFiles({}, {'file': result[1]}) ### 看这里返回的字典 174 | 175 | # This is the standard case. 176 | possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] 177 | chunk_size = min([2 ** 31 - 4] + possible_sizes) 178 | chunks = ChunkIter(stream, chunk_size) 179 | counters = [0] * len(upload_handlers) 180 | 181 | for index, handler in enumerate(upload_handlers): 182 | try: 183 | handler.new_file(None, filename, content_type, 184 | content_length, encoding) 185 | except StopFutureHandlers: 186 | upload_handlers = upload_handlers[:index + 1] 187 | break 188 | 189 | for chunk in chunks: 190 | for index, handler in enumerate(upload_handlers): 191 | chunk_length = len(chunk) 192 | chunk = handler.receive_data_chunk(chunk, counters[index]) 193 | counters[index] += chunk_length 194 | if chunk is None: 195 | break 196 | 197 | for index, handler in enumerate(upload_handlers): 198 | file_obj = handler.file_complete(counters[index]) 199 | if file_obj is not None: 200 | return DataAndFiles({}, {'file': file_obj}) ### 看这里返回的字典 201 | 202 | raise ParseError(self.errors['unhandled']) 203 | ``` 204 | 205 | 如果在URL关键字参数中有```filename```,调用了相关的视图,视图调用了```FileUploadParser```解析器,那么这个关键字参数将会用作文件名。 206 | 207 | 如果并没有URL关键字参数,那么客户端必须在HTTP头中设置文件名。比如这样: ```Content-Disposition: attachment; filename=upload.jpg```。 208 | 209 | HTTP请求中的```.media_type```为 ```*/*``` 210 | 211 | 注意: 212 | 213 | - 解析器```FileUploadParser```应该用于本地的客户端上传原始的数据的请求。如果是基于Web端的上传文件,或者是为本地客户端支持```multipart upload```,那么解析器应该用```MultiPartParser```。 214 | - 由于```FileUploadParser```可以匹配任何类型的Media类型,所以通常这个解析器可以设置在APIView上。 215 | - 解析器```FileUploadParser```使用的是Django的```request.upload_handlers```。 216 | 217 | 基本使用案例: 218 | 219 | ```python 220 | # views.py 221 | class FileUploadView(views.APIView): 222 | parser_classes = (FileUploadParser,) 223 | 224 | def put(self, request, filename, format=None): 225 | file_obj = request.data['file'] 226 | # ... 227 | # do some stuff with uploaded file 228 | # ... 229 | return Response(status=204) 230 | 231 | # urls.py 232 | urlpatterns = [ 233 | # ... 234 | url(r'^upload/(?P[^/]+)$', FileUploadView.as_view()) 235 | ] 236 | ``` 237 | 238 | --- 239 | 240 |
241 |
242 |
243 |
244 | 245 | ### 自定义 解析器 246 | 247 | 要想实现自定义的解析器,你应该继承```BaseParser```类,并且要设置```.media_type```属性,还要实现```.parse(self, stream, media_type, parser_context)```方法。 248 | 249 | 这个方法返回的数据,被填充在```request.data```中。 250 | 251 | 应该给```.parser()```方法中传递的参数有: 252 | 253 | - ```stream```: 一个表示request body的流式对象。 254 | - ```media_type```: 可选的,如果设置了,那么就代表进入请求的内容的Media类型。依赖HTTP请求头中的```Content-Type:```。 255 | - ```parser_context```: 可选的。如果提供,这个参数将是一个字典,其中包含可能需要分析请求内容的任何附加上下文。默认包含以下key, ```view```, ```request```, ```args```和```kwargs```。 256 | 257 | #### ```Example``` 258 | 259 | 下面是一个普通文本解析器的一个实例,最后会将文本的内容字符串作为数据填充到```request.data```中: 260 | 261 | ```python 262 | class PlainTextParser(BaseParser): 263 | """ 264 | Plain text parser. 265 | """ 266 | media_type = 'text/plain' 267 | 268 | def parse(self, stream, media_type=None, parser_context=None): 269 | """ 270 | Simply return a string representing the body of the request. 271 | """ 272 | return stream.read() 273 | ``` 274 | 275 | --- 276 | 277 |
278 |
279 |
280 |
281 | 282 | ### 第三方包 283 | 284 | 下面是一些可用的第三方包。 285 | 286 | #### YAML 287 | 288 | [REST framework YAML](https://jpadilla.github.io/django-rest-framework-yaml/)提供了YAML的解析和渲染支持。之前在REST framework有YAML的支持,现在使用第三方的包来替代。 289 | 290 | 使用pip安装: 291 | 292 | ```shell 293 | $ pip install djangorestframework-yaml 294 | ``` 295 | 296 | 修改你的REST framework的设置: 297 | 298 | ```python 299 | REST_FRAMEWORK = { 300 | 'DEFAULT_PARSER_CLASSES': ( 301 | 'rest_framework_yaml.parsers.YAMLParser', 302 | ), 303 | 'DEFAULT_RENDERER_CLASSES': ( 304 | 'rest_framework_yaml.renderers.YAMLRenderer', 305 | ), 306 | } 307 | ``` 308 | 309 | #### XML 310 | 311 | [REST Framework XML](https://jpadilla.github.io/django-rest-framework-xml/)提供了一个简单的非正式的XML格式。 312 | 313 | 使用pip安装: 314 | 315 | ```shell 316 | $ pip install djangorestframework-xml 317 | ``` 318 | 319 | 修改REST framework的设置: 320 | 321 | ```python 322 | REST_FRAMEWORK = { 323 | 'DEFAULT_PARSER_CLASSES': ( 324 | 'rest_framework_xml.parsers.XMLParser', 325 | ), 326 | 'DEFAULT_RENDERER_CLASSES': ( 327 | 'rest_framework_xml.renderers.XMLRenderer', 328 | ), 329 | } 330 | ``` 331 | 332 | #### MessagePack 333 | 334 | MessagePack 是一个快速的、高效的 二进制序列化格式。 [djangorestframework-msgpack](https://github.com/juanriaza/django-rest-framework-msgpack)包为REST framework提供了```MessagePack```的渲染器和解析器。 335 | 336 | #### 驼峰 JSON 337 | 338 | [djangorestframework-camel-case](https://github.com/vbabiy/djangorestframework-camel-case)包为REST framework提供了驼峰JSON的渲染器和解析器。这意味着你可以在序列化程序中使用Python风格的下划线语法,而最后导出的时候使用JavaScript风格的驼峰语法。 339 | 340 | --- 341 | 342 | 参考链接: 343 | 344 | - https://jpadilla.github.io/django-rest-framework-yaml/ 345 | - https://jpadilla.github.io/django-rest-framework-xml/ 346 | - https://github.com/juanriaza/django-rest-framework-msgpack 347 | - https://github.com/vbabiy/djangorestframework-camel-case -------------------------------------------------------------------------------- /api/viewsets.md: -------------------------------------------------------------------------------- 1 | ## 视图组Viewsets 2 | 3 | > 路由器决定使用哪个控制器处理请求后,控制器负责解析请求,生成相应的输出。 4 | —— Ruby on Rails Documentation 5 | 6 | 点击此处[查看文档](http://guides.rubyonrails.org/action_controller_overview.html)。 7 | 8 | Django REST framework允许您将一组相关视图的逻辑组合到一个名为```ViewSet```的类中。在其他框架中,您可能会发现概念上类似的实现,名为"Resources"或"Controllers"。 9 | 10 | 一个```ViewSet```类,只是一个简单的基于类的视图,它不提供任何处理方法,比如```.get()```或者```.post()```,而是提供了动作方法来代替,比如```.list()```和```.create()```。 11 | 12 | 一个```ViewSet```类,会使用```.as_view()```方法将处理方法和动作方法绑定。 13 | 14 | 相关的源代码位置在: ```rest_framework.viewsets.ViewSetMixin``` 15 | 16 | 通常,不是在urlconf中通过viewset来显示的注册你的视图,而是将viewset注册到router类中,这将会自动的为您确定需要的urlconf。 17 | 18 | 19 | #### 例子 20 | 21 | 让我们来定义一个简单的viewset, 主要目的是可以列出或者获取系统中的用户: 22 | 23 | ```python 24 | from django.contrib.auth.models import User 25 | from django.shortcuts import get_object_or_404 26 | from myapps.serializers import UserSerializer 27 | from rest_framework import viewsets 28 | from rest_framework.response import Response 29 | 30 | class UserViewSet(viewsets.ViewSet): 31 | """ 32 | A simple ViewSet for listing or retrieving users. 33 | """ 34 | def list(self, request): 35 | queryset = User.objects.all() 36 | serializer = UserSerializer(queryset, many=True) 37 | return Response(serializer.data) 38 | 39 | def retrieve(self, request, pk=None): 40 | queryset = User.objects.all() 41 | user = get_object_or_404(queryset, pk=pk) 42 | serializer = UserSerializer(user) 43 | return Response(serializer.data) 44 | ``` 45 | 46 | 如果有必要,你可以绑定这个viewset到两个单独的视图里面,比如这样: 47 | 48 | ```python 49 | user_list = UserViewSet.as_view({'get': 'list'}) 50 | user_detail = UserViewSet.as_view({'get': 'retrieve'}) 51 | ``` 52 | 53 | 通常我们不会这样做,而是注册viewset到router里面,这将会自动生成urlconf: 54 | 55 | ```python 56 | from myapp.views import UserViewSet 57 | from rest_framework.routers import DefaultRouter 58 | 59 | router = DefaultRouter() 60 | router.register(r'users', UserViewSet, base_name='user') 61 | urlpatterns = router.urls 62 | ``` 63 | 64 | 如果你不是编写你自己的```viewsets```,而是你要使用一些现有的基类提供的一组默认功能,你完全可以这样做: 65 | 66 | ```python 67 | class UserViewSet(viewsets.ModelViewSet): 68 | """ 69 | A viewset for viewing and editing user instances. 70 | """ 71 | serializer_class = UserSerializer 72 | queryset = User.objects.all() 73 | ``` 74 | 75 | 使用```ViewSet```类相比使用```View```类,有两个主要的优点: 76 | 77 | - 重复的逻辑可以合并到一个类中。在上面的例子中,我们只需要制定一次```queryset```,就可以在多个视图中使用了。 78 | - 通过使用routers, 我们不再需要自己处理URL配置。 79 | 80 | 这两个都有一个这种。使用常规的视图和URL配置,会更加清晰明确,并且更容易控制。但是如果想要快速的运行,使用ViewSets; 或者您有大量的API并且希望始终有一致的URL配置,那么ViewSets就会很方便。 81 | 82 | #### ViewSet 包含的动作 83 | 84 | REST framework中默认的路由器将会为 create/retrieve/update/destroy 风格的动作提供一系列的路由,如下: 85 | 86 | ```python 87 | class UserViewSet(viewsets.ViewSet): 88 | """ 89 | 这是一个viewset 的演示, 90 | 标准动作 将会 被 路由器处理. 91 | 92 | 如果你使用格式后缀, 93 | 请在每个动作中确保, 94 | 包含了`format=None`关键字参数. 95 | """ 96 | 97 | def list(self, request): 98 | pass 99 | 100 | def create(self, request): 101 | pass 102 | 103 | def retrieve(self, request, pk=None): 104 | pass 105 | 106 | def update(self, request, pk=None): 107 | pass 108 | 109 | def partial_update(self, request, pk=None): 110 | pass 111 | 112 | def destroy(self, request, pk=None): 113 | pass 114 | ``` 115 | 116 | 在调度(```dispatch```)动作的期间,当前的动作可以通过```.action```属性获取。你可以通过检查```.action```来调整当前动作的行为。 117 | 118 | 比如,你可以在限制权限为处理```list```动作之外的其他: 119 | 120 | ```python 121 | def get_permissions(self): 122 | """ 123 | Instantiates and returns the list of permissions that this view requires. 124 | """ 125 | if self.action == 'list': 126 | permission_classes = [IsAuthenticated] 127 | else: 128 | permission_classes = [IsAdmin] 129 | return [permission() for permission in permission_classes] 130 | ``` 131 | 132 | #### 让额外的动作 使用路由 133 | 134 | 如果你又额外的方法需要被路由,那么你可以使用```@detail_route```或者```@list_route```装饰器,来标记这些方法可以被路由。 135 | 136 | 装饰器```@detail_route```在URL中匹配```pk```并且适用于只需要单个实例的方法。 137 | 138 | 装饰器```@list_route```适用于需要操作一组实例的方法。 139 | 140 | 比如: 141 | 142 | ```python 143 | from django.contrib.auth.models import User 144 | from rest_framework import status 145 | from rest_framework import viewsets 146 | from rest_framework.decorators import detail_route, list_route 147 | from rest_framework.response import Response 148 | from myapp.serializers import UserSerializer, PasswordSerializer 149 | 150 | class UserViewSet(viewsets.ModelViewSet): 151 | """ 152 | A viewset that provides the standard actions 153 | """ 154 | queryset = User.objects.all() 155 | serializer_class = UserSerializer 156 | 157 | @detail_route(methods=['post']) 158 | def set_password(self, request, pk=None): 159 | user = self.get_object() 160 | serializer = PasswordSerializer(data=request.data) 161 | if serializer.is_valid(): 162 | user.set_password(serializer.data['password']) 163 | user.save() 164 | return Response({'status': 'password set'}) 165 | else: 166 | return Response(serializer.errors, 167 | status=status.HTTP_400_BAD_REQUEST) 168 | 169 | @list_route() 170 | def recent_users(self, request): 171 | recent_users = User.objects.all().order('-last_login') 172 | 173 | page = self.paginate_queryset(recent_users) 174 | if page is not None: 175 | serializer = self.get_serializer(page, many=True) 176 | return self.get_paginated_response(serializer.data) 177 | 178 | serializer = self.get_serializer(recent_users, many=True) 179 | return Response(serializer.data) 180 | ``` 181 | 182 | 这些装饰器还可以为仅仅路由到的视图上设置一些额外的参数。比如: 183 | 184 | ```python 185 | @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf]) 186 | def set_password(self, request, pk=None): 187 | ... 188 | ``` 189 | 190 | 这些装饰器,默认情况下是路由```GET```请求,但是也可以设置接收其他HTTP方法,使用```methods```参数。比如: 191 | 192 | ```python 193 | @detail_route(methods=['post', 'delete']) 194 | def unset_password(self, request, pk=None): 195 | ... 196 | ``` 197 | 198 | 这两个新的动作将会在URL```^users/{pk}/set_password/$```和```^users/{pk}/unset_password/$```中可用。 199 | 200 | #### 解析URL中的动作 201 | 202 | 如果你需要获取一个带有动作的URL,使用```.reverse_action()```方法。这是对```reverse()```的一个便捷封装,它会自动传递视图的```request```对象,并且将```url_name```预先加入```.basename```属性。 203 | 204 | 关于```reverser()```的文档,点击[查看](https://docs.djangoproject.com/en/2.0/ref/urlresolvers/)。 205 | 206 | 注意:```basename```是在```ViewSet```注册到路由的时候提供的。如果你不使用```router```,那么你必须提供```basename```参数到```.as_view()```方法。 207 | 208 | 使用前一个小结的例子: 209 | 210 | ```python 211 | >>> view.reverse_action('set-password', args=['1']) 212 | 'http://localhost:8000/api/users/1/set_password' 213 | ``` 214 | 215 | 参数```url_name```应该和装饰器```@list_route```和```@detail_route```相匹配。另外,这也可以被用于```reverse```默认的```list```和```detail```路由。 216 | 217 | --- 218 | 219 | ### API 参考 220 | 221 | 源码位置: ```rest_framework.viewsets``` 222 | 223 | #### ViewSet 类 224 | 225 | 类```ViewSet```是```APIView```的子类。所以你可以使用任何标准的属性,比如在你的ViewSet中使用```permission_classes```, ```authentication_classes```来控制API策略。 226 | 227 | 类```ViewSet```中并没有提供任何动作的实现。所以你如果要使用```ViewSet```,你需要自己定义动作的实现。 228 | 229 | REST framework 中的源代码如下: 230 | 231 | ```python 232 | class ViewSet(ViewSetMixin, views.APIView): 233 | """ 234 | The base ViewSet class does not provide any actions by default. 235 | """ 236 | pass 237 | ``` 238 | 239 | 在```ViewSetMixin```和```APIView```中都没有关于动作的实现。 240 | 241 | #### GenericViewSet 类 242 | 243 | 类```GenericViewSet```是```GenericAPIView```的子类,并且提供了默认的```get_object```和```get_queryset```方法,和其他通用的视图行为,但是不包括其他任何默认的动作。 244 | 245 | 如果要使用```GenericViewSet```类,你需要重写需要的mixin类,或者显示的定义动作的实现。 246 | 247 | REST framework中源码实现: 248 | 249 | ```python 250 | class GenericViewSet(ViewSetMixin, generics.GenericAPIView): 251 | """ 252 | The GenericViewSet class does not provide any actions by default, 253 | but does include the base set of generic view behavior, such as 254 | the `get_object` and `get_queryset` methods. 255 | """ 256 | pass 257 | ``` 258 | 259 | 父类```ViewSetMixin```和```GenericAPIView```没有任何动作的实现。 260 | 261 | #### ModelViewSet 类 262 | 263 | 类```ModelViewSet```是```GenericAPIView```的子类,并且包含了多种多样的动作的实现,通过多种多样的mixins类来提供各种行为。 264 | 265 | 类```ModelViewSet```提供的动作包括```.list()```, ```.retrieve()```, ```.create()```, ```.update()```, ```.partial_update()```和```.destroy()```。 266 | 267 | **比如** 268 | 269 | 因为```ModelViewSet```扩展了```GenericAPIView```,你至少要提供```queryset```和```serializer_class```属性。比如: 270 | 271 | ```python 272 | class AccountViewSet(viewsets.ModelViewSet): 273 | """ 274 | A simple ViewSet for viewing and editing accounts. 275 | """ 276 | queryset = Account.objects.all() 277 | serializer_class = AccountSerializer 278 | permission_classes = [IsAccountAdminOrReadOnly] 279 | ``` 280 | 281 | 注意,你可以使用```GenericAPIView```类提供的任何标准的属性和方法。比如,如果你要动态的确定查询结果集,你可能会这么做: 282 | 283 | ```python 284 | class AccountViewSet(viewsets.ModelViewSet): 285 | """ 286 | A simple ViewSet for viewing and editing the accounts 287 | associated with the user. 288 | """ 289 | serializer_class = AccountSerializer 290 | permission_classes = [IsAccountAdminOrReadOnly] 291 | 292 | def get_queryset(self): 293 | return self.request.user.accounts.all() 294 | ``` 295 | 296 | 注意,当从```ViewSet```中删除```queryset```属性之后,与之关联的[```router```](./routers.md)将无法自动获取你Model的```base_name```, 所以你必须在注册路由的时候要自己指定```base_name```。 297 | 298 | 提示: ```base_name```是根据```viewset```中的```queryset```属性来得到当前操作的模型的名称(小写) 299 | 300 | 还要注意,尽管这个类默认提供了完整的create/list/retrieve/update/destroy操作集,但还可以通过使用标准权限类来限制可用操作。 301 | 302 | #### ReadOnlyModelViewSet 类 303 | 304 | 类```ReadOnlyModelViewSet```类是```GenericAPIView```的之类,跟```ModelViewSet```类一样也包含多种动作的实现,但是不同的是,这里只包含只读的动作,```.list()```和```.retrieve()```。 305 | 306 | **例子** 307 | 308 | 和```ModelViewSet```一样, 你至少要提供```queryset```和```serializer_class```属性。 比如 : 309 | 310 | ```python 311 | class AccountViewSet(viewsets.ReadOnlyModelViewSet): 312 | """ 313 | A simple ViewSet for viewing accounts. 314 | """ 315 | queryset = Account.objects.all() 316 | serializer_class = AccountSerializer 317 | ``` 318 | 319 | 此外,同样的,你可以使用```GenericAPIView```提供的标准的属性和方法。 320 | 321 | --- 322 | 323 | ### 自定义ViewSet 基础类 324 | 325 | 你可能需要提供自定义的```ViewSet```类,并不像```ModelViewSet```类一样包含所有的动作, 或者自定义一些行为。 326 | 327 | #### 例子 328 | 329 | 创建一个基础的viewset类,来提供```create```,```list```和```retrieve```操作,继承```GenericViewSet```,并混合需要的动作: 330 | 331 | ```python 332 | from rest_framework import mixins 333 | 334 | class CreateListRetrieveViewSet(mixins.CreateModelMixin, 335 | mixins.ListModelMixin, 336 | mixins.RetrieveModelMixin, 337 | viewsets.GenericViewSet): 338 | """ 339 | A viewset that provides `retrieve`, `create`, and `list` actions. 340 | 341 | To use it, override the class and set the `.queryset` and 342 | `.serializer_class` attributes. 343 | """ 344 | pass 345 | ``` 346 | 347 | 通过创建你自己的基础的```ViewSet```类,你可以提供常见的行为,然后在你的API中重用他们。 348 | 349 | --- 350 | 351 | 参考链接: 352 | - http://guides.rubyonrails.org/action_controller_overview.html 353 | - https://docs.djangoproject.com/en/2.0/ref/urlresolvers/ -------------------------------------------------------------------------------- /tutorial/serialization.md: -------------------------------------------------------------------------------- 1 | # 序列化 2 | 3 | ## 介绍 4 | 5 | 本章将介绍,如何创建一个简单的支持代码高亮的WebAPI。在此过程中,我们将介绍构成REST framework框架的各个组件,并让您对所有组件的组合方式有一个全面的了解。 6 | 7 | 此章教程比较深入,所以请花一些时间仔细的阅读。 如果您只想快速浏览一下,则应该转到快速入门文档。 8 | 9 | --- 10 | 11 | 注意:本教程的代码可以在GitHub的[tomchristie/rest-framework-tutorial](https://github.com/encode/rest-framework-tutorial)库中找到。线上测试版,请点击[这里](https://restframework.herokuapp.com/)。 12 | 13 | --- 14 | 15 | ## 创建一个新的ENV环境 16 | 17 | 在我们做任何事情之前,我们都应使用```virtualenv```创建一个新的虚拟环境。这将保证我们的配置与我们正在进行的其他任何项目的配置保持良好的隔离。 18 | 19 | ```python 20 | virtualenv env 21 | source env/bin/activate 22 | ``` 23 | 24 | 现在我们进入了virtualenv环境,然后安装我们所需的依赖包: 25 | 26 | ```python 27 | pip install django 28 | pip install djangorestframework 29 | pip install pygments # 这个库用作语法高亮 30 | ``` 31 | 注意:要随时退出```virtualenv```环境,只需键入```deactivate```。要了解更多信息,请参阅[virtualenv文档](https://virtualenv.pypa.io/en/latest/index.html)。 32 | 33 | ## 开始 34 | 35 | 首先创建一个新的项目: 36 | 37 | ```python 38 | cd ~ 39 | django-admin.py startproject tutorial 40 | cd tutorial 41 | ``` 42 | 43 | 项目创建成功后,我们可以创建一个app, 我们将用它来创建一个简单的WebAPI。 44 | 45 | ```python 46 | python manage.py startapp snippets 47 | ``` 48 | 49 | 我们需要在```INSTALLED_APPS```添加新```snippets```app和``rest_framework``app。 50 | 51 | 编辑这个```tutorial/settings.py```文件: 52 | 53 | ```python 54 | INSTALLED_APPS = ( 55 | ... 56 | 'rest_framework', 57 | 'snippets.apps.SnippetsConfig', 58 | ) 59 | ``` 60 | 61 | ## 创建model 62 | 63 | 首先将先创建一个简单的```Snippet``` Model, 创建```snippets/models.py```文件。 64 | 65 | 注意:良好的编程实践是包括注释的。在官方代码版本库里可以找到注释,此处省略了注释,只需要关注代码即可。 66 | 67 | 68 | ```python 69 | from django.db import models 70 | from pygments.lexers import get_all_lexers 71 | from pygments.styles import get_all_styles 72 | 73 | LEXERS = [item for item in get_all_lexers() if item[1]] 74 | LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) 75 | STYLE_CHOICES = sorted((item, item) for item in get_all_styles()) 76 | 77 | 78 | class Snippet(models.Model): 79 | created = models.DateTimeField(auto_now_add=True) 80 | title = models.CharField(max_length=100, blank=True, default='') 81 | code = models.TextField() 82 | linenos = models.BooleanField(default=False) 83 | language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100) 84 | style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100) 85 | 86 | class Meta: 87 | ordering = ('created',) 88 | ``` 89 | 90 | 91 | 我们根据snippet model初始化,并且同步到数据库。 92 | 93 | ```python 94 | python manage.py makemigrations snippets 95 | python manage.py migrate 96 | ``` 97 | 98 | 99 | ## 创建一个Serializer 类 100 | 101 | 第一步,我们需要为WebAPI提供一个用于序列化和反序列化的方法,用来把```snippet```对象转换成```json```数据格式。我们可以通过声明与Django表单(forms)非常相似的序列化器(serializers)来实现这一点。 102 | 103 | 在```snippets```目录中创建一个文件```serializers.py```并添加以下内容: 104 | 105 | ```python 106 | from rest_framework import serializers 107 | from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES 108 | 109 | 110 | class SnippetSerializer(serializers.Serializer): 111 | id = serializers.IntegerField(read_only=True) 112 | title = serializers.CharField(required=False, allow_blank=True, max_length=100) 113 | code = serializers.CharField(style={'base_template': 'textarea.html'}) 114 | linenos = serializers.BooleanField(required=False) 115 | language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python') 116 | style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly') 117 | 118 | def create(self, validated_data): 119 | """ 120 | 如果数据合法有效,创建并返回一个新的`Snippet`实例。 121 | """ 122 | return Snippet.objects.create(**validated_data) 123 | 124 | def update(self, instance, validated_data): 125 | """ 126 | 如果数据合法有效,更新并返回一个已存在的`Snippet`段实例。 127 | """ 128 | instance.title = validated_data.get('title', instance.title) 129 | instance.code = validated_data.get('code', instance.code) 130 | instance.linenos = validated_data.get('linenos', instance.linenos) 131 | instance.language = validated_data.get('language', instance.language) 132 | instance.style = validated_data.get('style', instance.style) 133 | instance.save() 134 | return instance 135 | ``` 136 | 137 | 这个序列化器(```serializers```)类的第一部分定义了序列化/反序列化的字段。 138 | 139 | 该类中的```create()```和```update()```方法则定义了,当我们调用```serializer.save()```时如何创建或修改实例。 140 | 141 | 序列化器(```serializers```)类与Django Form类非常相似,并在各个字段中包含类似的数据校验标志,例如```required```,```max_length```和```default```。 142 | 143 | 上面的字段标志还可以控制在某些情况下应该如何展示序列化程序。例如当渲染为HTML时,```{'base_template': 'textarea.html'}```标志等同于Django Form类中的```widget=widgets.Textarea```。这对于控制如何显示可浏览的API特别有用,我们将在本教程后面看到的。 144 | 145 | 我们实际上也可以通过使用这个```ModelSerializer```类节省一些时间,我们在后面会看到,但现在我们应该先理解序列化器是如何定义的。 146 | 147 | ## 使用Serializers 148 | 149 | 在进一步讨论之前,我们将先熟悉使用序列化器(```Serializer```)类。让我们进入Django shell: 150 | 151 | ```python 152 | python manage.py shell 153 | ``` 154 | 155 | 我们导入一些模块, 然后创建一些实例: 156 | 157 | ```python 158 | from snippets.models import Snippet 159 | from snippets.serializers import SnippetSerializer 160 | from rest_framework.renderers import JSONRenderer 161 | from rest_framework.parsers import JSONParser 162 | 163 | snippet = Snippet(code='foo = "bar"\n') 164 | snippet.save() 165 | 166 | snippet = Snippet(code='print "hello, world"\n') 167 | snippet.save() 168 | ``` 169 | 170 | 现在我们已经有可操作的实例了。让我们来看看序列化其中一个实例: 171 | 172 | ```python 173 | serializer = SnippetSerializer(snippet) 174 | serializer.data 175 | # {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'} 176 | ``` 177 | 178 | 我们已经将```模型实例```转换为Python原生数据类型。我们将数据转换为json,完成序列化: 179 | 180 | ```python 181 | content = JSONRenderer().render(serializer.data) 182 | content 183 | # '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}' 184 | ``` 185 | 186 | 反序列化也是相似的。首先,我们将一个stream解析为Python原生数据类型: 187 | 188 | ```python 189 | from django.utils.six import BytesIO 190 | 191 | stream = BytesIO(content) 192 | data = JSONParser().parse(stream) 193 | ``` 194 | 195 | 然后,我们将这些原生数据类型转换为对象: 196 | 197 | ```python 198 | serializer = SnippetSerializer(data=data) 199 | serializer.is_valid() 200 | # True 201 | serializer.validated_data 202 | # OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]) 203 | serializer.save() 204 | # 205 | ``` 206 | 207 | 这个API和Django Form的工作方式是多么相似。当我们开始使用序列化器编写视图(```view```)的时候,相似性应该变得更加明显。 208 | 209 | 我们还可以序列化```queryset```(查询结果集),而不是模型实例。为了实现这一操作,我们只需要在序列化器参数中添加```many=True```标记。 210 | 211 | 212 | ```python 213 | serializer = SnippetSerializer(Snippet.objects.all(), many=True) 214 | serializer.data 215 | # [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])] 216 | ``` 217 | 218 | 219 | ## 使用ModelSerializers 220 | 221 | 我们```SnippetSerializer```类和```Snippet```Model中的大量信息都是重复的。如果我们能让代码更简洁一点,那就更好了! 222 | 223 | 就像Django提供```Form```类和```ModelForm```类一样,REST框架包含```Serializer```类和```ModelSerializer```类。 224 | 225 | 让我们用```ModelSerializer```类重构我们的序列化程序。打开```snippets/serializers.py```文件,并用下面的代码替换。 226 | 227 | ```python 228 | class SnippetSerializer(serializers.ModelSerializer): 229 | class Meta: 230 | model = Snippet 231 | fields = ('id', 'title', 'code', 'linenos', 'language', 'style') 232 | ``` 233 | 234 | 可以通过打印来检查序列化程序实例中的所有字段。使用```python manage.py shell```打开Django shell,然后尝试以下操作: 235 | 236 | ```python 237 | from snippets.serializers import SnippetSerializer 238 | serializer = SnippetSerializer() 239 | print(repr(serializer)) 240 | # SnippetSerializer(): 241 | # id = IntegerField(label='ID', read_only=True) 242 | # title = CharField(allow_blank=True, max_length=100, required=False) 243 | # code = CharField(style={'base_template': 'textarea.html'}) 244 | # linenos = BooleanField(required=False) 245 | # language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')... 246 | # style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')... 247 | ``` 248 | 249 | 记住```ModelSerializer```类没有做任何特别的事情,它们只是创建```serializer```的一个快捷方式: 250 | 251 | 1. 自动检查并确定字段。 252 | 2. 简单的实现了```create()```和```update()```方法。 253 | 254 | ## 使用Serializer来编写正常的Django视图 255 | 256 | 我们来看看如何使用新的Serializer类来编写一些API视图。目前,我们不会使用任何REST框架的其他功能,我们只会将视图编写为常规的Django视图。 257 | 258 | 编辑```snippets/views.py```文件,并添加以下内容。 259 | 260 | ```python 261 | from django.http import HttpResponse, JsonResponse 262 | from django.views.decorators.csrf import csrf_exempt 263 | from rest_framework.renderers import JSONRenderer 264 | from rest_framework.parsers import JSONParser 265 | from snippets.models import Snippet 266 | from snippets.serializers import SnippetSerializer 267 | ``` 268 | 269 | 我们的API最基本的功能是,支持列出所有snippets实例,或新建一个snippets实例。 270 | 271 | ```python 272 | @csrf_exempt 273 | def snippet_list(request): 274 | """ 275 | 展示所有的snippets, 或者创建一个新的snippet. 276 | """ 277 | if request.method == 'GET': 278 | snippets = Snippet.objects.all() 279 | serializer = SnippetSerializer(snippets, many=True) 280 | return JsonResponse(serializer.data, safe=False) 281 | 282 | elif request.method == 'POST': 283 | data = JSONParser().parse(request) 284 | serializer = SnippetSerializer(data=data) 285 | if serializer.is_valid(): 286 | serializer.save() 287 | return JsonResponse(serializer.data, status=201) 288 | return JsonResponse(serializer.errors, status=400) 289 | ``` 290 | 291 | 请注意,因为我们希望没有CSRF令牌的客户端访问到这个视图,所以我们需要将视图标记为```csrf_exempt```。正常情况下你不应该这么做,并且REST framework提供了更高级更安全的做法,但它现在就可以满足我们需求。 292 | 293 | 我们还需要另外一个视图,可以用来查看,更新或删除操作。 294 | 295 | ```python 296 | @csrf_exempt 297 | def snippet_detail(request, pk): 298 | """ 299 | 查看, 更新 或者 删除 一个 snippet. 300 | """ 301 | try: 302 | snippet = Snippet.objects.get(pk=pk) 303 | except Snippet.DoesNotExist: 304 | return HttpResponse(status=404) 305 | 306 | if request.method == 'GET': 307 | serializer = SnippetSerializer(snippet) 308 | return JsonResponse(serializer.data) 309 | 310 | elif request.method == 'PUT': 311 | data = JSONParser().parse(request) 312 | serializer = SnippetSerializer(snippet, data=data) 313 | if serializer.is_valid(): 314 | serializer.save() 315 | return JsonResponse(serializer.data) 316 | return JsonResponse(serializer.errors, status=400) 317 | 318 | elif request.method == 'DELETE': 319 | snippet.delete() 320 | return HttpResponse(status=204) 321 | ``` 322 | 323 | 最后,我们把这些联系起来。创建```snippets/urls.py```文件: 324 | 325 | ```python 326 | from django.conf.urls import url 327 | from snippets import views 328 | 329 | urlpatterns = [ 330 | url(r'^snippets/$', views.snippet_list), 331 | url(r'^snippets/(?P[0-9]+)/$', views.snippet_detail), 332 | ] 333 | ``` 334 | 335 | 我们还需要在```tutorial/urls.py```文件引用我们应用程序的URL。 336 | 337 | ```python 338 | from django.conf.urls import url, include 339 | 340 | urlpatterns = [ 341 | url(r'^', include('snippets.urls')), 342 | ] 343 | ``` 344 | 345 | 值得注意的是,我们目前还没有处理好几个细节。如果我们发送的是畸形的```json```,或者如果一个不支持的HTTP请求方法,那么我们最终会收到500"服务器错误"响应。 346 | 347 | 348 | ## 测试我们第一个Web API 349 | 350 | 现在我们启动服务。 351 | 352 | 首先退出shell: 353 | 354 | ```python 355 | quit() 356 | ``` 357 | 358 | 并启动Django的服务器。 359 | 360 | ```python 361 | python manage.py runserver 362 | 363 | Validating models... 364 | 365 | 0 errors found 366 | Django version 1.11, using settings 'tutorial.settings' 367 | Development server is running at http://127.0.0.1:8000/ 368 | Quit the server with CONTROL-C. 369 | ``` 370 | 371 | 在另一个终端窗口中,我们可以测试服务器。 372 | 373 | 我们可以使用curl或httpie来测试我们的API 。Httpie是用Python编写的用户友好的http客户端。让我们先安装。 374 | 375 | 您可以使用pip安装httpie: 376 | 377 | ```python 378 | pip install httpie 379 | ``` 380 | 381 | 最后,我们可以得到所有snippets的列表: 382 | 383 | ```python 384 | http http://127.0.0.1:8000/snippets/ 385 | 386 | HTTP/1.1 200 OK 387 | ... 388 | [ 389 | { 390 | "id": 1, 391 | "title": "", 392 | "code": "foo = \"bar\"\n", 393 | "linenos": false, 394 | "language": "python", 395 | "style": "friendly" 396 | }, 397 | { 398 | "id": 2, 399 | "title": "", 400 | "code": "print \"hello, world\"\n", 401 | "linenos": false, 402 | "language": "python", 403 | "style": "friendly" 404 | } 405 | ] 406 | ``` 407 | 408 | 或者我们可以通过引用snippet的id来get一个对应的实例: 409 | 410 | ```python 411 | http http://127.0.0.1:8000/snippets/2/ 412 | 413 | HTTP/1.1 200 OK 414 | ... 415 | { 416 | "id": 2, 417 | "title": "", 418 | "code": "print \"hello, world\"\n", 419 | "linenos": false, 420 | "language": "python", 421 | "style": "friendly" 422 | } 423 | ``` 424 | 425 | 同样,您可以通过在Web浏览器中访问URL来显示相同​​的json。 426 | 427 | ## 我们现在在哪 428 | 429 | 目前为止,我们已经有了一个序列化API,它与Django的Forms API以及一些常规的Django视图非常相似。 430 | 431 | 我们的API视图目前不做其他的事情,除了服务json响应之外,还有一些我们仍然希望清理的错误处理边缘案例,但这是一个正常运行的Web API。 432 | 433 | 我们将看到此教程第2部分```Requests & Responses```。 434 | -------------------------------------------------------------------------------- /api/gviews.md: -------------------------------------------------------------------------------- 1 | ## 通用视图Generic views 2 | 3 | > Django 的通用视图建立在基础视图之上,用于作为经常用到的功能的快捷方式,例如显示对象的详细信息。它们提炼视图开发中常见的风格和模式并将它们抽象,这样你可以快速编写常见的视图而不用重复你自己。 4 | —— Django 官方文档(内置的基于类的视图API) 5 | 6 | 点击这里[查看官方文档](https://docs.djangoproject.com/en/2.0/ref/class-based-views/#base-vs-generic-views)。 7 | 8 | 基于类的视图,最大的好处之一就是他将允许你编写可以重用的行为。REST framework利用这一个有点提供了一些预先构建好的视图,这些视图提供了常用的模式。 9 | 10 | REST framework提供的通用视图允许你快速的来构建一个和你的数据库紧密映射的一个API视图。 11 | 12 | 如果REST framework提供的通用视图不能够满足你的API需求,你可以使用更底层的```APIView```类。或者重新使用```mixins```和通用视图基类来重新编写一个你自己的可以重复使用的通用视图类。 13 | 14 | #### 例子 15 | 16 | 通常使用通用视图类的时候,你仅仅需要设置一些类属性即可: 17 | 18 | ```python 19 | from django.contrib.auth.models import User 20 | from myapp.serializers import UserSerializer 21 | from rest_framework import generics 22 | from rest_framework.permissions import IsAdminUser 23 | 24 | class UserList(generics.ListCreateAPIView): 25 | queryset = User.objects.all() 26 | serializer_class = UserSerializer 27 | permission_classes = (IsAdminUser,) 28 | ``` 29 | 30 | 对于更复杂的场景,你可能还想要覆盖视图类中的各种方法,比如下面: 31 | 32 | ```python 33 | class UserList(generics.ListCreateAPIView): 34 | queryset = User.objects.all() 35 | serializer_class = UserSerializer 36 | permission_classes = (IsAdminUser,) 37 | 38 | def list(self, request): 39 | # Note the use of `get_queryset()` instead of `self.queryset` 40 | queryset = self.get_queryset() 41 | serializer = UserSerializer(queryset, many=True) 42 | return Response(serializer.data) 43 | ``` 44 | 45 | 在极简单的情况下,你可能只需要通过```.as_view()```传递给类一些属性就可以了。比如,在你的```URLconf```中可能会包含如下的代码: 46 | 47 | ```python 48 | url(r'^/users/', ListCreateAPIView.as_view(queryset=User.objects.all(), serializer_class=UserSerializer), name='user-list') 49 | ``` 50 | 51 | --- 52 | 53 | ### API 参考 54 | 55 | #### ```GenericAPIView``` 类 56 | 57 | 源码位置: ```rest_framework.generics.GenericAPIView``` 58 | 59 | 这个类是所有具名通用视图的基础类。 60 | 61 | 这个类扩展了REST framework的```APIView```类,为标准的```list```视图和```detail```视图添加了一些必要的行为。 62 | 63 | 每个提供给用户使用的具体的命名通用视图,是使用内置的```GenericAPIView```和一个或者多个的```Mixins```类组合而成的。具体的下面有介绍。 64 | 65 |
66 |
67 |
68 |
69 | 70 | ##### 属性 71 | 72 | **基础设置**: 73 | 74 | 下面的属性,控制基础的视图行为。 75 | - ```queryset```: —— 用于从视图返回对象。通常情况下,你必须要设置这个属性或者是重写```get_queryset()```方法。如果你重写了视图的方法,一定要记住,不能直接访问这个属性,而是要调用```get_queryset()```方法; 因为```queryset```属性,只会评估计算一次,为后续所有的请求提供结果。 76 | - ```serializer_class```: —— 这个属性设置的类,将用于验证用户的输入并反序列化,或者序列化输出结果。通常你需要设置这个属性,或者是重写```get_serializer_class()```方法。 77 | - ```lookup_field```: —— 这是一个模型字段,用来执行单个的模型实例的对象查看(意思是,外键查看)。默认是```'pk'```。注意,当使用超链接的API,你需要确保API视图和序列化类设置```lookup```字段,如果你需要使用一个自定义的值。 78 | - ```lookup_url_kwarg```: —— 被应用于对象查找URL关键字参数。URLconf中应包括相应于该值的关键字参数。如果未设置,那么默认使用和```lookup_field```相同的值。 79 | 80 |
81 |
82 | 83 | **分页设置**: 84 | 85 | 下面的属性被用于当使用```list```视图的时候来控制分页。 86 | 87 | - ```pagination_class```: —— 用于给```list```视图结果分页。默认使用的是```settings```中的```DEFAULT_PAGINATION_CLASS```选项,选项结果是```'rest_framework.pagination.PageNumberPagination'```。 设置```pagination_class=None```将会禁用该视图的分页。 88 | 89 | 注意: 在使用```Django REST Framework 3.7.7```版本的过程中,该分页设置为```None```,也就是默认没有分页效果。 90 | 91 | 默认设置位置: ```rest_framework.settings.DEFAULTS.DEFAULT_PAGINATION_CLASS``` 92 | 93 | 关于分页类的源码位置: ```rest_framework.pagination.*Pagination``` 94 | 95 |
96 |
97 | 98 | **过滤设置**: 99 | 100 | - ```filter_backends```: —— 可以用来过滤Queryset的过滤类的一个列表。默认值是```settings```中的```DEFAULT_FILTER_BACKENDS```选项。 101 | 102 | 注意: 在使用```Django REST Framework 3.7.7```版本的过程中,该过滤设置为```()```。 103 | 104 |
105 |
106 |
107 |
108 | 109 | ##### 方法 110 | 111 | **基础方法**: 112 | 113 | 方法1:```get_queryset(self)``` 114 | 115 | 返回```list```视图所需要的```queryset```(查询结果集),并且可以用于```detail```视图的查询基础。默认情况下返回的是```queryset```属性指定的查询结果集。 116 | 117 | 应该总是使用```get_queryset()```方法而不是直接使用```self.queryset```来获取查询结果集。因为```self.queryset```仅仅计算一次,并且这些结果被缓存用于所有后续的请求。 118 | 119 | 在某些情况下,你可能要重写这个方法,用来适应动态的请求,比如,为每个当前访问的用户返回特定的结果集。代码如下: 120 | 121 | ```python 122 | def get_queryset(self): 123 | user = self.request.user 124 | return user.accounts.all() 125 | ``` 126 | 127 | 方法2:```get_object(self)``` 128 | 129 | 返回用于```detail```视图的一个实例对象。默认使用```lookup_field```参数来过滤基础查询集。 130 | 131 | 在某些情况下,你可能要重写这个方法,用来提供更复杂的行为,比如基于多个```URL kwarg```的对象查询。代码如下: 132 | 133 | ```python 134 | def get_object(self): 135 | queryset = self.get_queryset() 136 | filter = {} 137 | for field in self.multiple_lookup_fields: 138 | filter[field] = self.kwargs[field] 139 | 140 | obj = get_object_or_404(queryset, **filter) 141 | self.check_object_permissions(self.request, obj) 142 | return obj 143 | ``` 144 | 145 | 注意,如果你的API不包含任何对象级别的权限,你完全不需要```self.check_object_permissions```,只需要从```get_object_or_404```查找返回对象即可。 146 | 147 | 方法3:```filter_queryset(self, queryset)``` 148 | 149 | 给定一个查询结果集,然后过滤器会处理该查询结果集,最后返回一个新的```queryset```查询结果集。 150 | 151 | 比如: 152 | 153 | ```python 154 | def filter_queryset(self, queryset): 155 | filter_backends = (CategoryFilter,) 156 | 157 | if 'geo_route' in self.request.query_params: 158 | filter_backends = (GeoRouteFilter, CategoryFilter) 159 | elif 'geo_point' in self.request.query_params: 160 | filter_backends = (GeoPointFilter, CategoryFilter) 161 | 162 | for backend in list(filter_backends): 163 | queryset = backend().filter_queryset(self.request, queryset, view=self) 164 | 165 | return queryset 166 | ``` 167 | 168 | 方法4:```get_serializer_class(self)``` 169 | 170 | 返回用于序列化的class。默认返回的是```serializer_class```属性设定的类。 171 | 172 | 在某些情况下,你可能要重写这个方法,用来适应动态的行为,比如可以为读 和 写 操作 设定不同的序列化器, 或者为 不同的用户 设定 不同的序列化器。 173 | 174 | 比如: 175 | 176 | ```python 177 | def get_serializer_class(self): 178 | if self.request.user.is_staff: 179 | return FullAccountSerializer 180 | return BasicAccountSerializer 181 | ``` 182 | 183 |
184 |
185 | 186 | **保存 和 删除 钩子**: 187 | 188 | 下面的这些方法是由```Mixins```类提供,并提供了简单的用于覆盖对象默认的 保存和删除 的行为。 189 | 190 | - ```perform_create(self, serializer)``` —— 当保存一个新的对象实例的时候,通过```CreateModelMixin```来调用。 191 | - ```perform_update(self, serializer)``` —— 当保存一个已有的对象实例的时候,通过```UpdateModelMixin```来调用。 192 | - ```perform_destroy(self, instance)``` —— 当删除一个对象实例的时候,通过```DestroyModelMixin```来调用。 193 | 194 | 这些钩子对于设置请求中的隐藏属性,而这些属性又不是请求数据的一部分,是非常有用的。比如,你可能会基于请求的用户,或者基于URL的关键字参数来设置属性,比如下面这段代码: 195 | 196 | ```python 197 | def perform_create(self, serializer): 198 | serializer.save(user=self.request.user) 199 | ``` 200 | 201 | 这些可覆盖的点非常的多,用于在保存数据之前或者之后的各种行为。比如,在数据保存完之后可以发送确认邮件,或者日志更新等等。如下这段代码: 202 | 203 | ```python 204 | def perform_update(self, serializer): 205 | instance = serializer.save() 206 | send_email_confirmation(user=self.request.user, modified=instance) 207 | ``` 208 | 209 | 你还可以使用这些钩子来增加一些额外的验证,通过抛出```ValidationError()```异常。如果你需要在数据保存到数据库之前做一些逻辑性的验证操作,这可能是非常有用的。比如下面这段代码: 210 | 211 | ```python 212 | def perform_create(self, serializer): 213 | queryset = SignupRequest.objects.filter(user=self.request.user) 214 | if queryset.exists(): 215 | raise ValidationError('You have already signed up') 216 | serializer.save(user=self.request.user) 217 | ``` 218 | 219 | 注意: 这些方法取代了```2.x```版本中的```pre_save```, ```post_save```, ```pre_delete```和```post_delete```方法,它们将不再可用。 220 | 221 |
222 |
223 | 224 | **其他方法**: 225 | 226 | 你不需要重写以下方法,如果你使用```GenericAPIView```在编写自定义的视图,里面可能会用到它们。 227 | 228 | - ```get_serializer_context(self)``` —— 返回一个字典包含任意的额外上下文,来提供给序列化器。默认包含```'request'```, ```'view'```和```'format'```key。 229 | - ```get_serializer(self, instance=None, data=None, many=False, partial=False)``` —— 返回序列化器的实例。 230 | - ```get_paginated_response(self, data)``` —— 返回一个已经分页的```Response```对象。 231 | - ```paginate_queryset(self, queryset)``` —— 如果需要对```queryset```来进行分页,那么会返回一个分页对象,或者当没有给视图设置分页的时候返回一个```None```。 232 | - ```filter_queryset(self, queryset)``` —— 给定一个查询结果集,然后过滤器会处理该查询结果集,最后返回一个新的```queryset```查询结果集。 233 | 234 | --- 235 | 236 | ### Mixins 类 237 | 238 | 源码位置: ```rest_framework.mixins.*Mixin``` 239 | 240 | Mixins类用来提供基本的视图行为动作。注意,Mixins类提供的是动作方法,而不是处理方法,比如提供了```.create()```和```.list()```而不是```.post()```和```.get()```。这样做,会允许更加灵活的组合。也就是说,```GET```请求的处理方法```.get()```, 可以包含多种动作方法,比如```.create()```和```.list()```方法。 241 | 242 | 这些mixins类,可以通过```rest_framework.mixins```来导入。 243 | 244 | #### ```ListModelMixin``` 类 245 | 246 | 提供了```.list(request, *args, **kwargs)```方法,用于实现列出查询结果集。 247 | 248 | 如果查询结果集被填充,那么将返回一个```200 OK```的响应,response body是已经序列化的查询结果。当然这个响应的结果可能是已经分页的。 249 | 250 | #### ```CreateModelMixin``` 类 251 | 252 | 提供了```.create(request, *args, **kwargs)```方法,用于实现创建并保存一个模型实例。 253 | 254 | 如果一个对象被创建,那么会返回一个```201 Created```的响应,response body是已经序列化的对象。如果包含一个名为```url```的key,那么response header的```Location```将会填充该值。 255 | 256 | 关于```Location```,点击[此处](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Location)。 257 | 258 | 如果用于创建对象的请求数据是无效的,那么会返回一个```400 Bad Request```的响应,response body是错误详情。 259 | 260 | #### ```RetrieveModelMixin``` 类 261 | 262 | 提供了```.retrieve(request, *args, **kwargs)```方法,用来返回一个已经存在的模型实例。 263 | 264 | 如果对象可以被取出来,那么将返回一个```200 OK```的响应,response body是已经序列化的实例。否则的会返回```404 Not Found```。 265 | 266 | #### ```UpdateModelMixin``` 类 267 | 268 | 提供一个```.update(request, *args, **kwargs)```方法,用于实现更新并保存一个已经存在的模型实例。 269 | 270 | 在这个类里面提供了```.partial_update(request, *args, **kwargs)```方法,同样是更新,只不过该方法用于局部更新,更新字段是可选的。这将允许```PATCH```的HTTP请求。 271 | 272 | 如果对象被更新,返回一个```200 OK```的响应,response body是已经序列化的实例。 273 | 274 | 如果用于更新对象的请求数据是无效的,那么会返回一个```400 Bad Request```, response body是错误详情。 275 | 276 | #### ```DestroyModelMixin``` 类 277 | 278 | 提供一个```.destroy(request, *args, **kwargs)```方法, 用于实现删除一个已经存在的模型实例。 279 | 280 | 如果对象被删除,那么返回```204 No Content```,否则返回```404 Not Found```。 281 | 282 | --- 283 | 284 | ### 命名视图 类 285 | 286 | 源码位置: ```rest_framework.generics.*APIView``` 287 | 288 | 下面的类是具体的可以被直接使用的通用视图。如果你使用通用视图,那么下面的这些方法将足够你使用,除非你需要重写大量的行为。 289 | 290 | 这些类,可以从```rest_framework.generics```中导入。 291 | 292 | #### ```CreateAPIView``` 类 293 | 294 | 用于**create-only**endpoints。 295 | 296 | 提供一个```post```处理方法。 297 | 298 | 包含```CreateModelMixin```和```GenericAPIView``` 299 | 300 | 源代码: 301 | 302 | ```python 303 | class CreateAPIView(mixins.CreateModelMixin, 304 | GenericAPIView): 305 | """ 306 | 创建一个模型实例的 具体的命名视图 307 | """ 308 | def post(self, request, *args, **kwargs): 309 | return self.create(request, *args, **kwargs) 310 | ``` 311 | 312 | #### ```ListAPIView``` 类 313 | 314 | 用于一个只读的端点,来代表一个模型实例的集合。(查询结果集) 315 | 316 | 提供一个```get```处理方法。 317 | 318 | 包含```GenericAPIView```, ```ListModelMixin``` 319 | 320 | #### ```RetrieveAPIView``` 类 321 | 322 | 用于一个只读的端点,来代表一个单个的模型实例。 323 | 324 | 提供一个```get```处理方法。 325 | 326 | 包含```GenericAPIView```, ```RetrieveModelMixin``` 327 | 328 | #### ```DestroyAPIView``` 类 329 | 330 | 用于删除一个单个的模型实例。 331 | 332 | 提供一个```delete```处理方法。 333 | 334 | 包含```GenericAPIView```, ```DestroyModelMixin``` 335 | 336 | #### ```UpdateAPIView``` 类 337 | 338 | 用于更新一个单个的模型实例。 339 | 340 | 提供```put```和```patch```处理方法。 341 | 342 | 包含```GenericAPIView```, ```UpdateModelMixin``` 343 | 344 | #### ```ListCreateAPIView``` 类 345 | 346 | 用于一个可读 可写的 端点,来代表一个模型实例的集合。 347 | 348 | 提供```get```和```post```处理方法。 349 | 350 | 包含```GenericAPIView```, ```ListModelMixin```, ```CreateModelMixin``` 351 | 352 | 源代码: 353 | 354 | ```python 355 | class ListCreateAPIView(mixins.ListModelMixin, 356 | mixins.CreateModelMixin, 357 | GenericAPIView): 358 | """ 359 | 具名通用视图, 用于列出一个查询结果集 或者 创建一个模型实例 360 | """ 361 | def get(self, request, *args, **kwargs): 362 | return self.list(request, *args, **kwargs) 363 | 364 | def post(self, request, *args, **kwargs): 365 | return self.create(request, *args, **kwargs) 366 | ``` 367 | 368 | #### ```RetrieveUpdateAPIView``` 类 369 | 370 | 用于可读 可更新 的端点, 来代表一个单个的模型实例。 371 | 372 | 提供```get```, ```put```和```patch```处理方法。 373 | 374 | 包含```GenericAPIView```, ```RetrieveModelMixin```, ```UpdateModelMixin``` 375 | 376 | #### ```RetrieveDestroyAPIView``` 类 377 | 378 | 用于可读 可删除 的端点, 来代表一个单个的模型实例。 379 | 380 | 提供```get```,```delete```处理方法。 381 | 382 | 包含```GenericAPIView```, ```RetrieveModelMixin```, ```DestroyModelMixin``` 383 | 384 | #### ```RetrieveUpdateDestroyAPIView``` 类 385 | 386 | 用于 可读 可更新 可删除的端点, 来代表一个单个的模型实例。 387 | 388 | 提供```get```,```delete```, ```put```和```patch```处理方法。 389 | 390 | 包含```GenericAPIView```, ```RetrieveModelMixin```, ```UpdateModelMixin```, ```DestroyModelMixin``` 391 | 392 | --- 393 | 394 | ### 自定义 通用视图 395 | 396 | 通常你会使用已经存在的通用视图,只是会有一些自定义的行为。如果你发现你自己在多个地方重用了这些自定义的行为,你可能需要将这些行为重构到一个公共的类里面,然后就可以在任何的视图或者视图集合中来应用它。 397 | 398 | #### 创建自定义的 ```mixins``` 399 | 400 | 比如,你可能需要URL中的多个字段来查询对象,那么你可能需要创建一个Mixins类,比如下面这样: 401 | 402 | ```python 403 | class MultipleFieldLookupMixin(object): 404 | """ 405 | 应用这个mixin 到任何的view 或者 viewset 中 来获得 多个字段的过滤, 基于 `lookup_fields`属性; 406 | 来代替默认的单个字段过滤。 407 | """ 408 | def get_object(self): 409 | queryset = self.get_queryset() # Get the base queryset 410 | queryset = self.filter_queryset(queryset) # Apply any filter backends 411 | filter = {} 412 | for field in self.lookup_fields: 413 | if self.kwargs[field]: # Ignore empty fields. 414 | filter[field] = self.kwargs[field] 415 | obj = get_object_or_404(queryset, **filter) # Lookup the object 416 | self.check_object_permissions(self.request, obj) 417 | return obj 418 | ``` 419 | 420 | 在任何时候,只要你需要,你都可以在任何视图或者视图集合中去使用它,比如这样: 421 | 422 | ```python 423 | class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView): 424 | queryset = User.objects.all() 425 | serializer_class = UserSerializer 426 | lookup_fields = ('account', 'username') 427 | ``` 428 | 429 | 如果你需要自定义某些行为,那么使用Mixins是最好的选择。 430 | 431 | #### 创建自定义的 基础类 432 | 433 | 如果你使用了多个Mixins类,那么进一步,你可以自定义一个你自己的基础类,然后你可以在你的项目中使用它们。 比如下面这样: 434 | 435 | ```python 436 | class BaseRetrieveView(MultipleFieldLookupMixin, generics.RetrieveAPIView): 437 | pass 438 | 439 | class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView): 440 | pass 441 | ``` 442 | 443 | 如果在你的项目中,有大量的视图来处理各种行为,那么最好的办法是指定一个基础类。 444 | 445 | --- 446 | 447 | ### ```PUT``` 用作 创建 数据 448 | 449 | 在REST framework的3.0版本之前,对于```PUT```可以是更新,也可以是创建操作; 这取决于对象是否存在。 450 | 451 | 允许```PUT```来当做创建操作是由问题的,因为这必然会暴露对象存在或者不存在的信息。 452 | 453 | 在3.0的版本中,不会再由```PUT``` 用作 ```404``` 和 ```PUT``` 用作创建数据, 取而代之的是将```404```设为默认的行为,因为这更简单,更明了。 454 | 455 | 如果你需要使用通用的```PUT-as-create```, 你可以在Mixins中包含```AllowPUTAsCreateMixin```。点击此处[查看代码](https://gist.github.com/tomchristie/a2ace4577eff2c603b1b)。 456 | 457 | --- 458 | 459 | ### 第三方包 460 | 461 | 以下第三方包提供了额外的通用视图的实现。 462 | 463 | #### Django REST Framework bulk 464 | 465 | 这个[django-rest-framework-bulk package](https://github.com/miki725/django-rest-framework-bulk)实现了一些通用视图和Mixins类以及一些具体的命名通用视图来实现通过API 请求实现批量操作。 466 | 467 | #### Django Rest Multiple Models 468 | 469 | 这个[Django Rest Multiple Models](https://github.com/MattBroach/DjangoRestMultipleModels)是了一些通用视图和Mixins视图,来实现通过一个单独的API请求来发送多个序列的模型实例集合或者是queryset(查询结果集)。 470 | 471 | --- 472 | 473 | 参考链接: 474 | - https://docs.djangoproject.com/en/2.0/ref/class-based-views/#base-vs-generic-views 475 | - https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Location 476 | - https://github.com/miki725/django-rest-framework-bulk 477 | - https://github.com/MattBroach/DjangoRestMultipleModels 478 | - https://gist.github.com/tomchristie/a2ace4577eff2c603b1b -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /api/renderers.md: -------------------------------------------------------------------------------- 1 | ## 渲染器Renderers 2 | 3 | > 在TemplateResponse 实例返回给客户端之前,它必须被渲染。渲染的过程采用模板和上下文变量的中间表示形式,并最终将它转换为可以发送给客户端的字节流。 4 | —— Django官方文档(TemplateResponse and SimpleTemplateResponse) 5 | 6 | 点击此处,[查看官方文档](https://docs.djangoproject.com/en/2.0/ref/template-response/) 7 | 8 | REST framework包含很多的内置渲染器类。允许你使用各种各样的媒体类型返回响应。还可以设置自定义的渲染器类,来灵活的设计你自己的媒体类型。 9 | 10 | #### 渲染器是如何确定的 11 | 12 | 视图中有效的渲染器总是被设置为一个包含类的列表。当进入一个```View```逻辑的时候,REST framework会对传进来的请求执行内容协商,并且最终会确定一个最合适的渲染器,以满足Request。 13 | 14 | 内容协商的最基本的过程是检查HTTP请求头的```Accept```,用来确定哪种Media类型是响应最期望的。可选的,URL的格式后缀被用于显示请求特定的表示。比如,URL```http://example.com/api/users_count.json```可能只会返回JSON数据。 15 | 16 | 更多信息查看[内容协商文档](./cnegotiation.md) 17 | 18 | #### 设置渲染器 19 | 20 | 默认情况下渲染器的设置可能是通过```DEFAULT_RENDERER_CLASSES```设置的全局的。比如,下面的设置将会使用```JSON```作为主要的媒体类型并且还包括可HTML浏览API。 21 | 22 | ```python 23 | REST_FRAMEWORK = { 24 | 'DEFAULT_RENDERER_CLASSES': ( 25 | 'rest_framework.renderers.JSONRenderer', 26 | 'rest_framework.renderers.BrowsableAPIRenderer', 27 | ) 28 | } 29 | ``` 30 | 31 | 你还可以为基于```APIView```的单个视图或者视图集合(```viewset```)设置渲染器: 32 | 33 | ```python 34 | from django.contrib.auth.models import User 35 | from rest_framework.renderers import JSONRenderer 36 | from rest_framework.response import Response 37 | from rest_framework.views import APIView 38 | 39 | class UserCountView(APIView): 40 | """ 41 | A view that returns the count of active users in JSON. 42 | """ 43 | renderer_classes = (JSONRenderer, ) 44 | 45 | def get(self, request, format=None): 46 | user_count = User.objects.filter(active=True).count() 47 | content = {'user_count': user_count} 48 | return Response(content) 49 | ``` 50 | 51 | 或者为基于```@api_view```装饰器的函数视图设置渲染器: 52 | 53 | ```python 54 | @api_view(['GET']) 55 | @renderer_classes((JSONRenderer,)) 56 | def user_count_view(request, format=None): 57 | """ 58 | A view that returns the count of active users in JSON. 59 | """ 60 | user_count = User.objects.filter(active=True).count() 61 | content = {'user_count': user_count} 62 | return Response(content) 63 | ``` 64 | 65 | #### 渲染器类的排序 66 | 67 | 当为您的API指定渲染器类的时候,要考虑你想要分配给每种Media类型的优先级,这一点非常重要。当客户端没有指定一个可以接受的表现形式,比如发送一个```Accept: */*```HTTP请求头,或者在请求头中不包括任何```Accept```,那么REST framework将会选择列表中的第一个渲染器来用于响应数据。 68 | 69 | 比如你的API服务器支持```JSON```的响应和HTML可浏览的API,你可能想要指定```JSONRenderer```为你默认的渲染器,这样那些没有指定```Accept```HTTP头的客户端将会接收到```JSON```响应。 70 | 71 | 如果你的API服务器,包含可以根据请求来确定提供常规网页和API响应的视图,你可能会考虑设置```TemplateHTMLRenderer```作为你的默认的渲染器。 72 | 73 | --- 74 | 75 |
76 |
77 |
78 |
79 | 80 | ### API 参考 81 | 82 | 关于渲染器的源码位置: ```rest_framework.renderers``` 83 | 84 | #### ```JSONRenderer```渲染器 85 | 86 | 使用UTF-8编码,将请求数据渲染为```JSON```。 87 | 88 | 注意,默认情况下包含Unicode字符串,并且用紧凑的格式来呈现,没有多余的空白符: 89 | 90 | ```python 91 | {"unicode black star":"★","value":999} 92 | ``` 93 | 94 | 客户端可能会包含额外的Media类型参数```indent```,这表示返回的```JSON```数据会被缩进。比如```Accept: application/json; indent=4```: 95 | 96 | ```javascript 97 | { 98 | "unicode black star": "★", 99 | "value": 999 100 | } 101 | ``` 102 | 103 | 默认的编码风格使用```UNICODE_JSON```和```COMPACT_JSON```设置来修改。 104 | 105 | 在```rest_framework.settings.DEFAULTS``` 中有如下设置: 106 | 107 | ```python 108 | DEFAULTS = { 109 | ……省略…… 110 | 111 | # Encoding 112 | 'UNICODE_JSON': True, 113 | 'COMPACT_JSON': True, 114 | 'STRICT_JSON': True, 115 | 'COERCE_DECIMAL_TO_STRING': True, 116 | 'UPLOADED_FILES_USE_URL': True, 117 | 118 | # Browseable API 119 | 'HTML_SELECT_CUTOFF': 1000, 120 | 'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...", 121 | 122 | ……省略…… 123 | } 124 | ``` 125 | 126 | ```.media_type```为 ```application/json``` 127 | 128 | URL ```.format```后缀为: ```'.json'``` 129 | 130 | ```.charset```: ```None``` 131 | 132 | #### ```TemplateHTMLRenderer```渲染器 133 | 134 | 使用Django标准的模板渲染将数据渲染到HTML中。不像其他渲染器,数据被传递给```Response```不需要序列化。另外,与其他渲染器不同,在创建```Response```的时候你需要包含```template_name```参数。 135 | 136 | 渲染器```TemplateHTMLRenderer```将会创建一个```RequestContext```, 使用```response.data```作为内容,并且确定一个模板名称用于渲染一个内容。 137 | 138 | 模板名称是按照下面的顺序来确定的: 139 | 140 | 1. 显式的传递给```Response```对象一个```template_name```参数。 141 | 2. 在类里面明确的设置```.template_name```属性。 142 | 3. 返回```view.get_template_names()```的结果。 143 | 144 | 下面是一个使用```TemplateHTMLRenderer```渲染器的示例: 145 | 146 | ```python 147 | class UserDetail(generics.RetrieveAPIView): 148 | """ 149 | A view that returns a templated HTML representation of a given user. 150 | """ 151 | queryset = User.objects.all() 152 | renderer_classes = (TemplateHTMLRenderer,) 153 | 154 | def get(self, request, *args, **kwargs): 155 | self.object = self.get_object() 156 | return Response({'user': self.object}, template_name='user_detail.html') 157 | ``` 158 | 159 | 如果你要使用```TemplateHTMLRenderer```,你可以使用REST framework返回一个常规的HTML 页面,或者从一个单一的Endpoint同时返回HTML和API响应数据。 160 | 161 | 如果你使用```TemplateHTMLRenderer```和其他的渲染器来构建你的Web站点,你应该考虑将```TemplateHTMLRenderer```放在```renderer_classes```列表的第一个,这样即使客户端的HTTP Request Header中的```ACCEPT```不正确,它也会优先使用```TemplateHTMLRenderer```。 162 | 163 | 参考链接,[HTML & Forms Topic Page](http://www.django-rest-framework.org/topics/html-and-forms/)来查看更多的案例使用。 164 | 165 | ```.media_type```为 ```text/html``` 166 | 167 | URL ```.format```后缀为: ```'.html'``` 168 | 169 | ```.charset```: ```utf-8``` 170 | 171 | #### ```StaticHTMLRenderer```渲染器 172 | 173 | 一个简单的渲染器,它会返回一个```预渲染```的HTML。不像其他的渲染器,传递给```Response```对象的数据应该是一个表示要返回的字符串。 174 | 175 | 下面是一个使用```StaticHTMLRenderer```的例子: 176 | 177 | ```python 178 | @api_view(('GET',)) 179 | @renderer_classes((StaticHTMLRenderer,)) 180 | def simple_html_view(request): 181 | data = '

Hello, world

' 182 | return Response(data) 183 | ``` 184 | 185 | 如果你要使用```StaticHTMLRenderer```,你可以使用REST framework返回一个常规的HTML 页面,或者从一个单一的Endpoint同时返回HTML和API响应数据。 186 | 187 | ```.media_type```为 ```text/html``` 188 | 189 | URL ```.format```后缀为: ```'.html'``` 190 | 191 | ```.charset```: ```utf-8``` 192 | 193 | 注意: ```StaticHTMLRenderer```渲染器类是```TemplateHTMLRenderer```渲染器类的子类。 194 | 195 | #### ```BrowsableAPIRenderer```渲染器 196 | 197 | 为可视化的API将数据渲染到HTML中。 198 | 199 | 默认情况下,REST framework使用的这种方式进行渲染。默认配置(```rest_framework.settings```)如下: 200 | 201 | 202 | ```python 203 | DEFAULTS = { 204 | # Base API policies 205 | 'DEFAULT_RENDERER_CLASSES': ( 206 | 'rest_framework.renderers.JSONRenderer', 207 | 'rest_framework.renderers.BrowsableAPIRenderer', 208 | ), 209 | ……省略…… 210 | } 211 | ``` 212 | 213 | ![](../images/render1.png) 214 | 215 | 该渲染器,会确定哪个渲染器具有最高的级别,然后使用它在HTML页面中显示API的响应样式。 216 | 217 | 使用```.json```的形式如下: 218 | 219 | ![](../images/render2.png) 220 | 221 | ```.media_type```: ```text/html``` 222 | 223 | ```.format```: ```'.api'``` 224 | 225 | ```.charset```: ```utf-8``` 226 | 227 | ```.template```: ```'rest_framework/api.html'``` 228 | 229 | 源代码如下: 230 | 231 | ```python 232 | class BrowsableAPIRenderer(BaseRenderer): 233 | """ 234 | HTML renderer used to self-document the API. 235 | """ 236 | media_type = 'text/html' 237 | format = 'api' 238 | template = 'rest_framework/api.html' 239 | filter_template = 'rest_framework/filters/base.html' 240 | code_style = 'emacs' 241 | charset = 'utf-8' 242 | form_renderer_class = HTMLFormRenderer 243 | 244 | def get_default_renderer(self, view): 245 | """ 246 | Return an instance of the first valid renderer. 247 | (Don't use another documenting renderer.) 248 | """ 249 | renderers = [renderer for renderer in view.renderer_classes 250 | if not issubclass(renderer, BrowsableAPIRenderer)] 251 | non_template_renderers = [renderer for renderer in renderers 252 | if not hasattr(renderer, 'get_template_names')] 253 | 254 | if not renderers: 255 | return None 256 | elif non_template_renderers: 257 | return non_template_renderers[0]() 258 | return renderers[0]() 259 | 260 | ……省略…… 261 | ``` 262 | 263 | ##### 自定义```BrowsableAPIRenderer``` 264 | 265 | 默认情况下,响应内容将使用最高优先级渲染器渲染,除了```BrowsableAPIRenderer```。如果你需要自定义此行为,例如使用HTML作为默认返回格式,但在可浏览的API中使用JSON,则可以通过重写```get_default_renderer()```方法来实现。默认情况下(看上面的源代码),会检测第一个有效的渲染器来进行渲染,并不肯定是使用```JSONRender```来进行渲染的。例如: 266 | 267 | ```python 268 | class CustomBrowsableAPIRenderer(BrowsableAPIRenderer): 269 | def get_default_renderer(self, view): 270 | return JSONRenderer() 271 | ``` 272 | 273 | #### ```AdminRenderer```渲染器 274 | 275 | 已类似管理员(Django Admin)的方式在HTML中渲染数据。 276 | 277 | ![](../images/render3.png) 278 | 279 | 此渲染器适用于CRUD风格的Web API,它还应该提供用于管理数据的用户友好界面。 280 | 281 | 注意,如果你的视图有嵌套或者是列表序列化,那么当使用```AdminRenderer```的时候如果进行表单输入,那么HTML不会很好的支持。 282 | 283 | **注意**: 只有在你的序列化数据中包含正确的```URL_FIELD_NAME```(默认是```url```)属性的时候,```AdminRenderer```渲染器才会包含到```detail```页面的链接。只有在使用```HyperlinkedModelSerializer```的时候才会包含该属性,如果使用```ModelSerializer```或者是常规的```Serializer```类的时候,需要手动显式指定该字段。比如,在这里我们使用模型的```get_absolute_url```方法: 284 | 285 | ```python 286 | class AccountSerializer(serializers.ModelSerializer): 287 | url = serializers.CharField(source='get_absolute_url', read_only=True) 288 | 289 | class Meta: 290 | model = Account 291 | ``` 292 | 293 | ```.media_type```: ```text/html``` 294 | 295 | ```.format```: ```'.admin'``` 296 | 297 | ```.charset```: ```utf-8``` 298 | 299 | ```.template```: ```'rest_framework/admin.html'``` 300 | 301 | #### ```HTMLFormRenderer```渲染器 302 | 303 | 将序列化返回的数据渲染进HTML表单。 304 | 305 | 渲染器的输出不会包含完整的```
```标签,隐藏的```CSRF```和任意的提交按钮。 306 | 307 | 这个渲染器不能被直接使用。但是可以给序列化实例传递```render_form```模板标签来替代模板。 308 | 309 | 更多的信息参考[HTML & Forms](../topics/forms.md)文档。 310 | 311 | ```.media_type```: ```text/html``` 312 | 313 | ```.format```: ```'.form'``` 314 | 315 | ```.charset```: ```utf-8``` 316 | 317 | ```.template```: ```'rest_framework/horizontal/form.html'``` 318 | 319 | #### ```MultiPartRenderer```渲染器 320 | 321 | 这个渲染器被用来渲染HTML multipart form数据。它不适用与作为一个响应渲染器。但是可以用它来创建一个测试请求, 使用REST framework的[测试客户端和测试请求工厂](./testing.md)。 322 | 323 | ```.media_type```: ```multipart/form-data; boundary=BoUnDaRyStRiNg``` 324 | 325 | ```.format```: ```'.multipart'``` 326 | 327 | ```.charset```: ```utf-8``` 328 | 329 | --- 330 | 331 |
332 |
333 |
334 |
335 | 336 | ### 自定义渲染器 337 | 338 | 如果要实现一个自定义的渲染器,你应该重写```BaseRenderer```方法。并设置```.media_type```和```.format```属性,并且实现```.render(self, data, media_type=None, renderer_context=None)```方法。 339 | 340 | 这个方法应该返回一个```bytestring```,被用于HTTP Response body。 341 | 342 | 传递给```.render```的参数解释如下: 343 | 344 | - ```data```: 请求数据,这个数据是```Response()```设置的。 345 | - ```media_type=None```: 可选的,如果设定,那么就是在内容协商阶段确定的接收的Media type。依赖客户端的```Accept```头。 346 | - ```renderer_context=None```: 可选的。如果设定,那么这是由```view```提供的上下文信息。是一个字典。默认情况下这个字典会包括以下键: ```view```, ```request```, ```response```, ```args```, ```kwargs```。 347 | 348 | #### 例子 349 | 350 | 下面是一个文本渲染器的例子,它将返回一个带有```data```数据参数的响应作为响应的内容。 351 | 352 | ```python 353 | from django.utils.encoding import smart_unicode 354 | from rest_framework import renderers 355 | 356 | 357 | class PlainTextRenderer(renderers.BaseRenderer): 358 | media_type = 'text/plain' 359 | format = 'txt' 360 | 361 | def render(self, data, media_type=None, renderer_context=None): 362 | return data.encode(self.charset) 363 | ``` 364 | 365 | #### 设置字符集 366 | 367 | 默认的渲染器使用的是```UTF-8```编码,如果要设置不同的编码格式,在渲染器中设置```charset```参数: 368 | 369 | ```python 370 | class PlainTextRenderer(renderers.BaseRenderer): 371 | media_type = 'text/plain' 372 | format = 'txt' 373 | charset = 'iso-8859-1' 374 | 375 | def render(self, data, media_type=None, renderer_context=None): 376 | return data.encode(self.charset) 377 | ``` 378 | 379 | 注意,如果一个渲染类返回了一个unicode字符串,则响应内容将被```Response```类强制转换成bytestring,渲染器上的设置的 charset 属性将用于确定编码。 380 | 381 | 如果渲染器返回一个bytestring表示原始的二进制内容,则应该设置字符集的值为 None,确保响应请求头的 ```Content-Type``` 中不会设置 ```charset``` 值。 382 | 383 | 在某些情况下你可能还需要将 ```render_style``` 属性设置成 ```'binary'```。这么做也将确保可浏览的API不会尝试将二进制内容显示为字符串。 384 | 385 | ```python 386 | class JPEGRenderer(renderers.BaseRenderer): 387 | media_type = 'image/jpeg' 388 | format = 'jpg' 389 | charset = None 390 | render_style = 'binary' 391 | 392 | def render(self, data, media_type=None, renderer_context=None): 393 | return data 394 | ``` 395 | 396 | --- 397 | 398 |
399 |
400 |
401 |
402 | 403 | ### 高级渲染器用法 404 | 405 | 你可以使用REST framework的渲染器来做一些非常灵活的事情。比如: 406 | 407 | - 根据请求的Media type,从同一个Endpoint既能提供单一的又能提供嵌套的表示。 408 | - 从一个Endpoint既能处理常规的HTML页面又能提供基于JSON的API响应。 409 | - 为API客户端提供多种形式的HTML表现。 410 | - 未指定渲染器的媒体类型,例如使用 ```media_type = 'image/*'```,并使用 Accept 标头来更改响应的编码。 411 | 412 | #### 通过媒体类型(Media Type)改变行为 413 | 414 | 在一些场景下,你可能想要你的视图根据接收的media type来使用不同的序列化风格。如果你想要实现这个功能,你可以访问```request.accepted_renderer```来确定用于Response的协商渲染器。 415 | 416 | 比如: 417 | 418 | ```python 419 | @api_view(('GET',)) 420 | @renderer_classes((TemplateHTMLRenderer, JSONRenderer)) 421 | def list_users(request): 422 | """ 423 | 这个视图可以返回系统中的用户,以 JSON 或者是 HTML的表现形式 424 | """ 425 | queryset = Users.objects.filter(active=True) 426 | 427 | if request.accepted_renderer.format == 'html': 428 | # TemplateHTMLRenderer takes a context dict, 429 | # and additionally requires a 'template_name'. 430 | # It does not require serialization. 431 | data = {'users': queryset} 432 | return Response(data, template_name='list_users.html') 433 | 434 | # JSONRenderer requires serialized data as normal. 435 | serializer = UserSerializer(instance=queryset) 436 | data = serializer.data 437 | return Response(data) 438 | ``` 439 | 440 | #### 不明确的media type 441 | 442 | 在某些场景中,你可能想要渲染器提供一组媒体类型。在本案例中你可以使用```image/*```或者```*/*```来指定一个不明确的媒体类型。 443 | 444 | 如果你不指定媒体类型,那么应该在响应数据中的```content_type```属性中指定媒体类型。比如这样: 445 | 446 | ```python 447 | return Response(data, content_type='image/png') 448 | ``` 449 | 450 | #### 设计你的媒体类型 451 | 452 | 大多数Web APIs的目的很简单,它们只需要返回一个JSON数据,用超链接来表现数据之间的关系就足够的。但是如果你想要更加完整的拥抱RESTful设计和HATEOAS需要更详细的考虑媒体类型的设计和使用。 453 | 454 | 用Roy Fielding的话来说,"REST API 应该花费所有的描述性努力来定义用于表示资源和驱动应用程序状态的媒体类型(们),或者为现有的标准媒体类型定义扩展关系名称和/或超文本启用标记"。 455 | 456 | 关于优秀的自定义媒体类型,参考链接: 457 | 458 | - https://developer.github.com/v3/media/ 459 | - http://www.amundsen.com/media-types/collection/ 460 | 461 | #### HTML 错误视图 462 | 463 | 通常渲染器都具有相同的行为,无论它处理的是正常的响应还是由异常引起的响应,如 ```Http404``` 或 ```PermissionDenied``` 异常,或者任何一个 ```APIException``` 的子类。 464 | 465 | 如果你正在使用 ```TemplateHTMLRenderer``` 或 ```StaticHTMLRenderer``` 时抛出了异常,行为略有不同。并且反映 [Django对错误视图的默认处理](https://docs.djangoproject.com/en/2.0/topics/http/views/#customizing-error-views). 466 | 467 | 由HTML渲染器引发异常和处理的异常将尝试按照优先顺序使用以下方法之一进行渲染。 468 | 469 | - 加载并渲染一个名为 ```{status_code}.html```的模板。 470 | - 加载并渲染一个名为 ```api_exception.html```的模板。 471 | - 渲染HTTP状态码和文本,例如 "404 Not Found"。 472 | 473 | 模板将使用一个包括```status_code```和```details```的 ```RequestContext``` 渲染。 474 | 475 | 注意: 如果设置了 ```DEBUG=True```,Django将展示它的标准回溯错误页面, 而不是渲染HTTP状态码和文本。 476 | 477 | --- 478 | 479 |
480 |
481 |
482 |
483 | 484 | ### 第三方包 485 | 486 | 下面都是一些第三方可用的包。 487 | 488 | #### YAML 489 | 490 | 包[REST framework YAML](https://jpadilla.github.io/django-rest-framework-yaml/) 提供 YAML 解析和渲染支持。它之前直接包含在REST framework 包中,现在被替代为第三方包支持。 491 | 492 | **安装和配置**: 493 | 494 | 使用pip安装: 495 | 496 | ```shell 497 | $ pip install djangorestframework-yaml 498 | ``` 499 | 500 | 修改设置: 501 | 502 | ```python 503 | REST_FRAMEWORK = { 504 | 'DEFAULT_PARSER_CLASSES': ( 505 | 'rest_framework_yaml.parsers.YAMLParser', 506 | ), 507 | 'DEFAULT_RENDERER_CLASSES': ( 508 | 'rest_framework_yaml.renderers.YAMLRenderer', 509 | ), 510 | } 511 | ``` 512 | 513 | #### XML 514 | 515 | 包[REST Framework XML](https://jpadilla.github.io/django-rest-framework-xml/) 提供了一个简单的非正式XML格式。它之前直接包含在REST framework 包中,现在被替代为第三方包支持。 516 | 517 | **安装和配置**: 518 | 519 | 使用pip安装: 520 | 521 | ```shell 522 | $ pip install djangorestframework-xml 523 | ``` 524 | 525 | 修改设置: 526 | 527 | ```python 528 | REST_FRAMEWORK = { 529 | 'DEFAULT_PARSER_CLASSES': ( 530 | 'rest_framework_xml.parsers.XMLParser', 531 | ), 532 | 'DEFAULT_RENDERER_CLASSES': ( 533 | 'rest_framework_xml.renderers.XMLRenderer', 534 | ), 535 | } 536 | ``` 537 | 538 | #### JSONP 539 | 540 | 包[REST framework JSONP](https://jpadilla.github.io/django-rest-framework-jsonp/)提供JSONP的渲染支持。它之前直接包含在REST framework 包中,现在被替代为第三方包支持。 541 | 542 | --- 543 | 544 | 警告: 如果你需要跨域的AJAX请求,你通常应该使用更现代化的```CORS```方法代替JSONP。更多详细信息请参阅[CORS文档](../cors.md)。 545 | 546 | jsonp 本质上是一个浏览器hack方法,仅适用于全局可读的API路径,其中GET请求未经身份验证,并且不需要任何用户权限。 547 | 548 | --- 549 | 550 | **安装和配置**: 551 | 552 | 使用pip安装: 553 | 554 | ```shell 555 | $ pip install djangorestframework-jsonp 556 | ``` 557 | 558 | 修改设置: 559 | 560 | ```python 561 | REST_FRAMEWORK = { 562 | 'DEFAULT_RENDERER_CLASSES': ( 563 | 'rest_framework_jsonp.renderers.JSONPRenderer', 564 | ), 565 | } 566 | ``` 567 | 568 | #### MessagePack 569 | 570 | [MessagePack](https://msgpack.org/)是一种快速,高效的二进制序列化格式。Juan Riaza维护着[djangorestframework-msgpack](https://github.com/juanriaza/django-rest-framework-msgpack) 包,它为REST framework提供MessagePack渲染器和解析器支持。 571 | 572 | #### CSV 573 | 574 | 逗号分隔的值是纯文本数据格式,可以轻松导入到电子表格应用中。Mjumbe Poe维护着[djangorestframework-csv](https://github.com/mjumbewu/django-rest-framework-csv)包,它为REST framework提供了CSV渲染器支持。 575 | 576 | #### UltraJSON 577 | 578 | UltraJSON是一个优化的C JSON编码器,可以显著提高JSON渲染速度。Jacob Haslehurst维护着使用UJSON包实现JSON渲染的[drf-ujson-renderer](https://github.com/gizmag/drf-ujson-renderer)包。 579 | 580 | #### 驼峰JSON 581 | 582 | [djangorestframework-camel-case](https://github.com/vbabiy/djangorestframework-camel-case)为REST framework提供了驼峰样式的JSON渲染器和解析器。这使序列化程序可以使用Python风格的下划线字段名,但是在API中显示成Javascript样式的驼峰字段名。它被Vitaly Babiy维护着。 583 | 584 | #### Pandas (CSV, Excel, PNG) 585 | 586 | [Django REST Pandas](https://github.com/wq/django-rest-pandas)提供了一个序列化器和渲染器,通过```Pandas``` DataFrame API提供额外的数据处理和输出。Django REST Pandas包括Pandas风格的CSV文件,Excel表格(包括 .xls 和 .xlsx)以及许多其他格式的渲染器。作为wq 项目的一部分由S. Andrew Sheppard维护着。 587 | 588 | #### LaTeX 589 | 590 | [Rest Framework Latex](https://github.com/mypebble/rest-framework-latex)提供了一个使用Laulatex输出PDF的渲染器。它由Pebble (S/F Software)维护着。 591 | 592 | --- 593 | 594 | 参考链接: 595 | 596 | - https://docs.djangoproject.com/en/2.0/ref/template-response/ 597 | - https://docs.djangoproject.com/en/2.0/topics/http/views/#customizing-error-views 598 | - https://developer.github.com/v3/media/ 599 | - http://www.amundsen.com/media-types/collection/ 600 | - https://jpadilla.github.io/django-rest-framework-yaml/ 601 | - https://jpadilla.github.io/django-rest-framework-xml/ 602 | - https://jpadilla.github.io/django-rest-framework-jsonp/ -------------------------------------------------------------------------------- /api/routers.md: -------------------------------------------------------------------------------- 1 | ## 路由器Routers 2 | 3 | > 资源路由(resource routing)允许我们为资源式控制器快速声明所有常见路由。只需一行代码即可完成资源路由的声明,无需为 index、show、new、edit、create、update 和 destroy 动作分别声明路由。 4 | —— Ruby on Rails文档 5 | 6 | 点击此处, [查看文档](http://edgeguides.rubyonrails.org/routing.html) 7 | 8 | 一些Web框架比如Rails提供了自动确定应该如何为应用程序的URLs映射到逻辑处理功能来处理传入的请求。 9 | 10 | REST framework为Django增加了自动路由的功能,并且为你提供了一个简单的、快速的和一致的方式来根据你的视图逻辑设置一组URLs。 11 | 12 | #### 用法 13 | 14 | 这是一个简单的URL配置,使用了```SimpleRouter```类: 15 | 16 | ```python 17 | from rest_framework import routers 18 | 19 | router = routers.SimpleRouter() 20 | router.register(r'users', UserViewSet) 21 | router.register(r'accounts', AccountViewSet) 22 | urlpatterns = router.urls 23 | ``` 24 | 25 | 方法```register()```必须接受两个参数: 26 | 27 | - ```prefix```: 这组路由的URL前缀。 28 | - ```viewset```: 对应的```viewset```类。 29 | 30 | 当然,你还可以指定一个额外的可选参数: 31 | 32 | - ```base_name```: 基于这个来确定创建的URL的名称。如果没有设置该参数,那么会自动的根据```viewset```中的```queryset```属性自动生成。如果```viewset```中没有定义```queryset```属性,那么在注册路由的时候必须指定该属性。 33 | 34 | 源码位置: ```rest_framework.routers.SimpleRouter```: 35 | 36 | ```python 37 | def get_default_base_name(self, viewset): 38 | """ 39 | If `base_name` is not specified, attempt to automatically determine 40 | it from the viewset. 41 | """ 42 | queryset = getattr(viewset, 'queryset', None) 43 | 44 | assert queryset is not None, '`base_name` argument not specified, and could ' \ 45 | 'not automatically determine the name from the viewset, as ' \ 46 | 'it does not have a `.queryset` attribute.' 47 | 48 | return queryset.model._meta.object_name.lower() 49 | ``` 50 | 51 | 提示: ```base_name```是根据```viewset```中的```queryset```属性来得到当前操作的模型的名称(小写) 52 | 53 | 提示: ```base_name```最后会自动生成```name={basename}-list```,```name={basename}-detail```和```name='{basename}-{methodnamehyphen}'```。 54 | 55 | 上面的示例将产生以下的URL模式: 56 | 57 | - URL pattern: ```^users/$``` 名称: ```'user-list'``` 58 | - URL pattern: ```^users/{pk}/$``` 名称: ```'user-detail'``` 59 | - URL pattern: ```^accounts/$``` 名称: ```'account-list'``` 60 | - URL pattern: ```^accounts/{pk}/$``` 名称: ```'account-detail'``` 61 | 62 | --- 63 | 64 | 注意: ```base_name```参数被用来指定```view name pattern```初始化的一部分。 在上面的例子中, ```user```或者```account```就代表这一部分。 65 | 66 | 通常来说我们不需要指定```base_name```参数。但是,如果你再```viewset```中自定义了```get_queryset```方法,那么```viewset```将不会再由```.queryset```属性。 此时, 如果你尝试去注册你的```viewset```,那么会得到如下错误: 67 | 68 | ```python 69 | 'base_name' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute. 70 | ``` 71 | 72 | 这个错误的意思是,当不能自动确定你的模型的名称的时候,当你注册```viewset```的时候,你需要显示的指定```base_name```参数。 73 | 74 | --- 75 | 76 | ##### 在Django的根URL中 包含 REST framework URLs 77 | 78 | 对于```router```的实例的```.urls```属性是一个简单的、标准的列表, 元素是```URL patterns```。所以说,就可以有多种风格将这些路由添加到你的Django根路由。 79 | 80 | 比如,你可以添加```router.urls```到现有的list: 81 | 82 | ```python 83 | router = routers.SimpleRouter() 84 | router.register(r'users', UserViewSet) 85 | router.register(r'accounts', AccountViewSet) 86 | 87 | urlpatterns = [ 88 | url(r'^forgot-password/$', ForgotPasswordFormView.as_view()), 89 | ] 90 | 91 | urlpatterns += router.urls 92 | ``` 93 | 94 | 或者,你还可以使用Django的```include```函数, 比如: 95 | 96 | ```python 97 | urlpatterns = [ 98 | url(r'^forgot-password/$', ForgotPasswordFormView.as_view()), 99 | url(r'^', include(router.urls)), 100 | ] 101 | ``` 102 | 103 | 当然也可以指定命名空间: 104 | 105 | ```python 106 | urlpatterns = [ 107 | url(r'^forgot-password/$', ForgotPasswordFormView.as_view()), 108 | url(r'^api/', include(router.urls, namespace='api')), 109 | ] 110 | ``` 111 | 112 | 如果你使用了```hyperlinked serializers```和 ```url 命名空间``` ,那么你需要确定```serializers```中的```view_name```正确的反应了你的命名空间。 在上面的例子中,你需要在你的序列化```user detail```视图的超链接字段中指定参数```view_name='api:user-detail'```。 113 | 114 | ```python 115 | class UserSerializer(serializers.HyperlinkedModelSerializer): 116 | # 如果在project/urls.py中引用API的话就不需要设定, 直接使用 view_name="model-detail" 117 | # 如果在project/urls.py中定义了app01/urls.py的namespace为app01,那么这里就必须定义,而且必须是view_name="app01:model-detail"这种格式 118 | url = serializers.HyperlinkedIdentityField(view_name="user-detail") 119 | ``` 120 | 121 | ##### 设置额外的 链接地址 和 动作 122 | 123 | 在```viewset```中的任何方法,只要加上装饰器```@detail_route```或者是```@list_route```之后,就会被路由。比如,在```UserViewSet```中给定一个方法: 124 | 125 | ```python 126 | from myapp.permissions import IsAdminOrIsSelf 127 | from rest_framework.decorators import detail_route 128 | 129 | class UserViewSet(ModelViewSet): 130 | ... 131 | 132 | @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf]) 133 | def set_password(self, request, pk=None): 134 | ... 135 | ``` 136 | 137 | 以下URL pattern将会生成: 138 | 139 | - URL pattern: ```^users/{pk}/set_password/$``` Name: ```'user-set-password'``` 140 | 141 | 如果你不想为你的自定义的动作使用默认的规则生成URL, 你可以使用```url_path```参数定制URL,来代替它。 142 | 143 | 比如,你想要改变我们自定义的动作的URL 为 ```^users/{pk}/change-password/$```, 你可以这样做: 144 | 145 | ```python 146 | from myapp.permissions import IsAdminOrIsSelf 147 | from rest_framework.decorators import detail_route 148 | 149 | class UserViewSet(ModelViewSet): 150 | ... 151 | 152 | @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_path='change-password') 153 | def set_password(self, request, pk=None): 154 | ... 155 | ``` 156 | 157 | 上面的示例,将会生成如下的URL pattern: 158 | 159 | - URL pattern: ```^users/{pk}/change-password/$``` Name: ```'user-change-password'``` 160 | 161 | 如果,你想要改变你自定义动作的默认的 名字, 你可以使用```url_name```参数来定制它。 162 | 163 | 比如, 如果你想要为你的自定义动作,改变名称为 ```'user-change-password'```, 你可以这样: 164 | 165 | ```python 166 | from myapp.permissions import IsAdminOrIsSelf 167 | from rest_framework.decorators import detail_route 168 | 169 | class UserViewSet(ModelViewSet): 170 | ... 171 | 172 | @detail_route(methods=['post'], permission_classes=[IsAdminOrIsSelf], url_name='change-password') 173 | def set_password(self, request, pk=None): 174 | ... 175 | ``` 176 | 177 | 上面的示例,将会生成如下的URL pattern: 178 | 179 | - URL pattern: ```^users/{pk}/set_password/$``` Name: ```'user-change-password'``` 180 | 181 | 当然你还可以同时使用```url_path```和```url_name```参数来生成URL。 182 | 183 | 关于```ViewSet```的文档,[点击这里查看](./viewsets.md)。 184 | 185 | --- 186 | 187 |
188 |
189 |
190 |
191 | 192 | ### API 指南 193 | 194 | #### ```SimpleRouter```类 195 | 196 | 这个路由实例包含了标准的```list```, ```create```, ```retrieve```, ```update```, ```partial_update```和```destroy```路由动作。在```viewset```中使用```@detail_route```和```@list_route```装饰器的额外的方法也可以被路由。 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 |
URL 风格HTTP 方法动作URL 名称
{prefix}/GETlist{basename}-list
POSTcreate
{prefix}/{methodname}/GET, 或者通过方法的参数指定使用`@list_route`装饰器的函数{basename}-{methodname}
{prefix}/{lookup}/GETretrieve{basename}-detail
PUTupdate
PATCHpartial_update
DELETEdestroy
{prefix}/{lookup}/{methodname}/GET, 或者通过方法的参数指定使用`@detail_route`装饰器的函数{basename}-{methodname}
209 | 210 | 默认情况下,使用```SimpleRouter```创建的URL的末尾都会追加一个```/```, 可以通过在初始化路由实例的时候,指定参数```trailing_slash```为```False```可以修改这个行为,比如: 211 | 212 | ```python 213 | router = SimpleRouter(trailing_slash=False) 214 | ``` 215 | 216 | 在URL的末尾带有```/```是Django的常规的行为,但是这种行为在其他的框架中并没有。选择使用哪种风格的URL在很大程度上是一个偏好问题,尽管一些JavaScript框架可能会期望特定的路由风格。 217 | 218 | 路由器会通过匹配任何除了```/```和```.```的字符来进行查找。如果要设置更加宽松或者更加严格的匹配规则,可以在```viewset```中设置```lookup_value_regex```属性。比如,比如你可以将查找设置为有效的UUIDs: 219 | 220 | ```python 221 | class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): 222 | lookup_field = 'my_model_id' 223 | lookup_value_regex = '[0-9a-f]{32}' 224 | ``` 225 | 226 | #### ```DefaultRouter```类 227 | 228 | 这个路由器,跟上面的```SimpleRouter```基本上是一样的。但是这个路由器额外的包含了一个默认的API根视图,这个视图会返回一个包含所有的```list view```的超链接。它还可以为可选的后缀为```.json```的风格生成路由。 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 |
URL 风格HTTP 方法动作URL 名称
[.format]GET自动生成的根视图api-root
{prefix}/[.format]GETlist{basename}-list
POSTcreate
{prefix}/{methodname}/[.format]GET, 或者通过方法的参数指定使用`@list_route`装饰器的函数{basename}-{methodname}
{prefix}/{lookup}/[.format]GETretrieve{basename}-detail
PUTupdate
PATCHpartial_update
DELETEdestroy
{prefix}/{lookup}/{methodname}/[.format]GET, 或者通过方法的参数指定使用`@detail_route`装饰器的函数{basename}-{methodname}
242 | 243 | 和```SimpleRouter```一样,会在URL末尾追加```/```, 然后可以通过初始化路由器的时候,修改参数来改变: 244 | 245 | ```python 246 | router = DefaultRouter(trailing_slash=False) 247 | ``` 248 | 249 | --- 250 | 251 |
252 |
253 |
254 |
255 | 256 | ### 自定义路由 257 | 258 | 实现自定义的路由,并不是经常要做的事情,但是如果你对API的URL结构有特殊的需求,那么这块就会非常的有用。这样,你就可以将URL以可重用的方式封装,以确保不会显式的编写URL pattern。 259 | 260 | 最简答的实现自定义路由的方式是,以现有的路由器类来自定义一个路由器之类。```.routes```属性是用来映射URL pattern和每个```viewset```视图的模板。```.routes```属性是个包含```Router``命名元组的列表。 261 | 262 | 这个```Route```命名元组的参数解释如下: 263 | 264 | - ```url```:一个字符串,表示要路由的URL。可能包含以下几个格式化字符串: 265 | - ```{prefix}```: 这组路由使用的URL前缀 266 | - ```{lookup}```: 用于匹配单个实例的查找字段 267 | - ```{trailing_slash}```: 可能为```/```或者是空字符串,依赖于```trailing_slash```参数 268 | - ```mapping```: HTTP方法名称和```view```方法的映射关系 269 | - ```name```: 可以被Django中的```reverse```来调用的URL的名称。可能包含以下格式化字符串: 270 | - ```{basename}```: URL名字是基于这个来创建的。 具体参考[viewset中设置basename](./viewsets.md) 271 | - ```initkwargs```: 在实例化视图时应该传递的任何附加参数的字典。注意,```.suffix```参数保留用于标识viewset的类型,用于生成视图名称和面包屑导航。 272 | 273 | 源码位置: ```rest_framework.routers.SimpleRouter``` 274 | 275 | ```python 276 | class SimpleRouter(BaseRouter): 277 | 278 | routes = [ 279 | # List route. 280 | Route( 281 | url=r'^{prefix}{trailing_slash}$', 282 | mapping={ 283 | 'get': 'list', 284 | 'post': 'create' 285 | }, 286 | name='{basename}-list', 287 | initkwargs={'suffix': 'List'} 288 | ), 289 | # Dynamically generated list routes. 290 | # Generated using @list_route decorator 291 | # on methods of the viewset. 292 | DynamicListRoute( 293 | url=r'^{prefix}/{methodname}{trailing_slash}$', 294 | name='{basename}-{methodnamehyphen}', 295 | initkwargs={} 296 | ), 297 | # Detail route. 298 | Route( 299 | url=r'^{prefix}/{lookup}{trailing_slash}$', 300 | mapping={ 301 | 'get': 'retrieve', 302 | 'put': 'update', 303 | 'patch': 'partial_update', 304 | 'delete': 'destroy' 305 | }, 306 | name='{basename}-detail', 307 | initkwargs={'suffix': 'Instance'} 308 | ), 309 | # Dynamically generated detail routes. 310 | # Generated using @detail_route decorator on methods of the viewset. 311 | DynamicDetailRoute( 312 | url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', 313 | name='{basename}-{methodnamehyphen}', 314 | initkwargs={} 315 | ), 316 | ] 317 | 318 | def __init__(self, trailing_slash=True): 319 | self.trailing_slash = trailing_slash and '/' or '' 320 | super(SimpleRouter, self).__init__() 321 | ……省略…… 322 | ``` 323 | 324 | #### 自定义动态路由 325 | 326 | 当然你还可以自定义```@list_route```和```@detail_route```装饰器应该被如何路由。它们都包含在```.routes```列表的```DynamicListRoute```和```DynamicDetailRoute```的命名元组中。 327 | 328 | 参数解释如下: 329 | 330 | - ```url```: 表示被路由的URL字符串。 331 | - ```name```: 可以被Django中的```reverse```来调用的URL的名称。 332 | - ```initkwargs```: 在实例化视图时应该传递的任何附加参数的字典。 333 | 334 | 源代码,看上一小节。 335 | 336 | #### 案例 337 | 338 | 在下面的例子中我们将仅仅会路由```list```和```retrieve```动作,并且不会遵循Django的```/```惯例: 339 | 340 | ```python 341 | from rest_framework.routers import Route, DynamicDetailRoute, SimpleRouter 342 | 343 | class CustomReadOnlyRouter(SimpleRouter): 344 | """ 345 | A router for read-only APIs, which doesn't use trailing slashes. 346 | """ 347 | routes = [ 348 | Route( 349 | url=r'^{prefix}$', 350 | mapping={'get': 'list'}, 351 | name='{basename}-list', 352 | initkwargs={'suffix': 'List'} 353 | ), 354 | Route( 355 | url=r'^{prefix}/{lookup}$', 356 | mapping={'get': 'retrieve'}, 357 | name='{basename}-detail', 358 | initkwargs={'suffix': 'Detail'} 359 | ), 360 | DynamicDetailRoute( 361 | url=r'^{prefix}/{lookup}/{methodnamehyphen}$', 362 | name='{basename}-{methodnamehyphen}', 363 | initkwargs={} 364 | ) 365 | ] 366 | ``` 367 | 368 | 接下来,让我们看看,我们的```CustomReadOnlyRouter```将会为我们简单的```viewset```生成怎么样的路由。 369 | 370 | 首先在```view.py```中,代码如下: 371 | 372 | ```python 373 | class UserViewSet(viewsets.ReadOnlyModelViewSet): 374 | """ 375 | viewset 默认提供了标准的动作, 这里我们只定义额外的动作 376 | """ 377 | queryset = User.objects.all() 378 | serializer_class = UserSerializer 379 | lookup_field = 'username' 380 | 381 | @detail_route() 382 | def group_names(self, request, pk=None): 383 | """ 384 | 返回属于给定用户的所有组的名字的列表. 385 | """ 386 | user = self.get_object() 387 | groups = user.groups.all() 388 | return Response([group.name for group in groups]) 389 | ``` 390 | 391 | 在```urls.py```中,代码如下: 392 | 393 | ```python 394 | router = CustomReadOnlyRouter() 395 | router.register('users', UserViewSet) 396 | urlpatterns = router.urls 397 | ``` 398 | 399 | 最后,会生成如下的映射关系: 400 | 401 | URL | HTTP 方法 | 动作 | URL 名称 402 | --- | --- | --- | --- 403 | /users| GET | list | user-list 404 | /users/{username} | GET | retrieve | user-detail 405 | /users/{username}/group-names | GET | group_names | user-group-names 406 | 407 | 更多的其他属性的设置,查看```SimpleRouter```源代码。 408 | 409 | #### 更加高级自定义路由 410 | 411 | 如果你想要提供一个完整的自定义的行为,你可以继承```BaseRouter```类,并且要重写```get_urls(self)```方法。这个方法应该检查注册的```viewset```并返回一个URL pattern列表。可以通过访问```self.registry```来检查注册的```prefix```(前缀), ```viewsets```(视图集) 和 ```basename```。 412 | 413 | 你可能想要覆盖```get_default_base_name(self, viewset)```方法,或者在向路由器注册的```ViewSet```中始终显式的设置```base_name```参数。 414 | 415 | 416 | 源代码位置: ```rest_framework.routers.SimpleRouter``` 417 | 418 | ```python 419 | class BaseRouter(object): 420 | def __init__(self): 421 | self.registry = [] 422 | 423 | def register(self, prefix, viewset, base_name=None): 424 | if base_name is None: 425 | base_name = self.get_default_base_name(viewset) 426 | self.registry.append((prefix, viewset, base_name)) 427 | ……省略…… 428 | 429 | 430 | class SimpleRouter(BaseRouter): 431 | ……省略…… 432 | def get_default_base_name(self, viewset): 433 | """ 434 | 如果 `base_name` 没有指定, 就会尝试自动去确定它, 435 | 从viewset中. 436 | """ 437 | queryset = getattr(viewset, 'queryset', None) 438 | 439 | assert queryset is not None, '`base_name` argument not specified, and could ' \ 440 | 'not automatically determine the name from the viewset, as ' \ 441 | 'it does not have a `.queryset` attribute.' 442 | 443 | return queryset.model._meta.object_name.lower() 444 | ……省略…… 445 | def get_urls(self): 446 | """ 447 | 使用已经注册的 viewsets 来生成 URL patterns 列表. 448 | """ 449 | ret = [] 450 | 451 | for prefix, viewset, basename in self.registry: 452 | lookup = self.get_lookup_regex(viewset) 453 | routes = self.get_routes(viewset) 454 | 455 | for route in routes: 456 | 457 | # Only actions which actually exist on the viewset will be bound 458 | mapping = self.get_method_map(viewset, route.mapping) 459 | if not mapping: 460 | continue 461 | 462 | # Build the url pattern 463 | regex = route.url.format( 464 | prefix=prefix, 465 | lookup=lookup, 466 | trailing_slash=self.trailing_slash 467 | ) 468 | 469 | # If there is no prefix, the first part of the url is probably 470 | # controlled by project's urls.py and the router is in an app, 471 | # so a slash in the beginning will (A) cause Django to give 472 | # warnings and (B) generate URLS that will require using '//'. 473 | if not prefix and regex[:2] == '^/': 474 | regex = '^' + regex[2:] 475 | 476 | initkwargs = route.initkwargs.copy() 477 | initkwargs.update({ 478 | 'basename': basename, 479 | }) 480 | 481 | view = viewset.as_view(mapping, **initkwargs) 482 | name = route.name.format(basename=basename) 483 | ret.append(url(regex, view, name=name)) ## 看这里的最后的结果就是Django的URL pattern 484 | 485 | return ret 486 | ``` 487 | 488 | --- 489 | 490 |
491 |
492 |
493 |
494 | 495 | ### 第三方包 496 | 497 | 以下第三方包也可以用。 498 | 499 | #### DRF Nested Routers 500 | 501 | 第三方包[drf-nested-routers package](https://github.com/alanjds/drf-nested-routers) 用于处理嵌套资源的 路由 和 关系字段。 502 | 503 | #### ModelRouter(wq.db.rest) 504 | 505 | 第三方包[wq.db package](https://wq.io/wq.db)提供了一个高级的[```ModelRouter```](https://wq.io/1.0/docs/router)类。 506 | 507 | #### DRF-extensions 508 | 509 | 第三方包[DRF-extensions](https://chibisov.github.io/drf-extensions/docs/)包提供用于创建[嵌套视图集的路由器](https://chibisov.github.io/drf-extensions/docs/#nested-routes),具有[可定制端点名称](https://chibisov.github.io/drf-extensions/docs/#controller-endpoint-name)的[集合级控制器](https://chibisov.github.io/drf-extensions/docs/#collection-level-controllers)。 510 | 511 | --- 512 | 513 | 参考链接: 514 | 515 | - http://edgeguides.rubyonrails.org/routing.html 516 | - https://github.com/alanjds/drf-nested-routers 517 | - https://wq.io/1.0/docs/router 518 | - https://chibisov.github.io/drf-extensions/docs/#nested-routes 519 | - https://chibisov.github.io/drf-extensions/docs/#collection-level-controllers 520 | - https://chibisov.github.io/drf-extensions/docs/#controller-endpoint-name 521 | --------------------------------------------------------------------------------