# DRF基础笔记

## **1. RESTful API规范**

### 1.1 域名(domain)

```python
# 应该尽量将API部署在专用域名之下。
https://api.example.com
https://www.example.com
```

### 1.2 版本(version)

```python
# 方法一： 版本号放入 URL
http://api.example.com/app/1.0/foo
http://api.example.com/app/2.0/foo
http://api.example.com/app/3.0/foo

# 方法二： 版本号放入 HTTP 头信息中

Accept: version=1.0
Accept: version=2.0
Accept: version=3.0
```

### 1.3 路径(Endpoint)

路径又称"终点"（endpoint），表示API的具体网址，每个网址代表一种资源（resource） 对于一个简洁结构，你应该始终用名词。 此外，利用的HTTP方法可以分离网址中的资源名称的操作。

对于一个简洁结构，你应该始终用名词。 此外，利用的HTTP方法可以分离网址中的资源名称的操作。

```python
GET /products    ：将返回所有产品清单
POST /products   ：将产品新建到集合
GET /products/4  ：将获取产品 4
PATCH（或）PUT /products/4 ：将更新产品
```

### 1.4 HTTP动词

对于资源的具体操作类型，由HTTP动词表示。

常用的HTTP动词有下面四个（括号里是对应的SQL命令）。

* GET（SELECT）：从服务器取出资源（一项或多项）。
* POST（CREATE）：在服务器新建一个资源。
* PUT（UPDATE）：在服务器更新资源（客户端提供改变后的完整资源）。
* DELETE（DELETE）：从服务器删除资源。

还有三个不常用的HTTP动词。

* PATCH（UPDATE）：在服务器更新(更新)资源（客户端提供改变的属性）。
* HEAD：获取资源的元数据。
* OPTIONS：获取信息，关于资源的哪些属性是客户端可以改变的。 下面是一些例子。

```python
GET /zoos：列出所有动物园
POST /zoos：新建一个动物园（上传文件）
GET /zoos/ID：获取某个指定动物园的信息
PUT /zoos/ID：更新某个指定动物园的信息（提供该动物园的全部信息）
PATCH /zoos/ID：更新某个指定动物园的信息（提供该动物园的部分信息）
DELETE /zoos/ID：删除某个动物园
GET /zoos/ID/animals：列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID：删除某个指定动物园的指定动物
```

### 1.5 过滤信息(Filtering)

如果记录数量很多，服务器不可能都将它们返回给用户。API应该提供参数，过滤返回结果。 下面是一些常见的参数。query\_string 查询字符串,地址栏后面问号后面的数据,格式: name=xx\&sss=xxx

```python
?limit=10               ：指定返回记录的数量
?offset=10              ：指定返回记录的开始位置。
?page=2&per_page=100    ：指定第几页，以及每页的记录数。
?sortby=name&order=asc  ：指定返回结果按照哪个属性排序，以及排序顺序。
?animal_type_id=1       ：指定筛选条件
```

### 1.6 状态码(status Codes)

服务器向用户返回的状态码和提示信息，常见的有以下一些（方括号中是该状态码对应的HTTP动词）。

```python
200 OK - [GET]                   ：服务器成功返回用户请求的数据
201 CREATED - [POST/PUT/PATCH]   ：用户新建或修改数据成功。
202 Accepted - [*]               ：表示一个请求已经进入后台排队（异步任务）
204 NO CONTENT - [DELETE]        ：用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]：用户发出的请求有错误，服务器没有进行新建或修改数据的操作
401 Unauthorized - [*]           ：表示用户没有权限（令牌、用户名、密码错误）。
403 Forbidden - [*]               表示用户得到授权（与401错误相对），但是访问是被禁止的。
404 NOT FOUND - [*]              ：用户发出的请求针对的是不存在的记录，服务器没有进行操作，该操作是幂等的。
406 Not Acceptable - [GET]       ：用户请求的格式不可得（比如用户请求JSON格式，但是只有XML格式）。
410 Gone -[GET]                  ：用户请求的资源被永久删除，且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时，发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]  ：服务器发生错误，用户将无法判断发出的请求是否成功。
```

状态码的完全列表参见这里或这里。 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>

### 1.7 错误处理(Error handling)

如果状态码是4xx，服务器就应该向用户返回出错信息。一般来说，返回的信息中将error作为键名，出错信息作为键值即可。

```json
{
    error: "Invalid API key"
}
```

### 1.8 返回结果

针对不同操作，服务器向用户返回的结果应该符合以下规范。

```python
GET /collections：返回资源对象的列表（数组）
GET /collection/ID：返回单个资源对象(json)
POST /collection：返回新生成的资源对象(json)
PUT /collection/ID：返回完整的资源对象(json)
DELETE /collection/ID：返回一个空文档(空字符串)
```

### 1.9 超媒体(Hypermedia API)

RESTful API最好做到Hypermedia（即返回结果中提供链接，连向其他API方法），使得用户不查文档，也知道下一步应该做什么。

比如，Github的API就是这种设计，访问api.github.com会得到一个所有可用API的网址列表。

```json
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}
```

从上面可以看到，如果想获取当前用户的信息，应该去访问api.github.com/user，然后就得到了下面结果。

```json
{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3"
}
```

上面代码表示，服务器给出了提示信息，以及文档的网址。

### 1.10 其他

服务器返回的数据格式，应该尽量使用JSON，避免使用XML

## 2. **Django Rest\_Framework简介与安装**

### 2.1 Django Rest\_Framework简介

核心思想: 缩减编写api接口的代 – DRF

Django REST framework是一个建立在Django基础之上的Web 应用开发框架，可以快速的开发REST API接口应用。在REST framework中，提供了序列化器Serialzier的定义，可以帮助我们简化序列化与反序列化的过程，不仅如此，还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST framework还提供了认证、权限、限流、过滤、分页、接口文档等功能支持。REST framework提供了一个API 的Web可视化界面来方便查看测试接口。

* 中文文档：<https://q1mi.github.io/Django-REST-framework-documentation/#django-rest-framework>
* github: <https://github.com/encode/django-rest-framework/tree/master>
* 英文文档：<https://www.django-rest-framework.org/>

```python
特点:

提供了定义序列化器Serializer的方法，可以快速根据 Django ORM 或者其它库自动序列化/反序列化；
提供了丰富的类视图、Mixin扩展类，简化视图的编写；
丰富的定制层级：函数视图、类视图、视图集合到自动生成 API，满足各种需要；
多种身份认证和权限认证方式的支持；[jwt]
内置了限流系统；
直观的 API web 界面；
可扩展性，插件丰富
```

### 2.2 环境安装与配置

DRF需要以下依赖：

* Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6)
* Django (1.10, 1.11, 2.0)

安装 DRF

```python
pip install django==2.2
pip install djangorestframework
```

## 3. **DRF创建项目流程**

### 3.1 添加rest\_framework应用

在settings.py的INSTALLED\_APPS中添加’rest\_framework’。

```python
INSTALLED_APPS = [
    ...
    'rest_framework',
]
```

项目中如果使用rest\_framework框架实现API接口，主要有以下三个步骤：

* 将请求的数据（如JSON格式）转换为模型类对象
* 操作数据库
* 将模型类对象转换为响应的数据（如JSON格式）

### 3.2 创建模型操作类

```python
class Student(models.Model):
    # 模型字段
    name = models.CharField(max_length=100,verbose_name="姓名",help_text='提示文本：不能为空')
    sex = models.BooleanField(default=1,verbose_name="性别")
    age = models.IntegerField(verbose_name="年龄")
    class_null = models.CharField(max_length=5,verbose_name="班级编号")
    description = models.TextField(max_length=1000,verbose_name="个性签名")

    class Meta:
        db_table="tb_student"
        verbose_name = "学生"
        verbose_name_plural = verbose_name
```

创建数据库(**嫌麻烦，可以使用默认的sqlite3测试**)

```python
create database students charset utf8;
```

主引用中\_\_init\_\_.py设置使用pymysql作为数据库驱动

```python
import pymysql
pymysql.install_as_MySQLdb()
```

settings.py配置文件中设置mysql的账号密码

```python
DATABASES = {
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    # },
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': "students",
        "HOST": "127.0.0.1",
        "PORT": 3306,
        "USER": "root",
        "PASSWORD":"123456",
    }
}
```

终端下，执行数据迁移

```python
python manage.py makemigrations
python manage.py migrate
```

### 3.3 创建序列化器

在students应用目录中新建 `serializers.py` 用于保存该应用的序列化器。 创建一个 `StudentModelSerializer` 用于序列化与反序列化。

