DRF
总体设计框架流程
DRF大体的工作流程如下图:
其中:这里的Request不再是Django默认的HttpRequest对象,而是REST Framework提供的扩展了HttpRequest类的Request类对象。
1. Web应用模式
在开发Web应用中,有两种应用模式:
- 前后端不分离[客户端看到的内容和所有界面效果都是由服务端提供出来的。]
- 前后端分离【把前端的界面效果(html,css,js分离到另一个服务端,python服务端只需要返回数据即可)】
前端形成一个独立的网站,服务端构成一个独立的网站
2. api接口
应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址或者一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。
当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个问题,api接口往往都是一个函数、类方法、或者url或其他网络地址,不断是哪一种,当api接口编写过程中,我们都要考虑一个问题就是这个接口应该怎么编写?接口怎么写的更加容易维护和清晰,这就需要大家在调用或者编写api接口的时候要有一个明确的编写规范!!!
为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们都需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少客户端和服务端双方之间的合作成本。
目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。
RPC( Remote Procedure Call ): 翻译成中文:远程过程调用[远程服务调用]. 从字面上理解就是访问/调用远程服务端提供的api接口。这种接口一般以服务或者过程式代码提供。
-
服务端提供一个唯一的访问入口地址:http://api.xxx.com/ 或 http://www.xx.com/api 或者基于其他协议的地址
-
客户端请求服务端的时候,所有的操作都理解为动作(action),一般web开发时,对应的就是HTTP请求的post请求
-
通过请求体参数,指定要调用的接口名称和接口所需的参数
action=get_all_student&class=301&sex=1
m=get_all_student&sex=1&age=22
command=100&sex=1&age=22
rpc接口多了,对应函数名和参数就多了,前端在请求api接口时难找.对于年代久远的rpc服务端的代码也容易出现重复的接口
restful: 翻译成中文: 资源状态转换.(表征性状态转移)
-
把服务端提供的所有的数据/文件都看成资源, 那么通过api接口请求数据的操作,本质上来说就是对资源的操作了.
因此,restful中要求,我们把当前接口对外提供哪种资源进行操作,就把资源的名称写在url地址。
-
web开发中操作资源,最常见的最通用的无非就是增删查改,所以restful要求在地址栏中声明要操作的资源是什么。然后通过http请求动词来说明对该资源进行哪一种操作.
POST http://www.xxx.com/api/students/ 添加学生数据
GET http://www.xxx.com/api/students/ 获取所有学生
GET http://www.xxx.com/api/students// 获取id=pk的学生
DELETE http://www.xxx.com/api/students// 删除id=pk的一个学生
PUT http://www.xxx.com/api/students// 修改一个学生的全部信息 [id,name,sex,age,]
PATCH http://www.xxx.com/api/students// 修改一个学生的部分信息[age]
也就是说,我们仅需要通过url地址上的资源名称结合HTTP请求动作,就可以说明当前api接口的功能是什么了。
restful是以资源为主的api接口规范,体现在地址上就是资源就是以名词表达。
rpc则以动作为主的api接口规范,体现在接口名称上往往附带操作数据的动作。
3. RESTful API规范
REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中。
RESTful是一种专门为Web 开发而定义API接口的设计风格,尤其适用于前后端分离的应用模式中。
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。
而对于数据资源分别使用POST、DELETE、GET、UPDATE等请求动作来表达对数据的增删查改。
GET | /students | 获取所有学生 |
---|---|---|
请求方法 | 请求地址 | 后端操作 |
POST | /students | 增加学生 |
GET | /students/ | 获取编号为pk的学生 |
PUT | /students/ | 修改编号为pk的学生 |
DELETE | /students/ | 删除编号为pk的学生 |
restful规范是一种通用的规范,不限制语言和开发框架的使用。事实上,我们可以使用任何一门语言,任何一个框架都可以实现符合restful规范的API接口。
参考文档:http://www.runoob.com/w3cnote/restful-architecture.html
幂等性
接口实现过程中,会存在幂等性。所谓幂等性是指代客户端发起多次同样请求时,是否对于服务端里面的资源产生不同结果。如果多次请求,服务端结果还是一样,则属于幂等接口,如果多次请求,服务端产生结果是不一样的,则属于非幂等接口。
请求方式 | 是否幂等 | 是否安全 |
---|---|---|
GET | 幂等 | 安全 |
POST | 不幂等 | 不安全 |
PUT/PATCH | 幂等 | 不安全 |
DELETE | 幂等 | 不安全 |
4. 序列化
api接口开发,最核心最常见的一个代码编写过程就是序列化,所谓序列化就是把数据转换格式。常见的序列化方式:
json,pickle,base64,….
序列化可以分两个阶段:
序列化: 把我们识别的数据转换成指定的格式提供给别人。
例如:我们在django中获取到的数据默认是模型对象,但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人。
反序列化:把别人提供的数据转换/还原成我们需要的格式。
例如:前端js提供过来的json数据,对于python而言json就是字符串,我们需要进行反序列化换成字典,然后接着字典再进行转换成模型对象,这样我们才能把数据保存到数据库中。
5. Django Rest_Framework
核心思想: 大量缩减编写api接口的代码
Django REST framework是一个建立在Django基础之上的Web 应用开发框架,可以快速的开发REST API接口应用。在REST framework中,提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程,不仅如此,还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST framework还提供了认证、权限、限流、过滤、分页、接口文档等功能支持。REST framework提供了一个API 的Web可视化界面来方便查看测试接口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nQhLtP81-1683971640693)(http://www.yuan316.com/post/DRF/assets/drf_logo-16318493501509.png)]
中文文档:https://q1mi.github.io/Django-REST-framework-documentation/#django-rest-framework
github: https://github.com/encode/django-rest-framework/tree/master
特点
- 提供了定义序列化器Serializer的方法,可以快速根据 Django ORM 或者其它库自动序列化/反序列化;
- 提供了丰富的类视图、Mixin扩展类,简化视图的编写;
- 丰富的定制层级:函数视图、类视图、视图集合到自动生成 API,满足各种需要;
- 多种身份认证和权限认证方式的支持;[jwt]
- 内置了限流系统;
- 直观的 API web 界面;【方便我们调试开发api接口】
- 可扩展性,插件丰富
6. 环境安装与配置
DRF需要以下依赖:
-
Python (3.5 以上)
-
Django (2.2 以上)
DRF是以Django子应用的方式提供的,所以我们可以直接利用已有的Django环境而无需从新创建。(若没有Django环境,需要先创建环境安装Django)
6.1 安装DRF
前提是已经安装了django,建议安装在虚拟环境
# conda create -n drfdemo python=3.8
# pip install django==3.2.4 -i https://pypi.douban.com/simple
pip install djangorestframework -i https://pypi.douban.com/simple
# 因为我们需要接下来,需要开发api接口肯定要操作数据库,所以安装pymysql
pip install pymysql -i https://pypi.douban.com/simple
6.1.1 创建django项目
cd ~/Desktop
django-admin startproject drfdemo
使用pycharm打开项目,设置虚拟环境的解析器,并修改manage.py中的后缀参数。
6.2 添加rest_framework应用
在settings.py的INSTALLED_APPS中添加’rest_framework’。
INSTALLED_APPS = [
...
'rest_framework',
]
接下来就可以使用DRF提供的功能进行api接口开发了。在项目中如果使用rest_framework框架实现API接口,主要有以下三个步骤:
-
将请求的数据(如JSON格式)转换为模型类对象
-
操作数据库
-
将模型类对象转换为响应的数据(如JSON格式)
接下来,我们快速体验下四天后我们学习完成drf以后的开发代码。接下来代码不需要理解,看步骤。
6.3 快速体验
6.3.1. 创建模型操作类
class Student(models.Model):
# 模型字段
name = models.CharField(max_length=100,verbose_name="姓名")
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
为了方便测试,所以我们可以先创建一个数据库。
create database students charset=utf8;
6.3.1.1 执行数据迁移
把students子应用添加到INSTALL_APPS中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7j7KO4eG-1683971640695)(http://www.yuan316.com/post/DRF/assets/1557023819604-163184936832712.png)]
初始化数据库连接
安装pymysql
pip install pymysql
主用中__init__.py
设置使用pymysql作为数据库驱动
import pymysql
pymysql.install_as_MySQLdb()
settings.py配置文件中设置mysql的账号密码
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":"123",
},
}
终端下,执行数据迁移。
python manage.py makemigrations
python manage.py migrate `
6.3.2. 创建序列化器
例如,在django项目中创建学生子应用。
python manage.py startapp students
在syudents应用目录中新建serializers.py用于保存该应用的序列化器。
创建一个StudentModelSerializer用于序列化与反序列化。
# 创建序列化器类,回头会在试图中被调用
class StudentModelSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
- model 指明该序列化器处理的数据字段从模型类Student参考生成
- fields 指明该序列化器包含模型类中的哪些字段,'all‘指明包含所有字段
6.3.3. 编写视图
在students应用的views.py中创建视图StudentViewSet,这是一个视图集合。
from rest_framework.viewsets import ModelViewSet
from .models import Student
from .serializers import StudentModelSerializer
# Create your views here.
class StudentViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
- queryset 指明该视图集在查询数据时使用的查询集
- serializer_class 指明该视图在进行序列化或反序列化时使用的序列化器
6.3.4. 定义路由
在students应用的urls.py中定义路由信息。
from . import views
from rest_framework.routers import DefaultRouter
# 路由列表
urlpatterns = []
router = DefaultRouter() # 可以处理视图的路由器
router.register('students', views.StudentViewSet) # 向路由器中注册视图集
urlpatterns += router.urls # 将路由器中的所以路由信息追到到django的路由列表中
最后把students子应用中的路由文件加载到总路由文件中.
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path("stu/",include("students.urls")),
]
6.3.5. 运行测试
运行当前程序(与运行Django一样)
python manage.py runserver
在浏览器中输入网址127.0.0.1:8000,可以看到DRF提供的API Web浏览页面:
1)点击链接127.0.0.1:8000/stu/students 可以访问获取所有数据的接口,呈现如下页面:
2)在页面底下表单部分填写学生信息,可以访问添加新学生的接口,保存学生信息:
点击POST后,返回如下页面信息:
3)在浏览器中输入网址127.0.0.1:8000/stu/students/5/,可以访问获取单一学生信息的接口(id为5的学生),呈现如下页面:
4)在页面底部表单中填写学生信息,可以访问修改学生的接口:
点击PUT,返回如下页面信息:
5)点击DELETE按钮,可以访问删除学生的接口:
返回,如下页面:
7、请求和响应
7.1、请求
REST framework 传入视图的request对象不再是Django默认的HttpRequest对象,而是REST framework提供的扩展了HttpRequest类的Request类的对象。
REST framework 提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单等)将请求数据进行parse解析,解析为类字典[QueryDict]对象保存到Request对象中。
Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。
无论前端发送的哪种格式的数据,我们都可以以统一的方式读取数据。
7.1.1、常用属性
1).data
request.data
返回解析之后的请求体数据。类似于Django中标准的request.POST
和 request.FILES
属性,但提供如下特性:
- 包含了解析之后的文件和非文件数据
- 包含了对POST、PUT、PATCH请求方式解析后的数据
- 利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据
2).query_params
request.query_params
与Django标准的request.GET
相同,只是更换了更正确的名称而已。
3)request._request
获取django封装的Request对象
7.1.2、基本使用
视图代码:
7.2、响应
rest_framework.response.Response
REST framework提供了一个响应类Response
,使用该类构造响应对象时,响应的具体数据内容会被转换(render渲染器)成符合前端需求的类型。
REST framework提供了Renderer
渲染器,用来根据请求头中的Accept
(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行Accept声明,则会采用Content-Type方式处理响应数据,我们可以通过配置来修改默认响应格式。
可以在rest_framework.settings查找所有的drf默认配置项
7.2.1 构造方式
drf的响应处理类和请求处理类不一样,Response就是django的HttpResponse响应处理类的子类。
data
数据不要是render处理之后的数据,只需传递python的内建类型数据即可,REST framework会使用renderer
渲染器处理data
。
data
不能是复杂结构的数据,如Django的模型类对象,对于这样的数据我们可以使用Serializer
序列化器序列化处理后(转为了Python字典类型)再传递给data
参数。
参数说明:
data
: 为响应准备的序列化处理后的数据;status
: 状态码,默认200;template_name
: 模板名称,如果使用HTMLRenderer
时需指明;headers
: 用于存放响应头信息的字典;content_type
: 响应数据的Content-Type,通常此参数无需传递,REST framework会根据前端所需类型数据来设置该参数
7.2.2 response对象的属性(一般很少用)
.data:传给response对象的序列化后,但尚未render处理的数据
.status_code:状态码的数字
.content:经过render处理后的响应数据
7.2.3 状态码
为了方便设置状态码,REST framewrok在rest_framework.status
模块中提供了常用http状态码的常量。
# 1)信息告知 - 1xx
HTTP_100_CONTINUE
HTTP_101_SWITCHING_PROTOCOLS
# 2)成功 - 2xx
HTTP_200_OK
HTTP_201_CREATED
HTTP_202_ACCEPTED
HTTP_203_NON_AUTHORITATIVE_INFORMATION
HTTP_204_NO_CONTENT
HTTP_205_RESET_CONTENT
HTTP_206_PARTIAL_CONTENT
HTTP_207_MULTI_STATUS
# 3)重定向 - 3xx
HTTP_300_MULTIPLE_CHOICES
HTTP_301_MOVED_PERMANENTLY
HTTP_302_FOUND
HTTP_303_SEE_OTHER
HTTP_304_NOT_MODIFIED
HTTP_305_USE_PROXY
HTTP_306_RESERVED
HTTP_307_TEMPORARY_REDIRECT
# 4)客户端错误 - 4xx
HTTP_400_BAD_REQUEST
HTTP_401_UNAUTHORIZED
HTTP_402_PAYMENT_REQUIRED
HTTP_403_FORBIDDEN
HTTP_404_NOT_FOUND
HTTP_405_METHOD_NOT_ALLOWED
HTTP_406_NOT_ACCEPTABLE
HTTP_407_PROXY_AUTHENTICATION_REQUIRED
HTTP_408_REQUEST_TIMEOUT
HTTP_409_CONFLICT
HTTP_410_GONE
HTTP_411_LENGTH_REQUIRED
HTTP_412_PRECONDITION_FAILED
HTTP_413_REQUEST_ENTITY_TOO_LARGE
HTTP_414_REQUEST_URI_TOO_LONG
HTTP_415_UNSUPPORTED_MEDIA_TYPE
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE
HTTP_417_EXPECTATION_FAILED
HTTP_422_UNPROCESSABLE_ENTITY
HTTP_423_LOCKED
HTTP_424_FAILED_DEPENDENCY
HTTP_428_PRECONDITION_REQUIRED
HTTP_429_TOO_MANY_REQUESTS
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS
# 5)服务器错误 - 5xx
HTTP_500_INTERNAL_SERVER_ERROR
HTTP_501_NOT_IMPLEMENTED
HTTP_502_BAD_GATEWAY
HTTP_503_SERVICE_UNAVAILABLE
HTTP_504_GATEWAY_TIMEOUT
HTTP_505_HTTP_VERSION_NOT_SUPPORTED
HTTP_507_INSUFFICIENT_STORAGE
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED
8. 序列化器-Serializer
作用:
1. 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串
2. 反序列化,把客户端发送过来的数据,经过request以后变成字典,序列化器可以把字典转成模型
3. 反序列化,完成数据校验功能
8.1 定义序列化器
Django REST framework中的Serializer使用类来定义,须继承自rest_framework.serializers.Serializer。
接下来,为了方便演示序列化器的使用,我们先创建一个新的子应用sers
python manage.py startapp sers
**注意:serializer不是只能为数据库模型类定义,也可以为非数据库模型类的数据定义。**serializer是独立于数据库之外的存在。
常用字段类型:
字段 | 字段构造方式 |
---|---|
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(maxlength=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_lenght | 最小长度 |
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页面时,显示的字段帮助提示信息 |
8.2 创建Serializer对象
定义好Serializer类后,就可以创建Serializer对象了。
Serializer的构造方法为:
Serializer(instance=None, data=empty, **kwarg)
说明:
1)用于序列化时,将模型类对象传入instance参数
2)用于反序列化时,将要被反序列化的数据传入data参数
3)除了instance和data参数外,在构造Serializer对象时,还可通过context参数额外添加数据,如
通过context参数附加的数据,可以通过Serializer对象的context属性获取。
- 使用序列化器的时候一定要注意,序列化器声明了以后,不会自动执行,需要我们在视图中进行调用才可以。
- 序列化器无法直接接收数据,需要我们在视图中创建序列化器对象时把使用的数据传递过来。
- 序列化器的字段声明类似于我们前面使用过的表单系统。
- 开发restful api时,序列化器会帮我们把模型数据转换成字典.
- drf提供的视图会帮我们把字典转换成json,或者把客户端发送过来的数据转换字典.
8.3 序列化器的使用
序列化器的使用分两个阶段:
- 处理客户端请求时,使用序列化器可以完成对数据的反序列化。
- 处理服务器响应时,使用序列化器可以完成对数据的序列化。
8.3.1 序列化
(1)基本序列化
《1》 先查询出一个学生对象
from sers.models import Book
book = Book.objects.get(pk=1)
《2》 构造序列化器对象
from .serializers import BookSerializer
bookSer = BookSerializer(instance=book)
《3》获取序列化数据
通过data属性可以获取序列化后的数据
bookSer.data
# {'title': '乱世佳人', 'price': 335, 'pub_date': '2012-12-12', 'publish_name': '苹果出版社', 'publish_email': '123@qq.com'}
路由视图代码:
# urls.py
path("sers/", include("sers.urls")),
# sers.urls
path('books/', BookView.as_view()),
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Book
from .sers import BookSerializer
class BookView(APIView):
def get(self, request):
book = Book.objects.get(pk=1)
bs = BookSerializer(instance=book)
return Response(bs.data)
《5》如果要被序列化的是包含多条数据的查询集QuerySet,可以通过添加many=True参数补充说明
class BookView(APIView):
def get(self, request):
# book = Book.objects.get(pk=1)
books = Book.objects.all()
bs = BookSerializer(instance=books, many=True)
return Response(bs.data)
(2)关联字段序列化
from rest_framework import serializers
from .models import Book, Publish, Author
class BookRelatedPublish(serializers.RelatedField):
def to_representation(self, value):
return "pub-%s" % value.name
class BookRelatedAuthor(serializers.RelatedField):
def to_representation(self, value):
return "author-%s" % value.name
class BookSerializer(serializers.Serializer):
title = serializers.CharField(max_length=32)
price = serializers.IntegerField(required=False)
pub_date = serializers.DateField(required=False)
# 序列化关联对象的主键,read_only=True限制只能做序列化
# publish = serializers.PrimaryKeyRelatedField(read_only=True)
# 序列化关联对象的主键,queryset设置可以反序列化做校验
# authors = serializers.PrimaryKeyRelatedField(queryset=Author.objects.all(),many=True)
# publish = serializers.StringRelatedField(read_only=True) # 序列化关联对象的字符串表示形式 即__str__
# publish = serializers.SlugRelatedField(read_only=True,slug_field="email") # 序列化关联对象的指定字段
authors = BookRelatedAuthor(read_only=True,many=True)
publish = BookRelatedPublish(read_only=True)
8.3.2 反序列化
(1)数据验证
使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或保存成模型类对象
。
在获取反序列化的数据前,必须调用**is_valid()**方法进行验证,验证成功返回True,否则返回False。
验证失败,可以通过序列化器对象的errors属性获取错误信息,返回字典,包含了字段和字段的错误。如果是非字段错误,可以通过修改REST framework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的键名。
验证成功,可以通过序列化器对象的validated_data属性获取数据。
在定义序列化器时,指明每个字段的序列化类型和选项参数,本身就是一种验证行为。
通过构造序列化器对象,并将要反序列化的数据传递给data构造参数,进而进行验证
from sers.sers import BookSerializer
bs = BookSerializer(data={"title":"小王子","price":100})
bs.is_valid() # 必须先要is_valid,才会有bs.validated_data和bs.errors
False
bs.validated_data
{}
bs.errors
{'pub_date': [ErrorDetail(string='This field is required.', code='required')], 'publish_id': [ErrorDetail(string='This field is required.', code='required')]}
可以设置required=False让校验字段可以为空!
is_valid()方法还可以在验证失败时抛出异常serializers.ValidationError,可以通过传递raise_exception=True参数开启,REST framework接收到此异常,会向前端返回HTTP 400 Bad Request响应。
# Return a 400 response if the data was invalid.serializer.is_valid(raise_exception=True)
如果觉得这些还不够,需要再补充定义验证行为,可以使用以下三种方法:
1) validate_字段名
对<field_name>
字段进行验证,如
class BookSerializer(serializers.Serializer):
title = serializers.CharField(max_length=32)
price = serializers.IntegerField(required=True)
pub_date = serializers.DateField(required=True)
def validate_title(self, value):
if 'django' not in value.lower():
raise serializers.ValidationError("图书不是关于Django的")
return value
测试
from sers.sers import BookSerializer
bs = BookSerializer(data={"title":"小王子","price":100})
bs.is_valid()
False
bs.errors
{'title': [ErrorDetail(string='图书不是关于Django的', code='invalid')], 'pub_date': [ErrorDetail(string='This field is required.', code='required')]}
还有一种写法:
def title_django(self, value):
if 'django' not in value.lower():
raise serializers.ValidationError("图书不是关于Django的")
return value
class BookSerializer(serializers.Serializer):
title = serializers.CharField(max_length=32,validators=[title_django,])
...
2) validate
在序列化器中需要同时对多个字段进行比较验证时,可以定义validate方法来验证,如
class BookSerializer(serializers.Serializer):
title = serializers.CharField(max_length=32)
price = serializers.IntegerField(required=False)
pub_date = serializers.DateField(required=False)
bread = serializers.IntegerField(label='阅读量', max_value=2147483647, min_value=-2147483648, required=False)
bcomment = serializers.IntegerField(label='评论量', max_value=2147483647, min_value=-2147483648, required=False)
def validate_title(self, value):
if 'django' not in value.lower():
raise serializers.ValidationError("图书不是关于Django的")
return value
def validate(self, data):
bread = data.get("bread")
bcomment = data.get("bcomment")
if bread < bcomment:
raise serializers.ValidationError('阅读量小于评论量')
return data
测试
bs = BookSerializer(data={"title":"Django深入浅出","bread":100,"bcomment":200,"publish_id":1})
bs.is_valid()
False
bs.errors
{'non_field_errors': [ErrorDetail(string='阅读量小于评论量', code='invalid')]}
(2)反序列化-保存数据
前面的验证数据成功后,我们可以使用序列化器来完成数据反序列化的过程.这个过程可以把数据转成模型类对象.
可以通过实现create()和update()两个方法来实现。
class BookSerializer(serializers.Serializer):
"""图书数据序列化器"""
...
def create(self, validated_data):
"""新建"""
return instance
def update(self, instance, validated_data):
return instance
实现了上述两个方法后,在反序列化数据的时候,就可以通过save()方法返回一个数据对象实例了
book = serializer.save()
1、如果创建序列化器对象的时候,没有传递instance实例,则调用save()方法的时候,create()被调用
2、相反,如果传递了instance实例,则调用save()方法的时候,update()被调用。
(3) 附加说明
1) 在对序列化器进行save()保存时,可以额外传递数据,这些数据可以在create()和update()中的validated_data参数获取到
# request.user 是django中记录当前登录用户的模型对象serializer.save(owner=request.user)
2)默认序列化器必须传递所有required的字段,否则会抛出验证异常。但是我们可以使用partial参数来允许部分字段更新
# Update `comment` with partial dataserializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
8.3.3 模型类序列化器
如果我们想要使用序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。
ModelSerializer与常规的Serializer相同,但提供了:
- 基于模型类自动生成一系列字段
- 基于模型类自动为Serializer生成validators,比如unique_together
- 包含默认的create()和update()的实现
(1)定义
比如我们创建一个BookInfoSerializer
class stu_model_sers(serializers.ModelSerializer):
#1. 转换的字段声明
# 必须声明两个属性model和fields
class Meta:
model = Student #继承得哪个模型
fields = ['id','name','sex','age','class_number','description'] # 字段列表
read_only_fields = [] #
-
model 指明参照哪个模型类
-
fields 指明为模型类的哪些字段生成
(2)指定字段
-
使用fields来明确字段,
__all__
表名包含所有字段,也可以写明具体哪些字段,如 -
使用exclude可以明确排除掉哪些字段
-
指明只读字段
-
可以通过read_only_fields指明只读字段,即仅用于序列化输出的字段
(2)添加额外参数
我们可以使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数
8.4 序列化器嵌套
在序列化器使用过程中,一般个序列化器对应一个模型数据。往往因为模型之间会存在外键关联,所以一般在输出数据
时不仅要获取当前模型的数据,甚至其他模型的数据也需要同时返回,这种情况下,我们可以通过序列化器嵌套调用的方
式,帮我们把当前模型数据进行转换以外还额可以同时转换外键对应的模型数据。
9. 视图
Django REST framwork 提供的视图的主要作用:
-
控制序列化器的执行(检验、保存、转换数据)
-
控制数据库模型的操作
REST framework 提供了众多的通用视图基类与扩展类,以简化视图的编写。
DRF的视图是层层继承的关系
9.1、两个视图基类
9.1.1、APIView[基本视图类]
特点
REST framework提供了一个APIView类,它是Django的View类的子类。
APIView类和一般的View类有以下不同:
- 被传入到处理方法的请求不会是Django的HttpRequest类的实例,而是REST framework的Request类的实例。
- 处理方法可以返回REST framework的Response,而不是Django的HttpRequest。视图会管理内容协议,给响应设置正确的渲染器。
- 任何APIException异常都会被捕获,并且传递给合适的响应。
- 进入的请求将会经过认证,合适的权限和(或)节流检查会在请求被派发到处理方法之前运行。
使用APIView类和使用一般的View类非常相似,通常,进入的请求会被分发到合适处理方法比如.get(),或者.post。另外,很多属性会被设定在控制API策略的各种切面的类上。
策略属性
下面这些属性控制了API视图可拔插的那些方面。
- .renderer_classes
- .parser_classes
- .authentication_classes 身份验证类
- .throttle_classes 流量控制类
- .permission_classes 权限检查类
- .content_negotiation_class
举例
- views.py
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.views import Response
from students.models import Student
from .serializers import StuModel_sers
class StudentAPIView(APIView):
def get(self,request):
'''获取所有信息'''
# 1. 从数据库中获取所有信息
student_list = Student.objects.all()
# 2. 实例化序列化器
serializer = StuModel_sers(instance=student_list,many=True)
# 3. 转化数据并返回客户端
return Response(serializer.data,status=status.HTTP_200_OK)
def post(self,request):
'''添加一条数据'''
# 1. 获取客户端数据,实例化序列化器,获取序列化对象
serializer = StuModel_sers(data=request.data)
# 2. 反序列化(验证数据,保存数据到数据库)
serializer.is_valid(raise_exception=True)
serializer.save()
# 3. 返回新增的模型数据给客户单里
return Response(serializer.data,status=status.HTTP_201_CREATED)
class StudentInfoView(APIView):
def get(self,request,pk):
# 获取一条数据
# 1. 使用pk作为条件获取模型对象‘
try:
student = Student.objects.get(pk=pk)
except Student.DoesNotExist:
return Response(status = status.HTTP_404_NOT_FOUND)
# 2. 序列化
serial = StuModel_sers(instance=student)
# 3. 返回结果
return Response(data=serial.data)
def put(self,request,pk):
try:
student = Student.objects.get(pk=pk)
except Student.DoesNotExist:
return Response(status = status.HTTP_404_NOT_FOUND)
# 获取客户端数据
serial = StuModel_sers(student,data=request.data)
serial.is_valid(raise_exception=True)
serial.save()
# 返回结果
return Response(data=request,status=status.HTTP_201_CREATED)
def delete(self,request,pk):
'''删除数据'''
try:
Student.objects.get(pk=pk).delete()
except Student.DoesNotExist:
pass
return Response(status=status.HTTP_201_CREATED)
9.1.2、GenericAPIView[通用视图类]
通用视图类主要作用就是把视图中的独特的代码抽取出来,让视图方法中的代码更加通用,方便把通用代码进行简写。
rest_framework.generics.GenericAPIView
继承自APIView
,主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类。
提供的关于序列化器使用的属性与方法
get_serializer_class(self)
当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在get_serializer_class方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了。
返回序列化器类,默认返回serializer_class
,可以重写
get_serializer(self, *args, **kwargs)
返回序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。
注意,该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request、format、view,这三个数据对象可以在定义序列化器时使用。
- request 当前视图的请求对象
- view 当前请求的类视图对象
- format 当前请求期望返回的数据格式
get_queryset(self)
返回视图使用的查询集,主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回queryset
属性,可以重写,例如:
def get_queryset(self):
user = self.request.user
return user.accounts.all()
get_object(self)
返回详情视图所需的模型类数据对象,主要用来提供给Mixin扩展类使用。
在试图中可以调用该方法获取详情信息的模型类对象。
若详情访问的模型类对象不存在,会返回404。
该方法会默认使用APIView提供的check_object_permissions方法检查当前对象是否有权限被访问。
其他可以设置的属性
- pagination_class 指明分页控制类
- filter_backends 指明过滤控制后端
使用例子
class StuGenc(GenericAPIView):
queryset = Student.objects.all()
serializer_class = StuModel_sers
def get(self, request):
"""获取所有学生信息"""
# 1. 从数据库中提取信息
# GenericAPIView提供的方法
queryset = self.get_queryset()
# 2. 序列化信息
serializer = self.get_serializer(instance=queryset, many=True)
# 3. 转换数据库信息
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request):
'''添加一条信息'''
# 1. 获取客户端数据,实例化序列化器,获取序列化对象
serializer = self.get_serializer(data=request.data, many=True)
# 2. 反序列化(验证数据,保存数据到数据库)
serializer.is_valid(raise_exception=True)
serializer.save()
# 3.返回新增的模型数据给客户单里
return Response(serializer.data, status=status.HTTP_201_CREATED)
class StudentInfoGenericApiView(GenericAPIView):
queryset = Student.objects.all()
serializer_class = StuModel_sers
def get(self, request, pk):
# 1. 使用pk作为条件获取模型对象
instance = self.get_object()
# 2. 序列化
serializer = self.get_serializer(instance=instance)
# 3. 返回结果
return Response(serializer.data)
def put(self, request, pk):
# 1. 使用pk作为条件获取模型对象
instance = self.get_object()
# 2. 序列化
serializer = self.get_serializer(data=request.data,instance = instance)
# 3. 验证数据
serializer.is_valid(raise_exception=True)
serializer.save()
# 4. 返回结果
return Response(data=request.data, status=status.HTTP_201_CREATED)
def delete(self, request, pk):
'''删除数据'''
self.get_object().delete()
# 返回结果
return Response(status=status.HTTP_204_NO_CONTENT)
9.2、5个视图扩展类
也叫混入类(Mixin)。
作用:
提供了几种后端视图(对数据资源进行增删改查
)处理流程的实现,如果需要编写的视图属于这五种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。
这五个扩展类需要搭配GenericAPIView通用视图基类,因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法。
(1)ListModelMixin
列表视图扩展类,提供list(request, *args, **kwargs)
方法快速实现列表视图,返回200状态码。
该Mixin的list方法会对数据进行过滤和分页。
class StudentMix(GenericAPIView, ListModelMixin):
queryset = Student.objects.all()
serializer_class = StuModel_sers
# 直接返回一个列表嵌套json
def get(self, request):
return self.list(request)
(2)CreateModelMixin
创建视图扩展类,提供create(request, *args, **kwargs)
方法快速实现创建资源的视图,成功返回201状态码。
如果序列化器对前端发送的数据验证失败,返回400错误。
(3)RetrieveModelMixin
详情视图扩展类,提供retrieve(request, *args, **kwargs)
方法,可以快速实现返回一个存在的数据对象。
如果存在,返回200, 否则返回404。
(4)UpdateModelMixin
更新视图扩展类,提供update(request, *args, **kwargs)
方法,可以快速实现更新一个存在的数据对象。
同时也提供partial_update(request, *args, **kwargs)
方法,可以实现局部更新。
成功返回200,序列化器校验数据失败时,返回400错误。
(5)DestroyModelMixin
删除视图扩展类,提供destroy(request, *args, **kwargs)
方法,可以快速实现删除一个存在的数据对象。
成功返回204,不存在返回404。
例子
from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
class StudentMix(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Student.objects.all()
serializer_class = StuModel_sers
# 直接返回一个列表嵌套json
def get(self, request):
return self.list(request)
# 添加一条数据
def post(self, request):
return self.create(request)
class StudentInfoMix(GenericAPIView, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin):
queryset = Student.objects.all()
serializer_class = StuModel_sers
# 根据id值获得一条数据
def get(self, request, pk):
return self.retrieve(request, pk=pk)
# 根据id值更新一条数据
def put(self, request, pk):
return self.retrieve(request, pk=pk)
# 根据id值删除一条数据
def delete(self, request, pk):
return self.destroy(request, pk=pk)
9.3、 GenericAPIView的视图子类
(1)CreateAPIView
提供了post方法,内部调用了create方法
继承自: GenericAPIView、CreateModelMixin
(2)ListAPIView
提供了get方法,内部调用了list方法
继承自:GenericAPIView、ListModelMixin
(3)RetrieveAPIView
提供了get方法,内部调用了retrieve方法
继承自: GenericAPIView、RetrieveModelMixin
(4)DestoryAPIView
提供了delete方法,内部调用了destory方法
继承自:GenericAPIView、DestoryModelMixin
(5)UpdateAPIView
提供了put和patch方法,内部调用了update和partial_update方法
继承自:GenericAPIView、UpdateModelMixin
(6)ListCreateAPIView
提供了get和post方法,内部调用了list和create方法
继承自:GenericAPIView、ListModelMixin、CreateModelMixin
(7)RetrieveUpdateAPIView
提供 get、put、patch方法
继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
(8)RetrieveDestoryAPIView
提供 get、delete方法
继承自:GenericAPIView、RetrieveModelMixin、DestoryModelMixin
(9)RetrieveUpdateDestoryAPIView
提供 get、put、patch、delete方法
继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin
from rest_framework.generics import ListCreateAPIView
class StudentView(ListCreateAPIView):
queryset = Student.objects.all()
serializer_class = StuModel_sers
上面的接口代码还可以继续更加的精简,drf在使用GenericAPIView和Mixins进行组合以后,还提供了视图子类。
视图子类是通用视图类和模型扩展类的子类,提供了各种的视图方法调用mix1ns操作
ListAPIView=GenericAPIView+ListModelMixin
获取多条数据的视图方法
CreateAPIView=GenericAPIView+CreateModelMixin
添加一条数据的视图方法
RetrieveAPIView=GenericAPIView+RetrieveModelMixin
获取一条数据的视图方法
UpdateAPIView=GenericAPIView+UpdateModelMixin
更新一条数据的视图方法
DestroyAPIView=GenericAPIView+DestroyModelMixin
删除一条数据的视图方法
组合视图子类
ListCreateAPIView = ListAPIView+CreateAPIView
RetrieveUpdateAPIView = RetrieveAPIView+UpdateAPIView
RetrieveDestroyAPIView = RetrieveAPIView+DestroyAPIView
RetrieveUpdateDestroyAPIView = RetrieveAPIView+UpdateAPIView+DestroyAPIView
9.4、视图集
9.4.1、ViewSet
上面的接口在实现过程中,也存在了代码重复的情况,我们如果合并成一个接口类,则需要考虑2个问题:
1.路由的合并问题
2.get方法重复问题
drf提供了视图集可以解决上面的问题
继承自APIView
与ViewSetMixin
,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。
ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典{“http请求”:“视图方法”}的映射处理工作,如{‘get’:‘list’},
在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。
使用视图集ViewSet,可以将一系列视图相关的代码逻辑和相关的http请求动作封装到一个类中:
-
list() 提供一组数据
-
retrieve() 提供单个数据
-
create() 创建数据
-
update() 保存数据
-
destory() 删除数据
ViewSet视图集类不再限制视图方法名只允许get()、post()等这种情况了,而是实现允许开发者根据自己的需要定义自定义方法名,例如 list() 、create() 等,然后经过路由中使用http和这些视图方法名进行绑定调用。
''' 视图集 ''' from rest_framework.viewsets import ViewSet class StuViewSet(ViewSet): def get_student_list(self,request): '''获取所有信息''' # 1. 从数据库中获取所有信息 student_list = Student.objects.all() # 2. 实例化序列化器 serializer = StuModel_sers(instance=student_list, many=True) # 3. 转化数据并返回客户端 return Response(serializer.data, status=status.HTTP_200_OK) def get_student_info(self, request, pk): # 获取一条数据 # 1. 使用pk作为条件获取模型对象‘ try: student = Student.objects.get(pk=pk) except Student.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) # 2. 序列化 serial = StuModel_sers(instance=student) # 3. 返回结果 return Response(data=serial.data)
9.4.2、GenericViewSet
继承自GenericAPIView和ViewSetMixin,作用让视图集的视图代码变得更加通用,抽离独特代码作为视图类的属性。
使用ViewSet通常并不方便,因为list、retrieve、create、update、destory等方法都需要自己编写,而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写。但是Mixin扩展类依赖与GenericAPIView
,所以还需要继承GenericAPIView
。
GenericViewSet就帮助我们完成了这样的继承工作,继承自GenericAPIView
与ViewSetMixin
,在实现了调用as_view()时传入字典(如{'get':'list'}
)的映射处理工作的同时,还提供了GenericAPIView
提供的基础方法,可以直接搭配Mixin扩展类使用。
视图代码:
'''GenericViewSet + 混入类'''
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
# 同时实现获得所有列表,插入,删除操作
class StudentGenericViewSet(GenericViewSet, ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin):
queryset = Student.objects.all()
serializer_class = StuModel_sers
9.4.3、ModelViewSet和ReadOnlyModelViewSet
ModelViewSet继承自GenericViewSet
,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。
ReadOnlyModelViewSet承自GenericViewSet
,同时包括了ListModelMixin、RetrieveModelMixin。
from rest_framework.viewsets import ReadOnlyModelViewSet
# ReadOnlyModelViewSet = RetrieveModelMixin + ListModelMixin + GenericViewSet
class StudentGenericViewSet2(ReadOnlyModelViewSet, CreateModelMixin, DestroyModelMixin, UpdateModelMixin):
queryset = Student.objects.all()
serializer_class = StuModel_sers
10. 路由Routers
对于视图集ViewSet,我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息。
REST framework提供了两个router
- SimpleRouter
- DefaultRouter
10.1 使用方法
-
实例化路由类
-
给路由注册路由集
-
把生成的路由列表和urlpatterns进行拼接
- urls.py
from rest_framework.routers import SimpleRouter, DefaultRouter
# 1. 实例化路由类
router = DefaultRouter()
# 2. 给路由注册路由集
router.register('student8', views.StudentGenericViewSet3, basename="student8")
# 3. 把生成的路由列表和urlpatterns进行拼接
print(router.urls)
urlpatterns += router.urls
- register(prefix, viewset, base_name)
- prefix 该视图集的路由前缀
- viewset 视图集
- base_name 路由别名的前缀
- views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
# ModelViewSet = RetrieveModelMixin + ListModelMixin + GenericViewSet + CreateModelMixin + UpdateModelMixin
class StudentGenericViewSet3(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StuModel_sers
@action(methods=['get'], detail=False)
def login(self, request):
'''登录视图'''
return Response({'msg': '登录成功!!!'})
10.2 视图集中附加action的声明
在视图集中,如果想要让Router自动帮助我们为自定义的动作生成路由信息,需要使用rest_framework.decorators.action
装饰器。
以action装饰器装饰的方法名会作为action动作名,与list、retrieve等同。
action装饰器可以接收两个参数:
-
methods: 声明该action对应的请求方式,列表传递
-
detail: 声明该action的路径是否与单一资源对应
路由前缀/<pk>/action方法名/
- True 表示路径格式是
xxx/<pk>/action方法名/
- False 表示路径格式是
xxx/action方法名/
- True 表示路径格式是
-
url_path:声明该action的路由尾缀。
举例:
class StudentGenericViewSet3(ModelViewSet): queryset = Student.objects.all() serializer_class = StuModel_sers @action(methods=['get'], detail=False, ) def login(self, request): '''登录视图''' return Response({'msg': '登录成功!!!'})
10.3 路由router形成URL的方式
1) SimpleRouter(prefix=“路由前缀”,viewset=视图集类,basename=“路由别名”)
2)DefaultRouter
DefaultRouter与SimpleRouter的区别是,DefaultRouter会多附带一个默认的API根视图,返回一个包含所有列表视图的超链接响应数据。
11、其它功能组件
为了方便接下来的学习,我们创建一个新的子应用 opt
python manage.py startapp opt
注册子应用
INSTALLED_APPS = [ ... 'opt', # drf提供的组件使用 ]
总路由,代码:
from django.contrib import admin from django.urls import path, include urlpatterns = [ path(‘admin/’, admin.site.urls), path(‘students/’, include(“students.urls”)), path(‘sers/’, include(“sers.urls”)), path(‘school/’, include(“school.urls”)), path(“req/”, include(“req.urls”)), path(“demo/”, include(“demo.urls”)), path(“opt/”, include(“opt.urls”)), ] ` | |
---|---|
子路由,代码:
1 2 3 4 5 | from django.urls import path from . import views urlpatterns = [ ] |
---|---|
因为接下来的认证组件中需要使用到登陆功能,所以我们使用django内置admin站点并创建一个管理员.
admin运营站点的访问地址:http://127.0.0.1:8000/admin
1 2 3 | python manage.py createsuperuser # 如果之前有账号,但是忘了,可以通过终端下的命令修改指定用户的密码,这里的密码必须8位长度以上的 python manage.py changepassword 用户名 |
---|---|
创建管理员以后,访问admin站点,先修改站点的语言配置
settings.py
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
11.1. 认证Authentication
可以在配置文件中配置全局默认的认证方案
常见的认证方式:cookie、session、token
/home/moluo/.virtualenvs/drfdemo/lib/python3.6/site-packages/rest_framework/settings.py 默认配置文件
REST_FRAMEWORK = {
# 配置认证方式的选项
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication', # session认证
'rest_framework.authentication.BasicAuthentication', # 基本认证
)
}
也可以在具体的视图类中通过设置authentication_classess类属性来设置单独的不同的认证方式
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView
class ExampleView(APIView):
# 类属性
authentication_classes = [SessionAuthentication, BasicAuthentication]
def get(self,request):
pass
认证失败会有两种可能的返回值,这个需要我们配合权限组件来使用:
-
401 Unauthorized 未认证
-
403 Permission Denied 权限被禁止
自定义认证,
drfdemo.authentication
代码:from rest_framework.authentication import BaseAuthentication from django.contrib.auth import get_user_model class CustomAuthertication(BaseAuthentication): ''' 自定义认证方式 ''' def authenticate(self, request): user = request.query_params.get('user') pwd = request.query_params.get('pwd') if user != "zh006" or pwd != "Zhangjunhao1": return None user = get_user_model().objects.first() return user, None
视图调用自定义认证,视图代码:
from rest_framework.authentication import SessionAuthentication, BasicAuthentication from rest_framework.views import APIView from rest_framework.response import Response from drflearn.authentication import CustomAuthertication class ExampleView(APIView): # 类属性 authentication_classes = [SessionAuthentication, BasicAuthentication, CustomAuthertication] def get(self, request): print(request.user.id) if request.user.id: return Response({'msg': 'ok!!!'}) return Response({'msg': '用户未登录!!'})
11.2. 权限Permissions
权限控制可以限制用户对于视图的访问和对于具有模型对象的访问。
- 在执行视图的as_view()方法的dispatch()方法前,会先进行视图访问权限的判断
- 在通过get_object()获取具体模型对象时,会进行模型对象访问权限的判断
使用
可以在配置文件中全局设置默认的权限管理类,如
REST_FRAMEWORK = {
....
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
如果未指明,则采用如下默认配置
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
)
也可以在具体的视图中通过permission_classes属性来进行局部设置,如
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
class ExampleView(APIView):
permission_classes = (IsAuthenticated,)
...
提供的权限
- AllowAny 允许所有用户,默认权限
- IsAuthenticated 仅通过登录认证的用户
- IsAdminUser 仅管理员用户
- IsAuthenticatedOrReadOnly 已经登陆认证的用户可以对数据进行增删改操作,没有登陆认证的只能查看数据。
举例
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]
自定义权限
如需自定义权限,需继承rest_framework.permissions.BasePermission父类,并实现以下两个任何一个方法或全部
-
.has_permission(self, request, view)
是否可以访问视图, view表示当前视图对象
-
.has_object_permission(self, request, view, obj)
是否可以访问模型对象, view表示当前视图, obj为模型数据对象
例如:
在当前子应用下,创建一个权限文件drfdemo.permissions.py中声明自定义权限类:
from rest_framework.permissions import BasePermission class IsXiaoMingPermission(BasePermission): """ 自定义权限,可用于全局配置,也可以用于局部 """ def has_permission(self, request, view): """ 视图权限 返回结果未True则表示允许访问视图类 request: 本次客户端提交的请求对象 view: 本次客户端访问的视图类 """ role = request.query_params.get("role") return role == "xiaoming" def has_object_permission(self, request, view, obj): """ 模型权限 返回结果为True则表示允许操作模型对象 """ return True
视图代码:
from .permissions import IsXiaoMingPermission class StudentViewSet(ModelViewSet): queryset = Student.objects.all() serializer_class = StudentSerializer permission_classes = [IsXiaoMingPermission] # 自定义权限
认证和权限的举例代码:
settings.py,全局配置,代码:
# 关于REST_FRAMEWORK的所有配置项都是填写在django的settings配置文件中的。 # 所有的REST_FRAMEWORK都要填写在 REST_FRAMEWORK的配置项,而且配置只能大写!! REST_FRAMEWORK = { # # 认证全局配置 # 'DEFAULT_AUTHENTICATION_CLASSES':[ # # 默认由drf提供的认证方式 # 'rest_framework.authentication.SessionAuthentication', # session认证 # 'rest_framework.authentication.BasicAuthentication', # 基本认证 # # 将来开发中,我们还可以自己实现属于自己项目的认证方式 # 'drfdemo.authentications.CustomAuthentication', # ], # # 权限全局配置 # 'DEFAULT_PERMISSION_CLASSES': [ # # 设置所有视图只能被已经登录认证过的用户访问 # 'rest_framework.permissions.IsAuthenticated', # ] }
视图代码:
from rest_framework.viewsets import ModelViewSet from student.models import Student from student.serializers import StudentModelSerializer from drfdemo.authentications import CustomAuthentication from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly from drfdemo.permissions import IsXiaoMingPermission class Student1ModelViewSet(ModelViewSet): queryset = Student.objects serializer_class = StudentModelSerializer # 局部认证配置方式 authentication_classes = [SessionAuthentication,CustomAuthentication] # 局部权限配置方式 # permission_classes = [IsAuthenticated] # 只要经过认证登录就可以访问 # permission_classes = [IsAdminUser] # 只要是站点管理员就可以访问 # permission_classes = [IsAuthenticatedOrReadOnly] # 登录用户可以访问视图的增删查改页面,未登录的游客只能查看数据。不能修改! # permission_classes = [] # 取消权限判断识别 permission_classes = [IsXiaoMingPermission] # 自定义权限
自定义认证类,
drfdemo.authentications
,代码:from rest_framework.authentication import SessionAuthentication,BaseAuthentication from django.contrib.auth.models import User class CustomAuthentication(BaseAuthentication): """ 自定义认证 """ def authenticate(self, request): """ 认证方法 request: 本次客户端发送过来的http请求对象 """ role = request.query_params.get("role") root = None if role == "root": root = User.objects.get(pk=1) return (root,None) # 按照固定的返回格式填写 (用户模型对象, None) else: return None
自定义权限类,
defdemo.permissions
,代码:from rest_framework.permissions import BasePermission class IsXiaoMingPermission(BasePermission): """ 自定义权限,可用于全局配置,也可以用于局部 """ def has_permission(self, request, view): """ 视图权限 返回结果未True则表示允许访问视图类 request: 本次客户端提交的请求对象 view: 本次客户端访问的视图类 """ role = request.query_params.get("role") return role == "xiaoming" # 认证的结果必须返回True或者False,表示是否有权限 def has_object_permission(self, request, view, obj): """ 模型权限 返回结果未True则表示允许操作模型对象 """ return True
urls,路由代码:
from django.urls import path,include from . import views # 视图集的路由 from rest_framework.routers import SimpleRouter router = SimpleRouter() router.register("stu1", views.Student1ModelViewSet,) urlpatterns = [ path("", include(router.urls)), ]
11.3. 限流Throttling
可以对接口访问的频次进行限制,以减轻服务器压力,或者实现特定的业务。
一般用于付费购买次数,投票等场景使用.
基本使用
可以在配置文件中,使用DEFAULT_THROTTLE_CLASSES
和 DEFAULT_THROTTLE_RATES
进行全局配置,
REST_FRAMEWORK = {
# 限流全局配置
# 'DEFAULT_THROTTLE_CLASSES':[ # 限流配置类
# 'rest_framework.throttling.AnonRateThrottle', # 未认证用户[未登录用户]
# 'rest_framework.throttling.UserRateThrottle', # 已认证用户[已登录用户]
# ],
'DEFAULT_THROTTLE_RATES':{ # 频率配置
'anon': '2/day', # 针对游客的访问频率进行限制,实际上,drf只是识别首字母,但是为了提高代码的维护性,建议写完整单词
'user': '5/day', # 针对会员的访问频率进行限制,
}
}
DEFAULT_THROTTLE_RATES
可以使用 second
, minute
, hour
或day
来指明周期。
也可以在具体视图中通过throttle_classess属性来配置,如
from rest_framework.throttling import UserRateThrottle
class Student2ModelViewSet(ModelViewSet):
queryset = Student.objects
serializer_class = StudentModelSerializer
# 限流局部配置[这里需要配合在全局配置中的DEFAULT_THROTTLE_RATES来设置频率]
throttle_classes = [UserRateThrottle]
可选限流类
1) AnonRateThrottle
限制所有匿名未认证用户,使用IP区分用户。【很多公司这样的,IP结合设备信息来判断,当然比IP要靠谱一点点而已】
使用DEFAULT_THROTTLE_RATES['anon']
来设置频次
2)UserRateThrottle
限制认证用户,使用User模型的 id主键 来区分。
使用DEFAULT_THROTTLE_RATES['user']
来设置频次
3)ScopedRateThrottle
限制用户对于每个视图的访问频次,使用ip或user id。
settings.py,代码:
REST_FRAMEWORK = {
# 限流全局配置
'DEFAULT_THROTTLE_CLASSES':[ # 限流配置类
# 'rest_framework.throttling.AnonRateThrottle', # 未认证用户[未登录用户]
# 'rest_framework.throttling.UserRateThrottle', # 已认证用户[已登录用户]
'rest_framework.throttling.ScopedRateThrottle', # 自定义限流
],
'DEFAULT_THROTTLE_RATES':{ # 频率配置
'anon': '2/day', # 针对游客的访问频率进行限制,实际上,drf只是识别首字母,但是为了提高代码的维护性,建议写完整单词
'user': '5/day', # 针对会员的访问频率进行限制,
'vip': '10/day', # 针对会员的访问频率进行限制,
}
}
视图代码:
from rest_framework.throttling import UserRateThrottle
class Student2ModelViewSet(ModelViewSet):
queryset = Student.objects
serializer_class = StudentModelSerializer
# 限流局部配置[这里需要配合在全局配置中的DEFAULT_THROTTLE_RATES来设置频率]
# throttle_classes = [UserRateThrottle] # 使用drf限流类来配置频率
throttle_scope = "vip" # 自定义频率
11.4. 过滤Filtering
对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展来增强支持。
pip install django-filter
在配置文件中增加过滤后端的设置:
INSTALLED_APPS = [
...
'django_filters', # 需要注册应用,
]
REST_FRAMEWORK = {
...
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
在视图类中添加类属性filter_fields,指定可以过滤的字段
class StudentListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
filter_fields = ['age', 'sex']
# 127.0.0.1:8000/four/students/?sex=1
11.5. 排序Ordering
对于列表数据,REST framework提供了OrderingFilter过滤器来帮助我们快速指明数据按照指定字段进行排序。
使用方法:
在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter
过滤器,REST framework会在请求的查询字符串参数中检查是否包含了ordering参数,如果包含了ordering参数,则按照ordering参数指明的排序字段对数据集进行排序。
前端可以传递的ordering参数的可选字段值需要在ordering_fields中指明。
示例:
class StudentListView(ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
filter_backends = [OrderingFilter]
ordering_fields = ['id', 'age']
# 127.0.0.1:8000/books/?ordering=-age
# -id 表示针对id字段进行倒序排序
# id 表示针对id字段进行升序排序
如果需要在过滤以后再次进行排序,则需要两者结合!
全局配置下的过滤组件不能和排序组件一起使用,只支持局部配置的过滤组件和排序组件一起使用。
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 = [OrderingFilter,DjangoFilterBackend]
ordering_fields = ['id', 'age']
11.6. 分页Pagination
因为django默认提供的分页器主要使用于前后端不分离的业务场景,所以REST framework也提供了分页的支持。
我们可以在配置文件中设置全局的分页方式,如:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 100 # 每页数目
}
# 如果在配置settings.py文件中, 设置了全局分页,那么在drf中凡是调用了ListModelMixin的list(),都会自动分页。如果项目中出现大量需要分页的数据,只有少数部分的分页,则可以在少部分的视图类中关闭分页功能。
# 另外,视图类在使用过分页以后,务必在编写queryset属性时,模型.objects后面调用结果。例如:
# Student.objects.all()
class Student3ModelViewSet(ListAPIView):
pagination_class = None
也可通过自定义Pagination类,来为视图添加不同分页行为。在视图中通过pagination_clas
属性来指明。
可选分页器:
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 前端最多能设置的每页数量
分页器类,
paginations
,代码:from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination # PageNumberPagination,以页码作为分页条件 # page=1&size=10 第1页 # page=2&size=10 第2页 # ... # LimitOffsetPagination,以数据库查询的limit和offset数值作为分页条件 # limit=10&offset=0 第1页 # limit=10&offset=10 第2页 # ... # PageNumberPagination class StudentPageNumberPagination(PageNumberPagination): page_query_param = "page" # 查询字符串中代表页码的变量名 page_size_query_param = "size" # 查询字符串中代表每一页数据的变量名 page_size = 2 # 每一页的数据量 max_page_size = 4 # 允许客户端通过查询字符串调整的最大单页数据量
视图,
views
,代码:from .paginations import StudentPageNumberPagination,StudentLimitOffsetPagination class Student3ModelViewSet(ModelViewSet): queryset = Student.objects.all() serializer_class = StudentModelSerializer # 取消当前视图类的分页效果 # pagination_class = None # 局部分页 pagination_class = StudentPageNumberPagination
2)LimitOffsetPagination
前端访问网址形式:
GET http://127.0.0.1/four/students/?limit=100&offset=100
可以在子类中定义的属性:
-
default_limit 默认限制,默认值与
PAGE_SIZE
设置一直 -
limit_query_param limit参数名,默认’limit’
-
offset_query_param offset参数名,默认’offset’
-
max_limit 最大limit限制,默认None
分页类,代码:
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination # PageNumberPagination,以页码作为分页条件 # page=1&size=10 第1页 # page=2&size=10 第2页 # LimitOffsetPagination,以数据库查询的limit和offset数值作为分页条件 # limit=10&offset=0 第1页 # limit=10&offset=10 第2页 # LimitOffsetPagination class StudentLimitOffsetPagination(LimitOffsetPagination): limit_query_param = "limit" # 查询字符串中代表每一页数据的变量名 offset_query_param = "offset" # 查询字符串中代表页码的变量名 default_limit = 2 # 每一页的数据量 max_limit = 4 # 允许客户端通过查询字符串调整的最大单页数据量
视图,
views
,代码:from .paginations import StudentPageNumberPagination,StudentLimitOffsetPagination class Student3ModelViewSet(ModelViewSet): queryset = Student.objects.all() serializer_class = StudentModelSerializer # 取消当前视图类的分页效果 # pagination_class = None # 局部分页 pagination_class = StudentLimitOffsetPagination
11.7. 异常处理 Exceptions
REST framework提供了异常处理,我们可以自定义异常处理函数。例如我们想在要创建一个自定义异常函数,
这个函数,我们保存到当前子应用opt中[注意,开发时,我们会找个独立的公共目录来保存这种公共的函数/工具/类库]。
1 2 3 4 5 6 7 8 9 10 11 | from rest_framework.views import exception_handler def custom_exception_handler(exc, context): # 先调用REST framework默认的异常处理方法获得标准错误响应对象 response = exception_handler(exc, context) # 在此处补充自定义的异常处理 if response is None: response.data['status_code'] = response.status_code return response |
---|---|
在配置文件中声明自定义的异常处理,settings
,代码:
1 2 3 | REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'drfdemo.exceptions.custom_excetion_handle' } |
---|---|
如果未声明,会采用默认的方式,如下
rest_frame/settings.py
1 2 3 | REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler' } |
---|---|
例如:
补充上处理关于数据库的异常,这里使用其他异常来举例:
主应用.exceptions
,代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # 自定义异常函数: 在drf本身提供的异常函数基础上,我们增加更多的异常处理就可以了。 from rest_framework.views import exception_handler from django.db import DatabaseError from rest_framework import status from rest_framework.response import Response def custom_excetion_handle(exc, context): """ 自定义异常函数,必须要在配置文件中注册才能被drf使用 exc: 异常对象,本次发生的异常对象 context: 字典,本次发生异常时,python解析器提供的执行上下文 所谓的执行上下文[context],就是程序执行到当前一行代码时,能提供给开发者调用的环境信息异常发生时,代码所在的路径,时间,视图,客户端http请求等等...] """ # 先让drf处理它能识别的异常 response = exception_handler(exc, context) # 在经过了drf的异常处理以后,还是返回None则表示有2种情况: if response is None: # 异常发生时的视图对象 view = context['view'] # 异常发生时的http请求 request = context["request"] if isinstance(exc, DatabaseError): print('[%s]: %s' % (view, exc)) response = Response({'detail': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE) if isinstance(exc, TypeError): print("0不能作为除数~") print(request) response = Response({'detail': '0不能作为除数'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return response |
---|---|
视图中,故意报错:
1 2 3 4 5 | from .paginations import StudentPageNumberPagination,StudentLimitOffsetPagination class Student3ModelViewSet(ModelViewSet): queryset = Student.objects # 去掉 .all(),就会报错。 serializer_class = StudentModelSerializer pagination_class = StudentPageNumberPagination |
---|---|
REST framework定义的异常
-
APIException 所有异常的父类
-
ParseError 解析错误
-
AuthenticationFailed 认证失败
-
NotAuthenticated 尚未认证
-
PermissionDenied 权限决绝
-
NotFound 未找到
-
MethodNotAllowed 请求方式不支持
-
NotAcceptable 要获取的数据格式不支持
-
Throttled 超过限流次数
-
ValidationError 校验失败
也就是说,很多的没有在上面列出来的异常,就需要我们在自定义异常中自己处理了。
11.8. 自动生成接口文档
REST framework可以自动帮助我们生成接口文档。
接口文档以网页的方式呈现。
自动接口文档能生成的是继承自APIView
及其子类的视图。
11.8.1. 安装依赖
REST framewrok生成接口文档需要coreapi
库的支持。
pip install coreapi
11.8.2. 设置接口文档访问路径
在总路由中添加接口文档路径。
文档路由对应的视图配置为rest_framework.documentation.include_docs_urls
,
参数title
为接口文档网站的标题。总路由,代码:
`from rest_framework.documentation
import include_docs_urls
urlpatterns = [ ... path('docs/', include_docs_urls(title='站点页面标题')) ]
在settings.py中配置接口文档。
1 2 3 4 5 | REST_FRAMEWORK = { # 。。。 其他选项 # 接口文档 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema', } |
---|---|
11.8.3. 文档描述说明的定义位置
1) 单一方法的视图,可直接使用类视图的文档字符串,如
1 2 3 4 | class BookListView(generics.ListAPIView): """ 返回所有图书信息. """ |
---|---|
2)包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如
1 2 3 4 5 6 7 8 | class BookListCreateView(generics.ListCreateAPIView): """ get: 返回所有图书信息. post: 新建图书. """ |
---|---|
3)对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet): """ list: 返回图书列表数据 retrieve: 返回图书详情数据 latest: 返回最新的图书数据 read: 修改图书的阅读量 """ |
---|---|
11.8.4. 访问接口文档网页
浏览器访问 127.0.0.1:8000/docs/,即可看到自动生成的接口文档。
两点说明:
1) 视图集ViewSet中的retrieve名称,在接口文档网站中叫做read
2)参数的Description需要在模型类或序列化器类的字段中以help_text选项定义,如:
1 2 3 4 | class Student(models.Model): ... age = models.IntegerField(default=0, verbose_name='年龄', help_text='年龄') ... |
---|---|
或
1 2 3 4 5 6 7 8 9 10 | class StudentSerializer(serializers.ModelSerializer): class Meta: model = Student fields = "__all__" extra_kwargs = { 'age': { 'required': True, 'help_text': '年龄' } } |
---|---|
12. jwt
12.1 简介和概述
jwt(JSON Web Tokens),是一种开发的行业标准 RFC 7519 ,用于安全的表示双方之间的声明。目前,jwt广泛应用在系统的用户认证方面,特别是现在前后端分离项目。
12.2. jwt认证流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1KnjLqom-1683971640714)(https://pythonav.com/media/uploads/2019/11/14/image-20191112150846485.png)]
在项目开发中,一般会按照上图所示的过程进行认证,即:用户登录成功之后,服务端给用户浏览器返回一个token,以后用户浏览器要携带token再去向服务端发送请求,服务端校验token的合法性,合法则给用户看数据,否则,返回一些错误信息。
传统token方式和jwt在认证方面有什么差异?
-
传统token方式
用户登录成功后,服务端生成一个随机token给用户,并且在服务端(数据库或缓存)中保存一份token,以后用户再来访问时需携带token,服务端接收到token之后,去数据库或缓存中进行校验token的是否超时、是否合法。
-
jwt方式
用户登录成功后,服务端通过jwt生成一个随机token给用户(服务端无需保留token),以后用户再来访问时需携带token,服务端接收到token之后,通过jwt对token进行校验是否超时、是否合法。
12.3. jwt创建token
原理
jwt的生成token格式如下,即:由 .
连接的三段字符串组成。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
生成规则如下:
-
第一段HEADER部分,固定包含算法和token类型,对此json进行base64url加密,这就是token的第一段。
{ "alg": "HS256", "typ": "JWT" }
-
第二段PAYLOAD部分,包含一些数据,对此json进行base64url加密,这就是token的第二段。
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 ... }
-
第三段SIGNATURE部分,把前两段的base密文通过
.
拼接起来,然后对其进行HS256
加密,再然后对hs256
密文进行base64url加密,最终得到token的第三段。base64url( HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret (秘钥加盐) ) )
最后将三段字符串通过 .
拼接起来就生成了jwt的token。
注意:base64url加密是先做base64加密,然后再将 -
替代 +
及 _
替代 /
。
代码实现
基于Python的pyjwt模块创建jwt的token。
-
安装
pip3 install pyjwt
-
实现
import jwt import datetime from jwt import exceptions SALT = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv=' def create_token(): # 构造header headers = { 'typ': 'jwt', 'alg': 'HS256' } # 构造payload payload = { 'user_id': 1, # 自定义用户ID 'username': 'wupeiqi', # 自定义用户名 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # 超时时间 } result = jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers).decode('utf-8') return result if __name__ == '__main__': token = create_token() print(token)
12.4. jwt校验token
一般在认证成功后,把jwt生成的token返回给用户,以后用户再次访问时候需要携带token,此时jwt需要对token进行超时
及合法性
校验。
获取token之后,会按照以下步骤进行校验:
-
将token分割成
header_segment
、payload_segment
、crypto_segment
三部分jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" signing_input, crypto_segment = jwt_token.rsplit(b'.', 1) header_segment, payload_segment = signing_input.split(b'.', 1)
-
对第一部分
header_segment
进行base64url解密,得到header
-
对第二部分
payload_segment
进行base64url解密,得到payload
-
对第三部分
crypto_segment
进行base64url解密,得到signature
-
对第三部分
signature
部分数据进行合法性校验- 拼接前两段密文,即:
signing_input
- 从第一段明文中获取加密算法,默认:
HS256
- 使用 算法+盐 对
signing_input
进行加密,将得到的结果和signature
密文进行比较。
- 拼接前两段密文,即:
import jwt
import datetime
from jwt import exceptions
def get_payload(token):
"""
根据token获取payload
:param token:
:return:
"""
try:
# 从token中获取payload【不校验合法性】
# unverified_payload = jwt.decode(token, None, False)
# print(unverified_payload)
# 从token中获取payload【校验合法性】
verified_payload = jwt.decode(token, SALT, True)
return verified_payload
except exceptions.ExpiredSignatureError:
print('token已失效')
except jwt.DecodeError:
print('token认证失败')
except jwt.InvalidTokenError:
print('非法的token')
if __name__ == '__main__':
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU"
payload = get_payload(token)
12.5. jwt实战
django 案例
在用户登录成功之后,生成token并返回,用户再次来访问时需携带token。
此示例在django的中间件中对tokne进行校验,内部编写了两个中间件来支持用户通过两种方式传递token。
-
url
传参http://www.pythonav.com?token=eyJhbGciOiJIUzI1N...
-
Authorization
请求头GET /something/ HTTP/1.1 Host: pythonav.com Authorization: JWT eyJhbGciOiAiSFMyNTYiLCAidHlwIj
源码下载:https://pan.baidu.com/s/1ANibEXYocu6V1JfDUydRHw
django rest framework 案例
在用户登录成功之后,生成token并返回,用户再次来访问时需携带token。
此示例在drf的认证组件中对token进行校验,内部编写了两个认证组件来支持用户通过两种方式传递token。
url
传参Authorization
请求头
源码下载:https://pan.baidu.com/s/14dxnH7YvVNVFwpHEjcBLbg
flask 案例
在用户登录成功之后,生成token并返回,用户再次来访问时需携带token。
此示例在flask的before_request中对token进行校验,内部编写了两个函数来支持用户通过两种方式传递token。
url
传参Authorization
请求头
源码下载:https://pan.baidu.com/s/13w8on_OBBxAd1Pp4KXETaQ