```python
from rest_framework import serializers
from .models import Student

# 创建序列化器类，回头会在试图中被调用
class StudentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = "__all__"
```

* model 指明该序列化器处理的数据字段从模型类Student参考生成
* fields 指明该序列化器包含模型类中的哪些字段，'all’指明包含所有字段

### 3.4 写视图

在students应用的views.py中创建视图StudentViewSet，这是一个视图集合

```python
from rest_framework.viewsets import ModelViewSet
from .models import Student
from .serializers import StudentSerializer

# Create your views here.
class StudentViewSet(ModelViewSet):
    queryset = Student.objects.all() # 指明该视图集在查询数据时使用的查询集
    serializer_class = StudentSerializer # 指明该视图在进行序列化或反序列化时使用的序列化器
```

* queryset 指明该视图集在查询数据时使用的查询集
* serializer\_class 指明该视图在进行序列化或反序列化时使用的序列化器

### 3.5 定义路由

在students应用的urls.py中定义路由信息。

```python
from . import views
from rest_framework.routers import DefaultRouter

# 路由列表
urlpatterns = []
router = DefaultRouter()  # 可以处理视图的路由器，自动通过视图来生成增删改查的url路径
router.register('students', views.StudentViewSet)  #students是生成的url前缀，名称随便写， 向路由器中注册视图集

urlpatterns += router.urls  # 将路由器中的所以路由信息追到到django的路由列表中
                            
# urlpatterns = [
#     re_path('^', include(router.urls)),
#     re_path(r'^api-auth/', include('rest_framework.urls',
#             namespace='rest_framework')),
# ]
```

最后把students子应用中的路由文件加载到总路由文件中.

```python
from django.contrib import admin
from django.urls import path,include  #path:直接写路径，re_path:可以写正则匹配

urlpatterns = [
    path('admin/', admin.site.urls),
    path("api/",include("students.urls")),
]
```

### 3.6 运行测试

运行该django项目

在浏览器输入路径，可以看到DRF提供的API Web浏览页面 在页面底部可以提交post请求，添加到数据库 在网址中输入路径/1，可以访问id为1的信息 在网址中输入路径/S/，可以访问删除的接口

![](/files/oe9Xj7LhtXnVWYkUijAk)

## 4. **序列化器–Serializer**

作用：

1. 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串
2. 反序列化,把客户端发送过来的数据,经过request以后变成字典,序列化器可以把字典转成模型
3. 反序列化,完成数据校验功能

定义好Serializer类后，就可以创建Serializer对象了。

### 4.1 Serializer构造方法

```python
Serializer(instance=None, data=empty, **kwarg)
```

说明：

1）用于序列化时，将模型类对象传入instance参数

2）用于反序列化时，将要被反序列化的数据传入data参数

3）除了instance和data参数外，在构造Serializer对象时，还可通过context参数额外添加数据，如

```python
serializer = AccountSerializer(account, context={'request': request})
```

通过context参数附加的数据，可以通过Serializer对象的context属性获取。

1. 使用序列化器的时候一定要注意，序列化器声明了以后，不会自动执行，需要我们在视图中进行调用才可以。
2. 序列化器无法直接接收数据，需要我们在视图中创建序列化器对象时把使用的数据传递过来。
3. 序列化器的字段声明类似于我们前面使用过的表单系统。
4. 开发restful api时，序列化器会帮我们把模型数据转换成字典.
5. drf提供的视图会帮我们把字典转换成json,或者把客户端发送过来的数据转换字典.

### 4.2 序列化功能的使用

1 定义一个序列化器，在应用中创建一个py文件，serializers.py

```python
from rest_framework import serializers

class StudentSerizlizer(serializers.Serializer):
    name = serializers.CharField()
    age = serializers.IntegerField()
    class_null = serializers.CharField()
    description = serializers.CharField()
```

2 写views.py文件

```python
from django.http import JsonResponse
from django.views import View
from .models import Student
from .serializers import StudentSerizlizer

# Create your views here.

class StudentViewSet(View):
    def get(self, request):
        # 查询所有数据
        all = Student.objects.all() 
        
        # 查询一条数据
        one = Student.objects.get(id=1)  
        
        # 多条记录序列化必须加many=True参数
        '''serializer.data是列表里有多个小字典的形式'''
        serializer = StudentSerizlizer(instance=all,many=True)
        
        # 单条记录序列化
        '''serializer.data是一个字典'''
        serializer = StudentSerizlizer(instance=one)  #得到的结果为字典 data


        # 若要序列化非dict对象，需要添加safe=false参数
         # 若要显示中文，需要添加ensure_ascii选项
        return JsonResponse(serializer.data,safe=False,json_dumps_params={'ensure_ascii':False})

```

3 配置路由

```python
from django.urls import path
from student import views

urlpatterns = [
    path('students/', views.StudentView.as_view())
]
```

```python
from django.contrib import admin
from django.urls import path, include #path:直接写路径，re_path:可以写正则匹配

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('student.urls')),
]
```

测试结果：

![](/files/jsRpI40byTrnGGtVJj7K)

### 4.3 反序列化功能的使用

1 定义一个序列化器，在当前应用中新建serializers.py

```python
from rest_framework import serializers

# 自定义校验函数
def check666(val):
    if '666' in val:
        raise serializers.ValidationError('不能光喊6666啊，没有用')
    else:
        return val


class StudentSerializer(serializers.Serializer):
    # validatior代表检查name时，会通过一遍check666函数
    name = serializers.CharField(max_length=4, validators=[check666, ])
    age = serializers.IntegerField(max_value=18)
    class_null = serializers.CharField()

    # required=False,allow_null=True允许字段为空，也就是不用传递过来这个data
    description = serializers.CharField(required=False, allow_null=True)

    # allow_blank=True 允许只为空字符串
    # description = serializers.CharField(allow_blank=True)

    # 局部钩子 validate_字段名：针对单个字段进行校验
    def validate_name(self, val):
        if '777' in val:
            raise serializers.ValidationError('错误！！')
        return val

    # 全局钩子 validate : 针对多个属性数据进行校验
    def validate(self, data):
        print(data)
        age = data.get('age')
        class_null = data.get('class_null')
        if age == class_null:
            raise serializers.ValidationError('错误！！！！')
        return data
```

2 写views.py文件

> 结合 **全局钩子和局部钩子** 验证

```python
from django.http import JsonResponse
from django.views import View
from .serializers import StudentSerializers
from django.views.decorators.csrf import csrf_exempt

# Create your views here.
class StuView(View):
  def get(self, request):
    ''''''

    def post(self, request):
        recv_data = {
            'name': request.POST.get('name'),
            'age': request.POST.get('age'),
            'class_null': request.POST.get('class_null'),
            'description': request.POST.get('description'),
        }

        # 接收传过来的数据，反序列化校验，全部通过得到True
        ser = StudentSerializer(data=recv_data)
        if ser.is_valid():
            print(ser.validated_data)  # 打印正确信息

            # 写入数据库
            ret = Student.objects.create(**ser.validated_data)

            # 序列化，并返回新增的数据
            serializer = StudentSerializer(instance=ret)
            return JsonResponse(serializer.data, safe=False, json_dumps_params={'ensure_ascii': False})

        #  校验失败，返回错误信息
        error = ser.errors  #
        return JsonResponse(data={'msg': error})

    @csrf_exempt  # 取消 csrf token,测试环境中设置取消
    def dispatch(self, *args, **kwargs):
        return super(StudentViewSet, self).dispatch(*args, **kwargs)


```

3 url：

```python
from django.urls import path
from use_unserializers import views
urlpatterns = [
    path('students/',views.StuView.as_view())
```

Tip: is\_valid中的raise\_exception参数

```python
serializer.is_valid(raise_exception=True) # 等于True会主动抛出异常
```

![](/files/Zn5Yf6YYj3ZuVbAlISQN)

### 4.4 反序列化功能校验顺序

执行顺序：

​ 　1.先执行name中CharField参数中的规则

　　2.执行name的局部钩子函数

　　3.执行age的IntegerField参数中的规则

　　4.执行age的局部钩子函数

　　5.执行全局钩子函数

### 4.5 序列化器一些常用参数的说明

常用字段:

| 字段                      | 字段构造方式                                                                                                                                                                                                                                                                                                |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **BooleanField**        | BooleanField()                                                                                                                                                                                                                                                                                        |
| **NullBooleanField**    | NullBooleanField()                                                                                                                                                                                                                                                                                    |
| **CharField**           | CharField(max\_length=None, min\_length=None, allow\_blank=False, trim\_whitespace=True)                                                                                                                                                                                                              |
| **EmailField**          | EmailField(max\_length=None, min\_length=None, allow\_blank=False)                                                                                                                                                                                                                                    |
| **RegexField**          | RegexField(regex, max\_length=None, min\_length=None, allow\_blank=False)                                                                                                                                                                                                                             |
| **SlugField**           | SlugField(max*length=50, min\_length=None, allow\_blank=False) 正则字段，验证正则模式 \[a-zA-Z0-9*-]+                                                                                                                                                                                                            |
| **URLField**            | URLField(max\_length=200, min\_length=None, allow\_blank=False)                                                                                                                                                                                                                                       |
| **UUIDField**           | UUIDField(format='hex\_verbose') format: 1) `'hex_verbose'` 如`"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"` 2） `'hex'` 如 `"5ce0e9a55ffa654bcee01238041fb31a"` 3）`'int'` - 如: `"123456789012312313134124512351145145114"` 4）`'urn'` 如: `"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"` 微软时间戳，通过微秒生成一个随机字符串 |
| **IPAddressField**      | IPAddressField(protocol='both', unpack\_ipv4=False, \*\*options)                                                                                                                                                                                                                                      |
| **IntegerField**        | IntegerField(max\_value=None, min\_value=None)                                                                                                                                                                                                                                                        |
| **FloatField**          | FloatField(max\_value=None, min\_value=None)                                                                                                                                                                                                                                                          |
| **DecimalField**        | DecimalField(max\_digits, decimal\_places, coerce\_to\_string=None, max\_value=None, min\_value=None) max\_digits: 最多位数 decimal\_palces: 小数点位置                                                                                                                                                        |
| **DateTimeField**       | DateTimeField(format=api\_settings.DATETIME\_FORMAT, input\_formats=None)                                                                                                                                                                                                                             |
| **DateField**           | DateField(format=api\_settings.DATE\_FORMAT, input\_formats=None)                                                                                                                                                                                                                                     |
| **TimeField**           | TimeField(format=api\_settings.TIME\_FORMAT, input\_formats=None)                                                                                                                                                                                                                                     |
| **DurationField**       | DurationField()                                                                                                                                                                                                                                                                                       |
| **ChoiceField**         | ChoiceField(choices) choices与Django的用法相同                                                                                                                                                                                                                                                              |
| **MultipleChoiceField** | MultipleChoiceField(choices)                                                                                                                                                                                                                                                                          |
| **FileField**           | FileField(max\_length=None, allow\_empty\_file=False, use\_url=UPLOADED\_FILES\_USE\_URL)                                                                                                                                                                                                             |
| **ImageField**          | ImageField(max\_length=None, allow\_empty\_file=False, use\_url=UPLOADED\_FILES\_USE\_URL)                                                                                                                                                                                                            |
| **ListField**           | ListField(child=, min\_length=None, max\_length=None)                                                                                                                                                                                                                                                 |
| **DictField**           | DictField(child=)                                                                                                                                                                                                                                                                                     |

选项参数

| 参数名称                 | 作用       |
| -------------------- | -------- |
| **max\_length**      | 最大长度     |
| **min\_length**      | 最小长度     |
| **allow\_blank**     | 是否允许为空   |
| **trim\_whitespace** | 是否截断空白字符 |
| **max\_value**       | 最大值      |
| **min\_value**       | 最小值      |

通用参数

| 参数名称                | 说明                         |
| ------------------- | -------------------------- |
| **read\_only**      | 表明该字段仅用于序列化输出，默认False      |
| **write\_only**     | 表明该字段仅用于反序列化输入，默认False     |
| **required**        | 表明该字段在反序列化时必须输入，默认True     |
| **default**         | 反序列化时使用的默认值                |
| **allow\_null**     | 表明该字段是否允许传入None，默认False    |
| **validators**      | 该字段使用的验证器                  |
| **error\_messages** | 包含错误编号与错误信息的字典             |
| **label**           | 用于HTML展示API页面时，显示的字段名称     |
| **help\_text**      | 用于HTML展示API页面时，显示的字段帮助提示信息 |

### 4.6 反序列化的数据保存功能

**方式一:直接在视图中保存**

```python
class StudentView(View):
    ''''''
    def post(self,request):
        recv_data = {
            'name': request.POST.get('name'),
            'age': request.POST.get('age'),
            'class_null': request.POST.get('class_null'),
            'description': request.POST.get('description'),
        }

        # 接收传过来的数据，反序列化校验，全部通过得到True
        ser = StudentSerializer(data=recv_data)
        if ser.is_valid():
            ret = models.Student.objects.create(
                **ser.validated_data
            )
            serializer = StudentSerizlizer(instance=ret)

            return JsonResponse(serializer.data,safe=False,json_dumps_params={'ensure_ascii':False})
        return JsonResponse(data={'msg': ser.errors})
```

**方式二:视图中通过save方法来触发序列化器类中的create方法**

```python
 # 方式2 save+create

# 在序列化器中定义create方法来数据的保存
class StudentSerizlizer(serializers.Serializer):
    name = serializers.CharField(max_length=4,validators=[check666,])
    age = serializers.IntegerField(max_value=18)
    ......
    
    def create(self, validated_data): # 定义create方法
        '''self.validated_data和validated_data结果是相同的,所以直接用validated_data即可'''
        print(validated_data)
        ret = models.Student.objects.create(
            **validated_data
        )
        return ret

# 视图中通过save方法来触发序列化器类中的create方法
class StudentView(View):
    def post(self,request):
        '''略'''
        if ser.is_valid():
            '''通过save()方法去触发序列化器中的create方法'''
            instance = ser.save()  # 得到create方法的返回值
            serializer = StudentSerizlizer(instance=instance)

            return JsonResponse(serializer.data,safe=False,json_dumps_params={'ensure_ascii':False})
```

### 4.7 反序列化的数据更新功能

**在序列化器中定义update方法来数据的更新**

```python
class StudentSerializer(serializers.Serializer):
    
    name = serializers.CharField(max_length=4, validators=[check666, ])
    age = serializers.IntegerField(max_value=18)
  '''''' 略

    # 添加更新动作
    def update(self, instance, validated_data):
        instance.name = validated_data['name']
        instance.age = validated_data['age']
        instance.class_null = validated_data['class_null']
        instance.description = validated_data['description']
        instance.save()
        return instance
```

**视图部分**

```python
from django.http import QueryDict
class StudentViewSet(View):
  '''略'''
    def put(self, request):
        # 获取需要更新的数据
        put = QueryDict(request.body)
        put_str = list(put.items())[0][0]  # 将获取到的 Querydict对象转换成str类型
        put_dict = eval(put_str)           # 将str类型转换成字典
        recv_data = {
            'id': put_dict.get('id'),
            'name': put_dict.get('name'),
            'age': put_dict.get('age'),
            'class_null': put_dict.get('class_null'),
            'description': put_dict.get('description'),
        }

        obj = Student.objects.get(id=recv_data.get('id'))

        '''instance=obj用来指定这个save()触发的是序列化器中的update方法,而不是create方法,data就是你要更新的数据'''
        ser = StudentSerializer(instance=obj, data=recv_data)
        if ser.is_valid():
            ser.save()    # 触发序列化器类的update方法
            return JsonResponse(data={'msg': 'ok'})
        #  校验失败，返回错误信息
        error = ser.errors  #
        return JsonResponse(data={'msg': error})
```

![](/files/FkMYOe8LzJVegZYpbxxT)

**Tip: 如果只更新部分字段**

partial参数默认为False，若partial=True则代表只需要校验传入给序列化器的数据(data)，其他的字段不校验.适用于部分数据更新

**应用场景:**

比如在序列化器中加入定义了五个字段。

如果用户在前端提交数据时，必须要将五个字段都提交，否则就会报错。

但是在现实应用场景下，有很多情况是不需要将五个字段全部提交的。比如数据更新

这个时候想要更改，但是序列化器的代码已经成型了，不太好进行修改。 【eg：提交需要提交5个字段，更新提交2个字段】

```python
class StudentViewSet(View):
  '''略'''
    def put(self, request):
        # 获取需要更新的数据
        put = QueryDict(request.body)
        put_str = list(put.items())[0][0]  # 将获取到的 Querydict对象转换成str类型
        put_dict = eval(put_str)           # 将str类型转换成字典
        
        # 只传入需要更新的字段和必须传入的字段
        recv_data = {
            'name': put_dict.get('name'),                # 只传入校验必须的字段的
            'age': put_dict.get('age'),                  # 只传入校验必须的字段的
            'description': put_dict.get('description'),
        }

        obj = Student.objects.get(name=recv_data.get('name'))
        
        '''partial=True:只需要校验传入给序列化器的数据(data)，其他的字段不校验.适用于部分数据更新'''
        ser = StudentSerializer(data=recv_data, partial=True)
        if ser.is_valid():
            ser.save()
            return JsonResponse(data={'msg': 'ok'})
        
        #  校验失败，返回错误信息
        return JsonResponse(data={'msg': ser.errors})
```

### 4.8 Field字段参数:read\_only和write\_only

> read\_only和write\_only是field字段里面的参数,也是用来对字段做限制的

```python
from rest_framework import serializers

class StudentSerizlizer(serializers.Serializer):
    '''read_only=True,序列化时序列化出该字段数据,反序列化校验时不要校验这个数据'''
    id = serializers.IntegerField(read_only=True)
    
    name = serializers.CharField(max_length=4,)

    '''write_only=True，序列化时不序列化这个字段数据，反序列校验时需要客户端传递这个数据过来进行校验'''
    age = serializers.IntegerField(max_value=18,write_only=True)
    
    class_null = serializers.CharField()

    description = serializers.CharField(allow_blank=True)
```

### 4.9 ModelSerializer的使用(推荐)

序列化与反序列化

**1 定义一个序列化器**，继承serializers.ModelSerializer：

```
from rest_framework import serializers
from students.models import Student

class StudentModelSerializer(serializers.ModelSerializer):
    # 如果模型类序列化器,必须 1.声明本次调用是哪个模型,2.模型里面的哪些字段
    class Meta:
        model = Student
        fields = ["id","name","age","description","sex"]
        # fields = "__all__" # 表示操作模型中的所有字段
        
        # 添加额外的验证选项
        exclude = ['id',] # 排除字段
        extra_kwargs = {
            "sex":{"write_only":True,},
            "id":{"read_only":True,}
        }
```

**2 视图文件**

```python
from django.shortcuts import render
from django.http import JsonResponse

from django.views import View
from .serializers import StudentModelSerializer
from .models import Student

class StudentView(View):
    def get(self,request):
        all = Student.objects.all()
        model_ser = StudentModelSerializer(instance=all,many=True)
        return JsonResponse(model_ser.data, safe=False, json_dumps_params={'ensure_ascii':False})

    def post(self,request):
        print(request.POST)
        obj = StudentModelSerializer(data=request.POST)
        if obj.is_valid():
            print(obj.validated_data)
            obj.save()  # 上面使用的ModelSerializer，所以不需要我们自己写create方法了,自动保存
            return JsonResponse({'msg':'success'})
        print(obj.errors)
        print(obj.validated_data)
        return JsonResponse({'xx':'ssssss'})
```

## 5. **视图**

### 5.1 request对象和response对象

**request对象** 在get方法中request.GET<=>request.query\_params

在post方法中：

当发送的数据格式为…urlencoded类型时，request.POST<=>request.data

当发送过来的是json类型数据时，我们使用request.data属性能够获取到数据，得到的是普通字典类型。（如果是多选数据，不能使用getlist方法获取）

```python
from rest_framework.views import APIView

class UserView(APIView):

    def get(self,request):
        print(request.GET)
        print(request.query_params)
        # request.GET和request.query_params是等价的
        return JsonResponse({'xx':'xxxxx'})

    def post(self,request):
        print(request)
        # 当发送的数据格式为..urlencoded类型时,request.POST和request.data等价
        # print(request.POST.getlist('hobby')) #<QueryDict: {'name': ['小林2'], 'age': ['6']}>
        # 当发送过来的是json类型数据时,我们使用request.data属性能够获取到数据
        print(request.data) #{'username': 'xxxx', 'password': '123'},普通字典类型
        # request.data能够获取到客户端发送过来的json类型数据，但是得到的结果为普通字典类型，但是如果是多选数据，不能使用getlist方法获取

        return JsonResponse({'xx': 'xxxxx'})
```

**response对象**

```python
查看drf的默认配置
from rest_framework import settings

引入response
from rest_framework.response import Response



# 用户配置替换drf内部配置的写法
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (  # 默认响应渲染类
        'rest_framework.renderers.JSONRenderer',  # json渲染器
        'rest_framework.renderers.BrowsableAPIRenderer',  # 浏览器API渲染器
    )
}
```

通过浏览器访问：

![](/files/NJxKa8ZrGdtFx7HVRJJH)

注释浏览器API渲染器（得到的就是一个Json对象）：

![](/files/d4z58Pms3eCN4aZXbVDy)

**response的方法的相关参数**

```python
Response(data, status=None, template_name=None, headers=None, content_type=None)
```

* data： 响应的序列化数据。
* status： 响应的状态代码。默认为200。
* template\_name： 选择 HTMLRenderer 时使用的模板名称。
* headers： 设置 HTTP header，字典类型。
* content\_type： 响应的内容类型，通常渲染器会根据内容协商的结果自动设置，但有些时候需要手动指定。
* Response 可以将内置类型转换成字符串。写到data的位置

**响应状态码**

```python
from rest_framework import status

# 响应状态码方式一
return Response({'xx':'xxxxx'}, status=201)

# 响应状态码方式二
return Response({'xx':'xxxxx'}, status=status.HTTP_201_CREATED)
```

**所有状态码:**

```python
HTTP_100_CONTINUE = 100
HTTP_101_SWITCHING_PROTOCOLS = 101
HTTP_200_OK = 200
HTTP_201_CREATED = 201
HTTP_202_ACCEPTED = 202
HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
HTTP_204_NO_CONTENT = 204
HTTP_205_RESET_CONTENT = 205
HTTP_206_PARTIAL_CONTENT = 206
HTTP_207_MULTI_STATUS = 207
HTTP_208_ALREADY_REPORTED = 208
HTTP_226_IM_USED = 226
HTTP_300_MULTIPLE_CHOICES = 300
HTTP_301_MOVED_PERMANENTLY = 301
HTTP_302_FOUND = 302
HTTP_303_SEE_OTHER = 303
HTTP_304_NOT_MODIFIED = 304
HTTP_305_USE_PROXY = 305
HTTP_306_RESERVED = 306
HTTP_307_TEMPORARY_REDIRECT = 307
HTTP_308_PERMANENT_REDIRECT = 308
HTTP_400_BAD_REQUEST = 400
HTTP_401_UNAUTHORIZED = 401
HTTP_402_PAYMENT_REQUIRED = 402
HTTP_403_FORBIDDEN = 403
HTTP_404_NOT_FOUND = 404
HTTP_405_METHOD_NOT_ALLOWED = 405
HTTP_406_NOT_ACCEPTABLE = 406
HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
HTTP_408_REQUEST_TIMEOUT = 408
HTTP_409_CONFLICT = 409
HTTP_410_GONE = 410
HTTP_411_LENGTH_REQUIRED = 411
HTTP_412_PRECONDITION_FAILED = 412
HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
HTTP_414_REQUEST_URI_TOO_LONG = 414
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
HTTP_417_EXPECTATION_FAILED = 417
HTTP_418_IM_A_TEAPOT = 418
HTTP_422_UNPROCESSABLE_ENTITY = 422
HTTP_423_LOCKED = 423
HTTP_424_FAILED_DEPENDENCY = 424
HTTP_426_UPGRADE_REQUIRED = 426
HTTP_428_PRECONDITION_REQUIRED = 428
HTTP_429_TOO_MANY_REQUESTS = 429
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451
HTTP_500_INTERNAL_SERVER_ERROR = 500
HTTP_501_NOT_IMPLEMENTED = 501
HTTP_502_BAD_GATEWAY = 502
HTTP_503_SERVICE_UNAVAILABLE = 503
HTTP_504_GATEWAY_TIMEOUT = 504
HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
HTTP_506_VARIANT_ALSO_NEGOTIATES = 506
HTTP_507_INSUFFICIENT_STORAGE = 507
HTTP_508_LOOP_DETECTED = 508
HTTP_509_BANDWIDTH_LIMIT_EXCEEDED = 509
HTTP_510_NOT_EXTENDED = 510
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
```

### 5.2 **基于 APIView 视图**

**APIView总结**

1.APIView中封装了自己的request和response

2.django:request.GET对应drf中的request.query\_params

3.django:request.POST对应drf中的request.data

4.APIView中的response不需要写参数safe=False,ensure\_ascii这些东西了,因为这些功能已经封装在response里面了

5.当我们在浏览器上访问一个url，给你回复的是一个界面。如果我们用postman访问url，给你返回的就是json数据。这是因为response中有相关配置导致的。

**APIView实现代码**

```python
# 序列器
from rest_framework import serializers
from app01 import models

class StudentModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Student
        fields = '__all__'
```

```python
# 路由
urlpatterns = [
    path('students/', views.StudentAPIView.as_view()),
    re_path(r'students/(?P<pk>\d+)', views.StudentAPIView.as_view()),
]
```

```python
from .serializers import  StudentSerializer
from .models import Student
from rest_framework.viewsets import ModelViewSet
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class StudentAPIView(APIView):
    # 获取所有数据接口
    # def get(self, request):
    #     all = Student.objects.all()  # 获取数据库中Student表中所有数据
    #     ser = StudentSerializer(instance=all, many=True)  # 将后端数据序列化
    #     return Response(ser.data)  # 将序列化之后的数据传递给前端

    # 获取所有数据接口、获取单条记录
    def get(self, request, pk=None):
        try:
            pk = request.GET.get('pk', default=None)
            if pk:
                one = Student.objects.get(pk=pk)
                ser = StudentSerializer(instance=one)
                return Response(ser.data)
        except:
            return Response(status=status.HTTP_404_NOT_FOUND)

        all = Student.objects.all()  # 获取数据库中Student表中所有数据
        ser = StudentSerializer(instance=all, many=True)  # 将后端数据序列化
        return Response(ser.data)  # 将序列化之后的数据传递给前端

    # 添加单条记录的接口
    def post(self, reqeust):
        data = reqeust.data  # 获取用户在前端输入的数据
        ser = StudentSerializer(data=data)  # 将数据反序列化
        if ser.is_valid():    # 校验
            instance = ser.save()  # 添加新记录对象到数据库
            # 据API规范, 数据保存之后应该显示到前端上, 所以需要将新添加的数据序列化
            ser = StudentSerializer(instance=instance)
            return Response(ser.data, status=status.HTTP_201_CREATED)
        return Response(ser.errors)

    # 更新单条记录接口, 接受 JSON 数据
    def put(self, request):
        data = request.data
        if data:
            try:
                obj = Student.objects.get(pk=data.get('id'))
                if obj:
                    ser = StudentSerializer(
                        instance=obj, data=data, partial=True)
                    if ser.is_valid():
                        instance = ser.save()  # instance为添加的新纪录对象
                        ser = StudentSerializer(instance=instance)
                        return Response(ser.data)
                    return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)
            except:
                return Response(status=status.HTTP_404_NOT_FOUND)
        return Response(status=status.HTTP_400_BAD_REQUEST)

    # 删除单条记录
    def delete(self, request):
        try:
            data = request.data
            Student.objects.get(pk=data.get('id')).delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
        except:
            return Response(status=status.HTTP_400_BAD_REQUEST)
```

### 5.3 **基于 GenericAPIView 视图**

GenerivAPIView继承自APIView方法，**主要增加了操作序列化器和数据库查询的方法。**

**GenericAPIView中的属性和方法**

* **serializer\_class** 指明视图使用的序列化器
* **get\_serializer(self, \*args, \******\******kwargs)** 返回序列化器对象，主要用来提供给Mixin扩展类使用，如果我们在视图中想要获取序列化器对象，也可以直接调用此方法
* **queryset** 指明使用的数据查询集
* **get\_queryset(self)** 返回视图使用的查询集，主要用来提供给Mixin扩展类使用，是列表视图与详情视图获取数据的基础，默认返回`queryset`属性，可以重写
* **get\_object(self)** 返回详情视图所需的模型类数据对象，主要用来提供给Mixin扩展类使用。在视图中可以调用该方法获取详情信息的模型类对象。
* \***get\_serializer\_class(self)** 当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在get\_serializer\_class方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了。

　　　　　　　　　　　　返回序列化器类，默认返回`serializer_class`

```python
# 路由：
from django.urls import path, re_path
from . import views
urlpatterns = [
    path('student2/', views.StudentViews21.as_view()),
    re_path(r'student2/(?P<pk>\d+)/', views.StudentViews.as_view()),
]
```

```python
from rest_framework.generics import GenericAPIView


class StudentViews(GenericAPIView):
    queryset = models.Student.objects.all()  # 指明要使用的数据查询集[必须指定]
    serializer_class = StudentSerializer  # 指明视图要使用的序列化器[可写可不写]

    # 通过get_serializer_class来控制不同条件下，使用不同的序列化器类
    def get_serializer_class(self):
        if self.request.method == 'GET':
            return Student2Serializer
        else:
            return StudentSerializer

    # 获取所有数据接口
    def get(self, request):
        # all_data = models.Student.objects.all()
        # serializer = StudentSerializer(instance=all_data, many=True)
        serializer = self.get_serializer(instance=self.get_queryset(), many=True)
        return Response(serializer.data)

    # 添加一条记录的接口
    def post(self, request):
        data = request.data
        serializer = self.get_serializer(data=data)
        if serializer.is_valid():
            instance = serializer.save()  # instance为添加的新纪录对象
            serializer = self.get_serializer(instance=instance)

            return Response(serializer.data, status=status.HTTP_201_CREATED)

        else:
            print(serializer.errors)
            return Response({'error':'字段错误'})

class StudentViews(GenericAPIView):
    queryset = models.Student.objects.all()
    serializer_class = StudentSerializer

    # 获取单条记录
    def get(self, request, pk):
        # stu_obj = models.Student.objects.get(pk=pk)
        serializer = self.get_serializer(instance=self.get_object())
        return Response(serializer.data)

    # 更新单条记录
    def put(self, request, pk):
        # stu_obj = models.Student.objects.get(pk=pk)
        data = request.data
        serializer = self.get_serializer(instance=self.get_object(), data=data, partial=True)
        if serializer.is_valid():
            # print('>>>',serializer.data)  #在save方法之前，不能调用data属性，serializer.data
            instance = serializer.save()  # instance为添加的新纪录对象
            print(serializer.data)  #之后可以看
            serializer = self.get_serializer(instance=instance)

            return Response(serializer.data)
        else:
            print(serializer.errors)

    # 删除单条记录

    def delete(self, request, pk):

        models.Student.objects.get(pk=pk).delete()
        return Response(None, status=status.HTTP_204_NO_CONTENT)
```

### 5.4 **基于Mixin个视图扩展类视图**

> 提供了几种后端视图（对数据资源进行增删改查）处理流程的实现，如果需要编写的视图属于这五种，则视图可以通过继承相应的扩展类来复用代码，减少自己编写的代码量。
>
> 这五个扩展类需要搭配GenericAPIView父类，因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法。
>
> **换句话说。就是你在视图中的 get post put 等这些方法中不用写里面的代码了，里面的代码相关的操作已经被封装到对应的Mixin中了。**

相比于GenericAPIView，Mixin拓展封装了：调用序列器序列化与反序列化的过程

* ListModelMixin： self.list(request)
* CreateModelMixin： self.create(request)
* RetrieveModelMixin：self.retrieve(request,pk)
* UpdateModelMixin：self.update(request,pk)
* DestroyModelMixin：self.destroy(request,pk)

```python
# views.py
class StudentViews1(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    # 获取所有数据接口
    def get(self, request):
        return self.list(request)

    # 添加一条记录的接口
    def post(self, request):
        return self.create(request)


class StudentView2(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer

    # 获取单条记录
    def get(self, request, pk):
        return self.retrieve(request, pk)

    # 更新单条记录
    def put(self, request, pk):
        return self.update(request, pk)

    # 删除单条记录
    def delete(self, request, pk):
        return self.destroy(request, pk)
```

### ![](/files/RCb3hLNLj5TMrbaEEm6V)

![](/files/w6u5XQ5eyrOt8VP3frPA)

### 5.5 **基于GenericAPIView的视图子类 APIView**

上面的代码还是过于麻烦，因为既要继承**GenericAPIView**又要继承Mixin系列的类。

所以将各自的操作封装成自己的APIView类。用哪个继承哪个。

**而且连函数都不用写了**，在???APIView类中已经有了，所以不用再写了，继承自己的APIView类即可。

```python
class StudentViews1(ListAPIView, CreateAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer


class StudentViews2(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
```

### 5.6 **ViewSet**

> 主要解决问题：之前我们在写视图类的时候，获取多条数据和提交数据放在了一个类中。获取单条数据，更新单条数据，删除多条数据放到了一个类中
>
> 这是因为多条数据操作时不需要指定pk值，而针对单条数据时，需要指定pk值。也就是说需要知道你要操作哪一条数据
>
> 这样我们就很矛盾。我们没法将5个方法放到同一个类中。
>
> 而下面的ViewSet就可以解决这个问题，**将5个方法放到同一个类中**

```python
from rest_framework.viewsets import ViewSet

class Students5View(ViewSet):
    # 获取所有数据接口
    def get_all_student(self,request):  # action
        all_data = models.Student.objects.all()
        serializer = StudentSerializer(instance=all_data,many=True)
        return Response(serializer.data)

    # 添加一条记录的接口
    def add_student(self,request):
        data = request.data
        serializer = StudentSerializer(data=data)
        if serializer.is_valid():
            instance = serializer.save()  #instance为添加的新纪录对象
            serializer = StudentSerializer(instance=instance)

            return Response(serializer.data,status=status.HTTP_201_CREATED)

        else:
            print(serializer.errors)

    def get_one(self,request,pk):
        stu_obj = models.Student.objects.get(pk=pk)
        serializer = StudentSerializer(instance=stu_obj)
        return Response(serializer.data)
```

通过上面代码我们可以看出。\*\*单条数据操作和多条数据被放到了一个类中。\*\*并且方法名也不再局限于必须要使用get post put...这些方法名了。

这是因为在urls.py中的as\_view方法做了一个**请求方法和对应函数名的指定**。

而获取单条数据和获取多条数据能够共存在一个类中，是因为他们处于两个url中。虽然都是get请求过去的，但是他们在不同的url中，会执行各自的视图方法

通过下面url，我们就可以发挥出ViewSet的用处了：

```python
path('students5/', views.Students5View.as_view({'get':'get_all_student','post':'add_student'})),
re_path('students5/(?P<pk>\d+)/', views.Students5View.as_view({'get':'get_one'})),
```

### 5.7 **ViewSet + APIView**

```python
"""发挥下ViewSet的作用"""
from rest_framework.viewsets import ViewSet
from rest_framework.generics import ListAPIView,CreateAPIView,RetrieveAPIView

class Student2ViewSet(ViewSet,ListAPIView,CreateAPIView,RetrieveAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    
    def get_all(self,request):
        return self.list(request)

    def add_student(self,request):
        return self.create(request)

    def get_one(self,request,pk):
        return self.retrieve(request)
```

### 5.8 **GenericViewSet + Mixin**

> **效果和 Viewset + APIView 是相同的**

```python
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin

class Student3ViewSet(GenericViewSet,ListModelMixin,CreateModelMixin,RetrieveModelMixin):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    def get_all(self,request):
        return self.list(request)

    def add_student(self,request):

        return self.create(request)

    def get_one(self,request,pk):
        return self.retrieve(request)
```

### 5.9 **以上方法改良后**

```python
urlpatterns = [
    path("students7/", views.Student4ViewSet.as_view({"get": "list", "post": "create"})),
    re_path("students7/(?P<pk>\d+)/", views.Student4ViewSet.as_view({"get": "retrieve","put":"update","delete":"destroy"})),
]
```

```python
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin

class Student4ViewSet(GenericViewSet,ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
```

### 5.10 **基于ModelViewSet视图(终极版)**

> 上面的代码，在导入和继承的时候太麻烦了，需要写5个Mixin类
>
> 这时出现了ModelViewSet，ModelViewSet同时继承ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin这五个类,这样写起来就更加简单了。

```python
# serializer.py
class StudentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = "__all__"
```

```python
# urls.py
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('student/', views.StudentModelViewSet.as_view({'get': 'list', 'post': 'create'})),
    re_path(r'student/(?P<pk>\d+)/', views.StudentModelViewSet.as_view({'get':'retrieve','put':'update','delete':'destroy'})),

]
```

```python
# views.py
from rest_framework.viewsets import ModelViewSet

class StudentModelViewSet(ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
```

### 5.11 **ReadOnlyViewset(不常用)**

继承自`GenericViewSet`，同时包括了ListModelMixin、RetrieveModelMixin。

### 5.12 **添加自定义动作(action装饰器)**

在视图集中，除了上述默认的方法动作外，还可以添加自定义动作，进行扩展。

举例，比如做一个登录方法login：

```python
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action

class StudentModelViewSet(ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

    # methods 设置当前方法允许哪些http请求访问当前视图方法
    # detail 设置当前视图方法是否是操作一个数据
    # detail为True，表示路径名格式应该为 router_stu/{pk}/login/
    @action(methods=['get'], detail=True)
    def login(self,request):  # 这个就可以称为自定义的action动作
        """学生登录功能"""
        return Response({"message":"登录成功"})
    
    # detail为False 表示路径名格式应该为 router_stu/get_new_5/
    @action(methods=['put'], detail=False)
    def get_new_5(self,request):
        """获取最近添加的5个学生信息"""
        print(self.action) # 获取本次请求的视图方法名
        
        
'''通过路由访问到当前方法中.可以看到本次的action就是请求的方法名''
```

```python
# urls.py
urlpatterns = [
    path("students/", views.StudentModelViewSet.as_view({"get": "list", "post": "create"})),
    re_path("students/(?P<pk>\d+)/",
            views.StudentModelViewSet.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),

    path("students/login/",views.StudentModelViewSet.as_view({"get":"login"}))

]
```

## 6. **路由:Routers**

### 6.1 如何添加路由数据

如何添加路由数据,并且和视图函数做关联:

```python
from django.urls import path, re_path
from . import views
urlpatterns = [
    ...
]

"""使用drf提供路由类router给视图集生成路由列表"""
# 实例化路由类
# drf提供一共提供了两个路由类给我们使用,他们用法一致,功能几乎一样
from rest_framework.routers import DefaultRouter
router = DefaultRouter()

# 注册视图集
# router.register("路由前缀",视图集类)
router.register("router_stu",views.StudentModelViewSet)

# 把生成的路由列表追加到urlpatterns
urlpatterns += router.urls
```

上面的代码**就成功生成了路由地址\[增/删/改/查一条/查多条的功能]**，但是不会自动帮我们在视图集自定义方法的路由。

所以我们如果也要给自定义方法生成路由，则需要进行action动作的声明。

### 6.2 **在视图集中附加action声明**

在视图集中，如果想要让Router自动帮助我们为自定义的动作生成路由信息，需要使用`rest_framework.decorators.action`装饰器。

以action装饰器装饰的方法名会作为action动作名，与list、retrieve等同。

action装饰器可以接收两个参数：

* **methods**: 声明该action对应的请求方式，列表传递
* **detail：**

  声明该action的路径是否与单一资源对应，及是否是 xxx//action方法名/

  * True 表示路径格式是`xxx/<pk>/action方法名/`
  * False 表示路径格式是`xxx/action方法名/`

```python
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action

class StudentModelViewSet(ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer

    # methods 设置当前方法允许哪些http请求访问当前视图方法
    '''detail 设置当前视图方法是否是操作一个数据'''
    # detail为True，表示路径名格式应该为 router_stu/{pk}/login/
    @action(methods=['get'], detail=True)
    def login(self, request,pk):
        """登录"""
        ...

    # detail为False 表示路径名格式应该为 router_stu/get_new_5/
    @action(methods=['put'], detail=False)
    def get_new_5(self, request):
        """获取最新添加的5个学生信息"""
        ...
```

### 6.3 **DefaultRouter和SimpleRouter的区别(了解)**

DefaultRouter与SimpleRouter的区别是，DefaultRouter会多附带一个默认的API根视图，返回一个包含所有列表视图的超链接响应数据。

比如使用DefaultRouter时你访问一下`http://127.0.0.1:8001/router_stu/` 会看到一个页面。

而SimpleRouter会报错，一般都需要有个查看所有接口的页面，所以我们基本都是用的是DefaultRouter

#### 附:APIView思维导图

![](/files/7ZEJnxSTDKS6uQaBWEQ8)

#### 6.5 附:drf的执行流程图

![](/files/LF66f9myUPQM1zbzAyZF)

参考： <https://www.cnblogs.com/libolun/p/13861557.html>

## 7. 认证/权限/限流/过滤/排序/分页/异常处理

### 7.1 认证 Authentication

可以在配置文件中配置全局默认的认证方案

```python
from rest_framework import settings

'''在settings配置文件中，我们可以进行下面的配置来覆盖默认配置'''
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
      # 哪个写在前面，优先使用哪个认证
        
        'rest_framework.authentication.SessionAuthentication',  # session认证，admin后台其实就使用的session认证，其实接口开发很少用到session认证，所以我们通过配置可以改为其他认证，比如后面项目里面我们用到jwt，JSON WEB TOKEN认证，或者一些配合redis的认证
        
        'rest_framework.authentication.BasicAuthentication',   # 基本认证，工作当中可能一些测试人员会参与的话，他们会将一些认证数据保存在内存当中，然后验证的，我们基本上用不上
    )
}
```

也可以在每个视图中通过设置authentication\_classes属性来设置，比如说我们很多接口的数据都是可以让别人获取数据的，

但是有可能有些接口是调用给别人网站的，有可能到时候我们就需要一些单独的认证了

```python
# views.py
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView

class ExampleView(APIView):
    # 类属性
    authentication_classes = [SessionAuthentication, BasicAuthentication] # 也可以写成元组形式的,到时候我们使用我们自己开发的认证组件的时候，就需要自己写一个认证组件类，然后写在列表中来使用
    ...
```

认证失败会有两种可能的返回值：

* 401 Unauthorized 未认证
* 403 Permission Denied 权限被禁止

### 7.2 自定义认证

```python
from rest_framework.authentication import BaseAuthentication      # 引入基础的认证rest_framework基础的认证类
from rest_framework.exceptions import AuthenticationFailed        # 引入认证失败的错误

class APIAuth(BaseAuthentication):
    def authenticate(self, request):
        print(request) 

        if 1:
            return 'xx','oo'  # request.user = 'xx'  request.auth = 'oo'

        else:
            raise AuthenticationFailed('认证失败')
```

全局使用，settings配置文件中使用

```python
REST_FRAMEWORK = {

    'DEFAULT_AUTHENTICATION_CLASSES': (
        #哪个写在前面，优先使用哪个认证
        'use_view.utils.auth.Auth', # 路径，配置的是全局生效。在setting文件中注册该自定义的类
        'rest_framework.authentication.SessionAuthentication', 
        'rest_framework.authentication.BasicAuthentication', 
    ),
}
```

局部视图中使用

```python
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from four.utils.auth import APIAuth

class AuthAPIView(APIView):
    authentication_classes = [APIAuth,]         # 自定义的认证，在本视图中生效
    def get(self,request):
        print('>>>>',request.user)  # AnonymousUser  匿名用户，假用户
        print('>>>>',request.auth)  # AnonymousUser  匿名用户，假用户
        #>>>> root
        return Response({'msg':'hello'})
```

### 7.3 权限:Permissions

权限控制可以限制用户对于视图的访问和对于具体数据对象的访问。

* 在执行视图的dispatch()方法前，会先进行视图访问权限的判断
* 在通过get\_object()获取具体对象时，会进行模型对象访问权限的判断

全局设置默认的权限管理类

```python
REST_FRAMEWORK = {
    ....
    
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated', # 登录状态下才能访问我们的接口，可以通过退出admin后台之后，你看一下还能不能访问我们正常的接口就看到效果了
    )
}
```

局部设置实例

```python
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import RetrieveAPIView

class StudentAPIView(RetrieveAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]             # 设置当前视图只有认证的用户才能访问
```

* AllowAny 允许所有用户，未指明时默认为允许所有用户
* IsAuthenticated 仅通过认证的用户
* IsAdminUser 仅管理员用户（可以通过admin创建一个用户进行测试）
* IsAuthenticatedOrReadOnly 已经登陆认证的用户可以对数据进行增删改操作，没有登陆认证的只能查看数据。

![](/files/JknWjcHwDgTng2y0yd8f)

### 7.4 自定义权限

如需自定义权限，需继承rest\_framework.permissions.BasePermission父类，并实现以下两个任何一个方法或全部

* `.has_permission(self, request, view)`

  是否可以访问视图， view表示当前视图对象
* `.has_object_permission(self, request, view, obj)`

  是否可以访问数据对象， view表示当前视图， obj为数据对象

例如在当前子应用下，创建一个权限文件permissions.py中声明自定义权限类:

```python
from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):
    def has_permission(self, request, view):
        if request.user.username == 'xx':
            return True
        return False
```

视图函数

```python
from .permissions import MyPermission
from rest_framework.viewsets import ModelViewSet
from .serializer import StudentModelSerializer

class StudentViewSet(ModelViewSet):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    permission_classes = [MyPermission]
```

![](/files/rVH5gzFGDuYfJlTuQzLX)

### 7.5 限流:Throttling

可以对接口访问的频次进行限制，以减轻服务器压力。

一般用于付费购买次数,投票等场景使用.

**全局配置**

```python
REST_FRAMEWORK = {
  
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle', # 匿名用户，未登录的
        'rest_framework.throttling.UserRateThrottle', # 经过登录之后的用户
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/m',
        'user': '1000/m',
    }
}

# {'s': 1, 'm': 60, 'h': 3600, 'd': 86400} m表示分钟，可以写m，也可以写minute
```

DEFAULT\_THROTTLE\_RATES 可以使用 second, minute, hour 或day来指明周期。

**仅局部配置**

```python
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView

class ExampleView(APIView):
    throttle_classes = (UserRateThrottle,)  # 局部配置
    ...
```

![](/files/D0bBJSjtESIav8ljuYnp)

### 7.6 可选择的限流类

1） AnonRateThrottle

限制所有匿名未认证用户，使用IP区分用户。

使用`DEFAULT_THROTTLE_RATES['anon']` 来设置频次

2）UserRateThrottle

限制认证用户，使用User id 来区分。

使用`DEFAULT_THROTTLE_RATES['user']` 来设置频次

3）ScopedRateThrottle (待定...)

限制用户对于每个视图的访问频次，使用ip或user id,先找的用户id，没有设置用户id的话就会使用ip地址。

例如： 全局

```python
全局配置中设置访问频率

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle', # 匿名用户，未登录的
        'rest_framework.throttling.UserRateThrottle', # 经过登录之后的用户
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '3/minute',
        'user': '10/minute'
    }
}
```

```python
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import RetrieveAPIView
from rest_framework.throttling import UserRateThrottle

class StudentAPIView(RetrieveAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]
    throttle_classes = (UserRateThrottle,)
```

ScopedRateThrottle 局部使用示例

```python
# settings.py内容
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle', # 匿名用户，未登录的
        'rest_framework.throttling.UserRateThrottle', # 经过登录之后的用户
    ),
    'DEFAULT_THROTTLE_RATES': {
        'xx': '3/minute',
        'oo': '10/minute'
    }
}
    
# views.py内容

from rest_framework.throttling import ScopedRateThrottle

class StudentAPIView(ListAPIView):
    queryset = models.Student.objects.all()
    serializer_class = StudentModelSerializer
    throttle_classes = [ScopedRateThrottle,]
    throttle_scope = 'xx'

class StudentAPI2View(ListAPIView):
    queryset = models.Student.objects.all()
    serializer_class = StudentModelSerializer
    throttle_classes = [ScopedRateThrottle, ]
    throttle_scope = 'oo'
    
# urls.py内容
    path(r'students/',views.StudentAPIView.as_view()),
    path(r'students2/',views.StudentAPI2View.as_view()),
```

### 7.7 过滤:Filtering

对于列表数据可能需要根据字段进行过滤，我们可以通过添加django-fitlter扩展来增强支持。

```python
pip install django-filter  
```

在配置文件中增加过滤后端的设置：

```python
INSTALLED_APPS = [
    ...
    'django_filters',  # 需要注册应用，
]

REST_FRAMEWORK = {
    ...
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
```

在视图中添加 **filter\_fields** 属性，指定可以过滤的字段

```python
class StudentListView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
    filter_fields = ('age', 'sex')

# 127.0.0.1:8000/four/students/?sex=1
```

### 7.8 排序

1. 添加 **filter\_backends** 属性(使用OrderingFilter过滤器)
2. 指定字段，通过 \*\*ordering\_fields \*\*属性指定需要按某字段排序

```python
from rest_framework.filters import OrderingFilter

class StudentListView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
    filter_backends = [OrderingFilter]
    filter_fields = ('age', 'sex')
    
# 127.0.0.1:8000/books/?ordering=-age 
# 必须是ordering=某个值
# -id 表示针对id字段进行倒序排序
# id  表示针对id字段进行升序排序
```

### 7.9 排序 + 过滤

如果需要在过滤以后再次进行排序，则需要两者结合!

> 需要使用 **DjangoFilterBackend**

```python
from rest_framework.generics import ListAPIView
from students.models import Student
from .serializers import StudentModelSerializer
from django_filters.rest_framework import DjangoFilterBackend # 需要使用一下它才能结合使用
class Student3ListView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    filter_fields = ('age', 'sex')
    
    # 因为filter_backends是局部过滤配置，局部配置会覆盖全局配置,所以需要重新把过滤组件核心类再次声明,
    # 否则过滤功能会失效
    filter_backends = [OrderingFilter,DjangoFilterBackend]
    
    ordering_fields = ('id', 'age')
    
    
    # 针对的是继承的类中的list方法
    
# 127.0.0.1:8000/books/?sex=1&ordering=-age 
```

### 7.10 分页:Pagination

REST framework提供了分页的支持。

我们可以在配置文件中设置全局的分页方式

```python
REST_FRAMEWORK = {
  # 全局分页，一旦设置了全局分页，那么我们drf中的视图扩展类里面的list方法提供的列表页都会产生分页的效果。所以一般不用全局分页
    'DEFAULT_PAGINATION_CLASS':  'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 100  # 每页最大数据量
}
```

也可通过自定义Pagination类，来为视图添加不同分页行为。在视图中通过`pagination_class`属性来指明。

```python
class LargeResultsSetPagination(PageNumberPagination):
    page_size = 1000  # 默认每页显示多少条
    #127.0.0.1:8001/students/?page=5&page_size=10
    
    page_size_query_param = 'page_size'  #　自定义页码的参数
    max_page_size = 10000　　　　　　　　　# 客户端请求数量最多不能超过的数值
    
class BookDetailView(RetrieveAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    pagination_class = LargeResultsSetPagination
```

注意：如果在视图内关闭分页功能，只需在视图内设置

```python
pagination_class = None
```

![](/files/fhc9yrgu3wVII3bQFol7)

####

#### 7.10.1 **PageNumberPagination**

前端访问网址形式

```
GET  http://127.0.0.1:8000/students/?page=4
```

可以在子类中定义的属性：

* page\_size 每页数目
* page\_query\_param 前端发送的页数关键字名，默认为"page"
* page\_size\_query\_param 前端发送的每页数目关键字名，默认为None
* max\_page\_size 前端最多能设置的每页数量

```python
# 声明分页的配置类
from rest_framework.pagination import PageNumberPagination
class StandardPageNumberPagination(PageNumberPagination):
    # 默认每一页显示的数据量
    page_size = 2
    # 允许客户端通过get参数来控制每一页的数据量
    page_size_query_param = "size"
    max_page_size = 10  # 客户端通过size指定获取数据的条数时,最大不能超过多少
    # 自定义页码的参数名
    page_query_param = "p"

class StudentAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    pagination_class = StandardPageNumberPagination

# 127.0.0.1/four/students/?p=1&size=5
```

#### 7.10.2 LimitOffsetPagination

前端访问网址形式：其实就是通过偏移量来取数据

```
GET http://127.0.0.1/four/students/?limit=100&offset=400  # 从下标为400的记录开始，取100条记录
```

可以在子类中定义的属性：

* default\_limit 默认限制，每页数据量大小，默认值与`PAGE_SIZE`设置一致
* limit\_query\_param limit参数名，默认'limit' ， 可以通过这个参数来改名字
* offset\_query\_param offset参数名，默认'offset' ，可以通过这个参数来改名字
* max\_limit 最大limit限制，默认None， 无限制

```python
from rest_framework.pagination import LimitOffsetPagination

class StandardLimitOffsetPagination(LimitOffsetPagination):
    # 默认每一页查询的数据量,类似上面的page_size
    default_limit = 2
    limit_query_param = "size"  # 默认是limit
    offset_query_param = "start"  # 默认是offset

class StudentAPIView(ListAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentModelSerializer
    # 调用页码分页类
    # pagination_class = StandardPageNumberPagination
    # 调用查询偏移分页类
    pagination_class = StandardLimitOffsetPagination
```

![](/files/r32tuYPpRlTYAqY9xNNH)

###

### 7.11 异常处理:Exception

#### 7.11.1 自定义异常的捕获

先看一个简单的实例：

视图：

```python
class ApiError(Exception):
    pass

class Test(APIView):
    def get(self,request,pk):
        try:
            one = models.Student.objects.get(pk=pk)
        except models.Student.DoesNotExist:
            raise ApiError('查询出错！！！')
        ser = StudentModelSerializer(instance=one)
        return Response(ser.data)
```

当索引超出范围后，会抛出错误：

![](/files/yslTYjEJpy79K8MV2lto)

在REST framework中提供了异常处理，我们可以自定义异常处理函数，来捕获异常反馈前端

1 在应用下新建一个文件exceptions.py，

```python
from rest_framework.views import exception_handler
from .views import ApiError
from rest_framework.response import Response

def custom_exception_handler(exc, context): # 自定义的错误处理函数
    # 先调用REST framework默认的异常处理方法获得标准错误响应对象
    response = exception_handler(exc, context) #这个函数是drf提供的，它处理了一些错误，但是如果它处理不了的，它会返回None，所以，如果是None的话，我们需要自己来处理错误
    
    # 在此处补充自定义的异常处理
    if response is None:
        if isinstance(exc,ApiError):
            #这里就可以记录错误信息了，一般记录到文件中，可以使用日志系统来进行记录
            return Response({'msg':'自定义API错误了'})
            #response.data['status_code'] = response.status_code
    return response
```

2 在setting文件中配置REST\_FRAMEWORK

```python
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
```

这样就完成了自定义异常的捕获：

![](/files/LWyvrFpekajcainZ8KmU)

同理，补充上处理关于数据库的异常：

```python
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework import status
from django.db import DatabaseError

def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)

    if response is None:
        view = context['view'] # 出错的方法或者函数名称
        if isinstance(exc, DatabaseError):
            print('[%s]: %s' % (view, exc))
            response = Response({'detail': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)

    return response

```

#### 7.11.2 REST framework默认异常

* APIException 所有异常的父类
* ParseError 解析错误
* AuthenticationFailed 认证失败
* NotAuthenticated 尚未认证
* PermissionDenied 权限决绝
* NotFound 未找到
* MethodNotAllowed 请求方式不支持
* NotAcceptable 要获取的数据格式不支持
* Throttled 超过限流次数
* ValidationError 校验失败

也就是说，上面列出来的异常不需要我们自行处理了，很多的没有在上面列出来的异常，就需要我们在自定义异常中自己处理了。

## 8. 自动生成接口文档

### 8.1 安装依赖

```shell
pip3 install coreapi
```

### 8.2 配置路由

```python
from django.urls import path, include #path:直接写路径，re_path:可以写正则匹配
from rest_framework.documentation import include_docs_urls

urlpatterns = [
    ...
    path('docs/', include_docs_urls(title='站点页面标题'))
]
```

### 8.3 配置setting文件

```python
REST_FRAMEWORK = {
    ...
    'DEFAULT_SCHEMA_CLASS': "rest_framework.schemas.AutoSchema",

}
```

这样一个接口文档就自动生成了。

### 8.4 文档描述说明的定义位置

1） 单一方法的视图，可直接使用类视图的文档字符串，如

```python
class BookListView(generics.ListAPIView):
    """
    get: 返回所有图书信息.
    post: 添加记录
    """
    #注意，这是在类中声明的注释，如果在方法中你声明了其他注释，会覆盖这个注释的
```

2）包含多个方法的视图，在类视图的文档字符串中，分开方法定义，如

```python
class BookListCreateView(generics.ListCreateAPIView):
    """
    get:
    返回所有图书信息.

    post:
    新建图书.
    """
```

3）对于视图集ViewSet，仍在类视图的文档字符串中分开定义，但是应使用action名称区分，如

```python
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    """
    list:
    返回图书列表数据

    retrieve:
    返回图书详情数据

    latest:
    返回最新的图书数据

    read:
    修改图书的阅读量
    """
```

![](/files/GEHRib98l1Ij5PHtv2KD)

4） 视图集ViewSet中的 retrieve 名称，在接口文档网站中叫做read

![](/files/pNTkhUpw6uaHM9jzgFMS)

5）参数的Description需要在模型类或序列化器类的字段中以help\_text选项定义

![](/files/jzTW4vZXcc55zfhykdbH)

或 注意，如果你多个应用使用同一个序列化器，可能会导致help\_text的内容显示有些问题，小事情

```python
class StudentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = "__all__"
        extra_kwargs = {
            'age': {
                'required': True,
                'help_text': '年龄'
            }
        }
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://close.gitbook.io/yun-wei-bi-ji/python/drf/drf-ji-chu-bi-ji.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
