Django Rest_Framework
核心思想: 大量缩减编写api接口的代码
Django REST framework是一个建立在Django基础之上的Web 应用开发框架,可以快速的开发REST API接口应用。在REST framework中,提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程,不仅如此,还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST framework还提供了认证、权限、限流、过滤、分页、接口文档等功能支持。REST framework还提供了一个调试API接口 的Web可视化界面来方便查看测试接口。
特点
-
提供了定义序列化器Serializer的方法,可以快速根据 Django ORM 或者其它库自动序列化/反序列化;
-
提供了丰富的类视图、Mixin扩展类,简化视图的编写;
-
丰富的定制层级:函数视图、类视图、视图集合到自动生成 API,满足各种需要;
-
多种身份认证和权限认证方式的支持;[jwt]
-
内置了限流系统;
-
直观的 API web 界面;【方便我们调试开发api接口】
-
可扩展性,插件丰富
DRF需要以下依赖:
-
Python (3.5 以上)
-
Django (2.2 以上)
安装DRF
conda create -n drfdemo python=3.10
conda activate drfdemo
pip install django -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
在项目中如果使用rest_framework框架实现API接口,视图中主要有以下三个步骤:
-
将请求的数据(如JSON格式)转换为模型类对象
-
操作数据库
-
将模型类对象转换为响应的数据(如JSON格式)
序列化: 把我们识别的数据转换成指定的格式提供给别人。
例如:我们在django中获取到的数据默认是模型对象,但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人。
反序列化:把别人提供的数据转换/还原成我们需要的格式。
例如:前端js提供过来的json数据,对于python而言json就是字符串,我们需要进行反序列化换成字典,然后接着字典再进行转换成模型对象,这样我们才能把数据保存到数据库中。
序列化器-serializer
1. 序列化,序列化器会把模型对象转换成字典,经过视图中response对象以后变成json字符串
2. 反序列化,视图中request会把客户端发送过来的数据转换成字典,序列化器可以把字典转成模型
3. 反序列化,把客户端发送过来的数据进行校验
定义序列化
Django REST framework中的Serializer使用类来定义,必须直接或间接继承于rest_framework.serializers.Serializer。
rest_framework.serializers 是drf提供给开发者调用的序列化器模块
里面声明了所有的可用序列化器的基类,常用的有:
Serializer 序列化器基类,drf中所有的序列化器类都必须继承于 Serializer
ModelSerializer 模型序列化器基类,是序列化器基类Serializer的子类,在工作中,除了Serializer基类以外,最常用的序列化器类基类
from rest_framework import serializers
class StudentSerializer(serializers.Serializer):
"""学生信息序列化器"""
# 1. 转换的字段声明
id = serializers.IntegerField()
name = serializers.CharField()
sex = serializers.BooleanField()
age = serializers.IntegerField()
description = serializers.CharField()
# 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
# class Meta:
# model = 模型
# fields = "__all__"
# fields = ["字段1","字段2",....]
# 3. 验证代码的对象方法
# def validate(self, attrs): # 方法名validate是固定的,attrs就是客户端发送的数据,字典格式
# pass
# return attrs
# def validate_<字段名>(self, data): # 方法名的格式必须以 validate_<字段名> 为名称,否则序列化器不识别!
# pass
# return data
# 4. 模型操作的扩展方法
# def create(self, validated_data): # 添加数据操作,添加数据以后,就自动实现了从字典变成模型对象的过程
# pass
#
# def update(self, instance, validated_data): # 更新数据操作,更新数据以后,就自动实现了从字典变成模型对象的过程
# pass
注意:serializer不是只能为数据库模型类转换数据格式,也可以为非数据库模型类的转换数据格式。serializer是独立于数据库之外的存在。
serailziers中的字段声明时提供给客户端的,所以常用字段类型:
序列化器 字段 | 模型 字段 | 字段选项 |
---|---|---|
BooleanField | BooleanField | BooleanField() |
CharField | CharField TextField等 | CharField( max_length=最大长度, min_length=最小长度, allow_blank=False, 表示是否允许客户端提交空字符串,False表示不允许 trim_whitespace=True,表示是否移除字符串两边的空白字符,True表示移除 ) |
EmailField | EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
RegexField | CharField | RegexField(regex=正则表达式, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField | SlugField(max_length=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9*-]+ |
URLField | URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
UUIDField | UUIDField | UUIDField(format='hex_verbose') format: 设置UUID格式,一般默认使用hex_verbose 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 'int' - 如: "123456789012312313134124512351145145114" 'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" |
IPAddressField | IPAddressField | IPAddressField(protocol='both', unpack_ipv4=False, **options) |
IntegerField | SmallIntegerFiled IntegerField BigIntegerField | IntegerField(max_value=最大值, min_value=最小值) |
FloatField | FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField | DecimalField( max_digits=数值的数字总个数, decimal_places=小数位个数, coerce_to_string=None, max_value=None, min_value=None) |
DateTimeField | DateTimeField | DateTimeField( format=api_settings.DATETIME_FORMAT, 表示日期格式 input_formats=None) |
DateField | DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField | DurationField() |
ChoiceField | 对应整型或字符串中的choices=属性选项 | ChoiceField(choices=元祖选项) choices与Django的用法相同 |
MultipleChoiceField | 对应整型或字符串中的choices=属性选项 | MultipleChoiceField(choices=元祖选项) choices与Django的用法相同 |
FileField | FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | python里面的List | ListField(child=模型列表, min_length=None, max_length=None) |
DictField | python里面的Dict | DictField(child=模型对象) |
字段的选项参数:
参数名称 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空字符串 |
trim_whitespace | 是否移除两边的空白字符 |
max_value | 最小数值 |
min_value | 最大数值 |
字段的通用选项参数:
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False(只读自读,True时表示,该字段客户端不需要进行提交,服务的提供) |
write_only | 表明该字段仅用于反序列化输入,默认False(只写字段,True时表示,该字段服务的不需要进行传给客户端,客户端必须上传) |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明反序列化时该字段是否允许传入None,默认False |
validators | 表明反序列化时该字段使用的验证器函数 |
error_messages | 表明反序列化时如果验证出错了,返回错误错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称。 如果不写,则默认采用模型的verbose_name,但是前提是当前序列化器继承ModelSerializer |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 如果不写,则默认采用模型的help_text,但是前提是当前序列化器继承ModelSerializer |
创建Serializer对象
定义好Serializer类后,就可以创建Serializer对象了。
Serializer的构造方法为:
序列化器(instance=None, data=empty, many=False, context=None, **kwargs)
说明:
1)用于序列化时实例化,则需要将模型类对象传入instance参数
2)用于反序列化时实例化,将要被反序列化的数据传入data参数
3)用于序列化时实例化,当instance的值是一个QuerySet类型,则需要声明many=True
4)除了以上参数外,在构造Serializer对象时,还可通过context参数额外添加数据传入到序列化器中。
serializer = Student1Serializer(instance=student) # 序列化单个模型对象为dict字典
serializer = Student1Serializer(instance=student_list, many=True) # 序列化多个模型对象为list列表
serializer = Student1Serializer(instance=student_list, context={'request': request}) # 如果传递数据到序列化器中,可以使用context
通过context参数附加的数据,可以通过Serializer对象的context属性获取。
-
使用序列化器的时候一定要注意,序列化器声明了以后,不会自动执行,需要我们在视图中进行调用才可以。
-
序列化器无法直接接收数据,需要我们在视图中实例化序列化器对象时把使用的数据通过instance传递过来。
-
序列化器的字段声明类似于我们前面使用过的模型。
-
开发restful api时,序列化器会帮我们把模型对象转换成字典
序列化器的使用
序列化器的使用分两个阶段:
-
在客户端请求时,使用序列化器可以完成对数据的反序列化。
-
在服务器响应时,使用序列化器可以完成对数据的序列化。
序列化
基本使用
视图代码:
from django.views import View
from .serializers import StudentSerializer
from stuapi.models import Student
from django.http.response import JsonResponse
# Create your views here.
class Student1View(View):
def get(self, request):
"""序列化器的基本使用:序列化单个模型对象为字典"""
student = Student.objects.first()
print(student)
# 把模型对象作为instance参数的值传递到序列化器中进行转换数据格式
serializer = StudentSerializer(instance=student)
print(serializer.data)
"""
{'id': 1, 'name': 'xiaoming', 'sex': True, 'age': 16, 'description': 'xxxxx'}
"""
return JsonResponse(serializer.data)
如果要被序列化的是包含多条数据的查询集QuerySet,可以通过添加many=True参数补充说明
from django.views import View
from .serializers import StudentSerializer
from stuapi.models import Student
from django.http.response import JsonResponse
# Create your views here.
class Student1View(View):
def get1(self, request):
"""序列化器的基本使用:序列化单个模型对象为字典"""
student = Student.objects.first()
print(student)
# 把模型对象作为instance参数的值传递到序列化器中进行转换数据格式
serializer = StudentSerializer(instance=student)
print(serializer.data)
"""
{'id': 1, 'name': 'xiaoming', 'sex': True, 'age': 16, 'description': 'xxxxx'}
"""
return JsonResponse(serializer.data)
def get(self, request):
"""序列化器的基本使用:序列化多个模型对象为列表"""
student_list = Student.objects.all()
print(student_list)
# 把模型列表作为instance参数传递到序列化器中进行数据格式转换
serializer = StudentSerializer(student_list, many=True)
print(serializer.data)
return JsonResponse(serializer.data, safe=False)
反序列化
数据验证
作验证时,在验证之后必须将校验的值返回,否则serializer序列器对象会丢失验证数据
使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或保存成模型类对象。
在获取反序列化的数据前,必须调用is_valid()方法进行验证,验证成功返回True,否则返回False。
验证失败,可以通过序列化器对象的errors属性获取错误信息,返回字典,包含了字段和字段的错误。如果是非字段错误,可以通过修改REST framework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的键名。
验证成功,可以通过序列化器对象的validated_data属性获取数据。
在定义序列化器时,指明每个字段的序列化类型和选项参数,本身就是一种验证行为。
serializers代码:
from rest_framework import serializers
class Student2Serializer(serializers.Serializer):
"""学生信息序列化器"""
# 1. 转换的字段声明
# 字段 = serializers.字段类型(选项=选项值,)
id = serializers.IntegerField(read_only=True) # read_only=True, 在客户端提交数据[反序列化阶段不会要求id字段]
name = serializers.CharField(required=True) # required=True , 反序列化阶段必填
sex = serializers.BooleanField(default=True) # default=True, 反序列化阶段,客户端没有提交,则默认为True
age = serializers.IntegerField(max_value=100, min_value=0, error_messages={
"min_value": "The Age Filed Must Be 0 <= age", # age在反序列化必须是 0 <= age <= 100
"max_value": "The Age Filed Must Be age <= 100",
})
# error_messages 中支持使用字符串的格式化:;例如:虽大长度不能超过{max_length}
description = serializers.CharField(allow_null=True, allow_blank=True) # 允许客户端不填写内容(None),或者值为 ""
# 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
# class Meta:
# model = 模型
# fields = ["字段1","字段2",....]
# 3. 验证代码的对象方法
# def validate(self, attrs): # validate是固定的
# pass
# return attrs
# def validate_<字段名>(self, data): # 方法名的格式必须以 validate_<字段名> 为名称,否则序列化器不识别!
# pass
# return data_
# 4. 模型操作的方法,
# def create(self, validated_data): # 添加数据操作,添加数据以后,就自动实现了从字典变成模型对象的过程
# pass
#
# def update(self, instance, validated_data): # 更新数据操作,更新数据以后,就自动实现了从字典变成模型对象的过程
# pass
视图代码
import json
from django.views import View
from django.http.response import JsonResponse
from .serializers import Student1Serializer, Student2Serializer
from stuapi.models import Student
# Create your views here.
class StudentView(View):
def get(self,request):
"""反序列化-采用字段选项来验证数据[验证失败不抛出异常]"""
# 1. 接收客户端提交的数据
# data = json.dumps(request.body)
# 模拟来自客户端的数据
data = {
"name": "xiaohong",
"age": 117,
"sex": True,
"classmate": "301",
"description": "这家伙很懒,什么都没有留下~"
}
# 1.1 实例化序列化器,获取序列化对象
serializer = Student2Serializer(data=data)
# 1.2 调用序列化器进行数据验证
ret = serializer.is_valid() # 不抛出异常
# serializer.is_valid(raise_exception=true) # 验证失败后会抛出异常
print(f"ret={ret}")
# 1.3 获取验证以后的结果
if ret:
return JsonResponse(dict(serializer.validated_data))
else:
return JsonResponse(dict(serializer.errors))
# 2. 操作数据库
# 3. 返回结果
validate_字段名
对<field_name>
字段进行验证,如
class Student2Serializer(serializers.Serializer):
"""学生信息序列化器"""
# 1. 转换的字段声明
# 字段 = serializers.字段类型(选项=选项值,)
id = serializers.IntegerField(read_only=True) # read_only=True, 在客户端提交数据[反序列化阶段不会要求id字段]
name = serializers.CharField(required=True) # required=True , 反序列化阶段必填
sex = serializers.BooleanField(default=True) # default=True, 反序列化阶段,客户端没有提交,则默认为True
age = serializers.IntegerField(max_value=100, min_value=0, error_messages={
"min_value": "The Age Filed Must Be 0 <= age", # age在反序列化必须是 0 <= age <= 100
"max_value": "The Age Filed Must Be age <= 100",
})
description = serializers.CharField(allow_null=True, allow_blank=True) # 允许客户端不填写内容(None),或者值为 ""
# 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
# class Meta:
# model = 模型
# fields = ["字段1","字段2",....]
# 3. 验证代码的对象方法
# def validate(self, attrs): # validate是固定的
# pass
# return attrs
def validate_name(self, data):
"""验证单个字段
方法名的格式必须以 validate_<字段名> 为名称,否则序列化器不识别!
validate开头的方法,会自动被is_valid调用的
"""
if data in ["python", "django"]:
# 在序列化器中,验证失败可以通过抛出异常的方式来告知 is_valid
raise serializers.ValidationError(detail="学生姓名不能是python或django", code="validate_name")
# 验证成功以后,必须返回数据,否则最终的验证结果中,就不会出现这个数据了。
return data
# 4. 模型操作的方法,
# def create(self, validated_data): # 添加数据操作,添加数据以后,就自动实现了从字典变成模型对象的过程
# pass
#
# def update(self, instance, validated_data): # 更新数据操作,更新数据以后,就自动实现了从字典变成模型对象的过程
# pass
validate
在序列化器中需要同时对多个字段进行比较验证时,可以定义validate方法来验证,如
class Student2Serializer(serializers.Serializer):
"""学生信息序列化器"""
# 1. 转换的字段声明
# 字段 = serializers.字段类型(选项=选项值,)
id = serializers.IntegerField(read_only=True) # read_only=True, 在客户端提交数据[反序列化阶段不会要求id字段]
name = serializers.CharField(required=True) # required=True , 反序列化阶段必填
sex = serializers.BooleanField(default=True) # default=True, 反序列化阶段,客户端没有提交,则默认为True
age = serializers.IntegerField(max_value=100, min_value=0, error_messages={
"min_value": "The Age Filed Must Be 0 <= age", # age在反序列化必须是 0 <= age <= 100
"max_value": "The Age Filed Must Be age <= 100",
})
classmate = serializers.CharField()
description = serializers.CharField(allow_null=True, allow_blank=True) # 允许客户端不填写内容(None),或者值为 ""
# 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
# class Meta:
# model = 模型
# fields = ["字段1","字段2",....]
# 3. 验证代码的对象方法
def validate(self, attrs):
"""
验证来自客户端的所有数据
类似会员注册的密码和确认密码,就只能在validate方法中校验
validate是固定方法名,
参数:attrs,是在序列化器实例化时的data选项数据
"""
# 307班只能有女生,不能加入其他男生
if attrs["classmate"] == "307" and attrs["sex"]:
raise serializers.ValidationError(detail="307班只能进去小姐姐~", code="validate")
return attrs
def validate_name(self, data):
"""验证单个字段
方法名的格式必须以 validate_<字段名> 为名称,否则序列化器不识别!
validate开头的方法,会自动被is_valid调用的
"""
if data in ["python", "django"]:
# 在序列化器中,验证失败可以通过抛出异常的方式来告知 is_valid
raise serializers.ValidationError(detail="学生姓名不能是python或django", code="validate_name")
# 验证成功以后,必须返回数据,否则最终的验证结果中,就不会出现这个数据了。
return data
# 4. 模型操作的方法,
# def create(self, validated_data): # 添加数据操作,添加数据以后,就自动实现了从字典变成模型对象的过程
# pass
#
# def update(self, instance, validated_data): # 更新数据操作,更新数据以后,就自动实现了从字典变成模型对象的过程
# pass
validators
在字段中添加validators选项参数,也可以补充验证行为,如
def check_classmate(data):
"""外部验证函数"""
if len(data) != 3:
raise serializers.ValidationError(detail="班级编号格式不正确!必须是3个字符", code="check_classmate")
# 验证完成以后,务必返回结果,否则最终的验证结果没有该数据
return data
class Student2Serializer(serializers.Serializer):
"""学生信息序列化器"""
# 1. 转换的字段声明
# 字段 = serializers.字段类型(选项=选项值,)
id = serializers.IntegerField(read_only=True) # read_only=True, 在客户端提交数据[反序列化阶段不会要求id字段]
name = serializers.CharField(required=True) # required=True , 反序列化阶段必填
sex = serializers.BooleanField(default=True) # default=True, 反序列化阶段,客户端没有提交,则默认为True
age = serializers.IntegerField(max_value=100, min_value=0, error_messages={
"min_value": "The Age Filed Must Be 0 <= age", # age在反序列化必须是 0 <= age <= 100
"max_value": "The Age Filed Must Be age <= 100",
})
# validators 外部验证函数选项,值是一个列表,列表得到成员是函数名,不能是字符串!!!
classmate = serializers.CharField(validators=[check_classmate])
description = serializers.CharField(allow_null=True, allow_blank=True) # 允许客户端不填写内容(None),或者值为 ""
# 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
# class Meta:
# model = 模型
# fields = ["字段1","字段2",....]
# 3. 验证代码的对象方法
def validate(self, attrs):
"""
验证来自客户端的所有数据
类似会员注册的密码和确认密码,就只能在validate方法中校验
validate是固定方法名,
参数:attrs,是在序列化器实例化时的data选项数据
"""
# 307班只能有女生,不能加入其他男生
if attrs["classmate"] == "307" and attrs["sex"]:
raise serializers.ValidationError(detail="307班只能进去小姐姐~", code="validate")
return attrs
def validate_name(self, data):
"""验证单个字段
方法名的格式必须以 validate_<字段名> 为名称,否则序列化器不识别!
validate开头的方法,会自动被is_valid调用的
"""
if data in ["python", "django"]:
# 在序列化器中,验证失败可以通过抛出异常的方式来告知 is_valid
raise serializers.ValidationError(detail="学生姓名不能是python或django", code="validate_name")
# 验证成功以后,必须返回数据,否则最终的验证结果中,就不会出现这个数据了。
return data
# 4. 模型操作的方法,
# def create(self, validated_data): # 添加数据操作,添加数据以后,就自动实现了从字典变成模型对象的过程
# pass
#
# def update(self, instance, validated_data): # 更新数据操作,更新数据以后,就自动实现了从字典变成模型对象的过程
# pass
验证顺序:选项验证->validate_字段名->validate->自定义验证
保存数据
前面的验证数据成功后,我们可以使用序列化器来完成数据反序列化的过程.这个过程可以把数据转成模型类对象.
可以通过实现create()和update()两个方法来实现。
class Student2Serializer(serializers.Serializer):
"""学生信息序列化器"""
# 1. 转换的字段声明
# 字段 = serializers.字段类型(选项=选项值,)
id = serializers.IntegerField(read_only=True) # read_only=True, 在客户端提交数据[反序列化阶段不会要求id字段]
name = serializers.CharField(required=True) # required=True , 反序列化阶段必填
sex = serializers.BooleanField(default=True) # default=True, 反序列化阶段,客户端没有提交,则默认为True
age = serializers.IntegerField(max_value=100, min_value=0, error_messages={
"min_value": "The Age Filed Must Be 0 <= age", # age在反序列化必须是 0 <= age <= 100
"max_value": "The Age Filed Must Be age <= 100",
})
# validators 外部验证函数选项,值是一个列表,列表得到成员是函数名,不能是字符串!!!
classmate = serializers.CharField(validators=[check_classmate])
description = serializers.CharField(allow_null=True, allow_blank=True) # 允许客户端不填写内容(None),或者值为 ""
# 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
# class Meta:
# model = 模型
# fields = ["字段1","字段2",....]
# 3. 验证代码的对象方法
def validate(self, attrs):
"""
验证来自客户端的所有数据
类似会员注册的密码和确认密码,就只能在validate方法中校验
validate是固定方法名,
参数:attrs,是在序列化器实例化时的data选项数据
"""
# 307班只能有女生,不能加入其他男生
if attrs["classmate"] == "307" and attrs["sex"]:
raise serializers.ValidationError(detail="307班只能进去小姐姐~", code="validate")
return attrs
def validate_name(self, data):
"""验证单个字段
方法名的格式必须以 validate_<字段名> 为名称,否则序列化器不识别!
validate开头的方法,会自动被is_valid调用的
"""
if data in ["python", "django"]:
# 在序列化器中,验证失败可以通过抛出异常的方式来告知 is_valid
raise serializers.ValidationError(detail="学生姓名不能是python或django", code="validate_name")
# 验证成功以后,必须返回数据,否则最终的验证结果中,就不会出现这个数据了。
return data
# 4. 模型操作的方法,
def create(self, validated_data):
"""
添加数据
方法名固定为create,固定参数validated_data就是验证成功以后的结果
"""
student = Student.objects.create(**validated_data)
return student
def update(self, instance, validated_data):
"""
更新数据操作,
方法名固定为update,
固定参数instance,实例化序列化器对象时,必须传入的模型对象
固定参数validated_data就是验证成功以后的结果
更新数据以后,就自动实现了从字典变成模型对象的过程
"""
instance.name = validated_data["name"]
instance.age = validated_data["age"]
instance.sex = validated_data["sex"]
instance.classmate = validated_data["classmate"]
instance.description = validated_data["description"]
instance.save() # 调用模型对象的save方法,和视图中的serialzier.save()不是同一个类的方法
return instance
实现了上述两个方法后,在反序列化数据的时候,就可以通过save()方法返回一个数据对象实例了
book = serializer.save()
如果创建序列化器对象的时候,没有传递instance实例,则调用save()方法的时候,create()被调用,相反,如果传递了instance实例,则调用save()方法的时候,update()被调用。
def get(self,request):
"""反序列化-验证完成以后,更新数据入库"""
#1. 根据客户端访问的url地址中,获取pk值
# sers/students/2/ path("/students/(?P<pk>)\d+/", views.StudentView.as_view()),
pk = 5
try:
student = Student.objects.get(pk=pk)
except Student.DoesNotExist:
return JsonResponse({"errors": "当前学生不存在!"}, status=400)
# 2. 接收客户端提交的修改数据
# 模拟来自客户端的数据
data = {
"name": "xiaohong",
"age": 18,
"sex": False,
"classmate": "303",
"description": "这家伙很懒,什么都没有留下~"
}
# 3. 修改操作中的实例化序列化器对象,参数instance参数为null时,走create()
serializer = Student2Serializer(instance=student, data=data)
# 4. 验证数据
serializer.is_valid(raise_exception=True)
# 5. 入库
serializer.save()
# 6. 返回结果
return JsonResponse(serializer.data, status=201)
在对序列化器进行save()保存时,可以额外传递数据,这些数据可以在create()和update()中的validated_data参数获取到,用于补充数据给模型的
# request.user 是django中记录当前登录用户的模型对象
serializer.save(owner=request.user) # 可以在save中,传递一些不需要验证的数据到模型里面
默认序列化器必须传递所有required的字段,否则会抛出验证异常。但是我们可以使用partial参数来允许部分字段更新
partial=true表示对不存在的数据,不需要按序列化器的字段列表进行验证
# 更新 `name` ,不需要验证其他的字段,可以设置partial=True
serializer = StudentSerializer(student, data={'name': 'xiaohui'}, partial=True)
模型类序列化器(ModelSerializer)
如果我们想要使用序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。
ModelSerializer与常规的Serializer基本相同,但额外提供了:
-
基于模型类自动生成一系列序列化字段
-
基于模型类自动为Serializer生成validators,比如unique_together
-
ModelSerializer包含默认的create()和update()的实现
基本使用
class StudentModelSerializer(serializers.ModelSerializer):
"""学生信息序列化器"""
# 1. 转换的字段声明
# 字段声明中可以覆盖从模型那边导入的字段声明
# 同时可可以声明一些模型中没有的字段
# 字段名 = 字段类型(选项=选项值)
nickname = serializers.CharField(read_only=True)
# 2. 如果当前序列化器继承的是ModelSerializer,则需要声明调用的模型信息
# 必须给Meta声明2个属性
class Meta:
model = Student # 必填
# 必须将模型导入和上述声明的字段全部填入fields中
fields = ["id", "name", "age", "sex","nickname"] # 必填,可以是字符串和列表/元组,字符串的值只能是"__all__"表示返回所有字段
# read_only_fields = [] # 选填,只读字段列表,表示设置这里的字段只会在序列化阶段采用
extra_kwargs = { # 选填,字段额外选项声明
"age": {
"min_value": 5,
"max_value": 20,
"error_messages": {
"min_value": "年龄的最小值必须大于等于5",
"max_value": "年龄的最大值必须小于等于20",
}
},
}
# 3. 验证代码的对象方法
# def create(self, validated_data):
# 密码加密
# validated_data["password"] = make_password(validated_data["password"])
# super().create(validated_data)
# 4. 模型操作的方法,
视图代码
def get5(self,request):
"""反序列化-验证完成以后,添加数据入库"""
# 1. 接收客户端提交的数据
# 模拟来自客户端的数据
data = {
"name": "xiaoming",
"age": 11,
"sex": True,
"classmate": "301",
"description": "这家伙很懒,什么都没有留下~"
}
# 1.1 实例化序列化器,获取序列化对象
serializer = StudentModelSerializer(data=data)
# 1.2 调用序列化器进行数据验证
serializer.is_valid(raise_exception=True) # 抛出异常,代码不会往下执行
# 2. 获取验证以后的结果。操作数据库
serializer.save() # 会根据实例化序列化器的时候,是否传入instance属性来自动调用create或者update方法。传入instance属性,自动调用update方法;没有传入instance属性,则自动调用create
# 3. 返回结果
return JsonResponse(serializer.data, status=201)
def get(self,request):
"""反序列化-验证完成以后,更新数据入库"""
#1. 根据客户端访问的url地址中,获取pk值
# sers/students/2/ path("/students/(?P<pk>)\d+/", views.StudentView.as_view()),
pk = 5
try:
student = Student.objects.get(pk=pk)
except Student.DoesNotExist:
return JsonResponse({"errors": "当前学生不存在!"}, status=400)
# 2. 接收客户端提交的修改数据
# 模拟来自客户端的数据
data = {
"name": "xiaolv",
}
# 3. 修改操作中的实例化序列化器对象
serializer = StudentModelSerializer(instance=student, data=data, partial=True)
# 4. 验证数据
serializer.is_valid(raise_exception=True)
# 5. 入库
serializer.save() # 可以在save中,传递一些不需要验证的数据到模型里面
# 6. 返回结果
return JsonResponse(serializer.data, status=201)
Meta类里面必须声明2个属性:
-
model 指明参照哪个模型类
-
fields 指明为模型类的哪些字段生成
使用vaildate(self,attrs)进行验证
例如验证密码
def validate(self,attrs):
if attrs["password] != attrs["re_password"]:
raise serializers.ValidationError(detail="俩次密码不同")
# 可以在验证完成后,把数据库中不需要的字典从attrs中移除
attrs.pop("re_password")
# 务必把验证后的内容返回给客户端
return attrs
ModelSerializer内置了create与update方法,所以ModelSerializer在使用过程中,实际上不需要我们手写create与update方法的。而Serializer的源码中并没有内置create与update方法,所以我们如果要让Serializer实现数据保存功能,则务必实现create与update方法。
什么时候声明的序列化器需要继承序列化器基类Serializer,什么时候继承模型序列化器类ModelSerializer?
继承序列化器类Serializer 字段声明 验证 添加/保存数据功能 继承模型序列化器类 ModelSerializer 字段声明[可选,看需要] Meta声明[必填] 验证 添加/保存数据功能[可选,看需要,例如一次性添加数据到多个模型中]
看客户端提交的数据是否与数据库模型相关,如果是则使用ModelSerializer,不是则使用Serializer,当然,即便和数据库相关,我们偏要使用Serializer,也没有问题。
HTTP请求响应
drf除了在数据序列化部分简写代码以外,还在视图中提供了简写操作。所以在django原有的django.views.View类基础上,drf封装了多个视图子类出来提供给我们使用。
Django REST framwork 提供的视图的主要作用:
-
控制序列化器的执行(检验、保存、转换数据)
-
控制数据库查询的执行
-
调用请求类和响应类[这两个类也是由drf帮我们再次扩展了一些功能类。
内容协商
内容协商:drf在django原有的基础上,新增了一个request对象内置到了drf提供的APIVIew视图类里面,并在django原有的HttpResponse响应类的基础上实现了一个子类rest_framework.response.Response响应类。这两个类,都是基于内容协商来完成数据的格式转换的。
drf中实现的内容协商流程:
DRF的request类->parser(http请求解析类->识别客户端请求头中的Content-Type来完成数据转换成->类字典(QueryDict,字典的子类)
DRF的response类->renderer(http响应渲染类)->识别客户端请求头的"Accept"来提取客户端期望的返回数据格式-> 转换成客户端的期望
注意:django默认是没有实现内容协商的,所以我们如果希望使用内容协商,则要么手动给django提供这个功能,要么使用drf提供的视图类。
Request
REST framework 传入视图的request对象不再是Django默认的HttpRequest对象,而是REST framework提供的扩展了HttpRequest类的Request类的对象。
REST framework 提供了Parserhttp请求解析器类,在接收到客户端的http请求后会自动根据Content-Type指明的请求数据类型(如JSON、html表单等)将请求数据进行parse解析,解析为类字典[QueryDict]对象保存到Request对象中。
Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。也就是说,无论前端发送的哪种格式的数据,我们都可以以统一的方式读取客户端提交的数据。
常用属性
1).data
request.data
提供了Parse解析之后的请求体数据。类似于Django中标准的request.POST
和 request.FILES
、request.body
属性,但提供如下特性:
-
包含了解析之后的文件和非文件数据
-
包含了对POST、PUT、PATCH请求方式解析后的数据
-
利用了REST framework的parser解析器,不仅支持表单类型数据,也支持JSON数据
2).query_params
query_params,查询参数,也叫查询字符串(query string )
注意:request.query_params
是不区分http请求方法的,只要是地址栏?号后面的查询字符串都可以接收,结果是QueryDict格式。
request.query_params
本质上就是Django提供的request.GET
,只是更换了更正确的名称而已。
3)request._request
获取django内容的htp请求处理对象(WSGIRequest的实例对象)
基本使用
"""django的原生视图类"""
from django.views import View
from django.http.response import JsonResponse
class StudentView(View):
def get(self,request):
print(request) # <WSGIRequest: GET '/req/s1'> from django.core.handlers.wsgi import WSGIRequest
return JsonResponse({"name":"django视图返回的xiaoming"})
"""drf提供的API视图类"""
from rest_framework.views import APIView
from rest_framework.response import Response
class StudentAPIView(APIView):
def get(self, request):
print(request) # <rest_framework.request.Request: GET '/req/s2'>
print(request._request) # <WSGIRequest: GET '/req/s2'>
"""
从上面可以看到,APIView类中提供的request对象是drf单独声明的,但是里面提供了_request属性的值是django视图类提供的request
"""
print(request.query_params)
return Response({"name": "xiaoming"})
def post(self,request):
"""request提供了data属性,基于内容协商以统一的data属性来获取客户端以任何形式提交过来的数据"""
print(request.data)
# 如果客户端提交的是json数据,则request.data得到的数据是一个字典 {'name': 'xiaoming', 'age': 18, 'description': 'xxxxx'}
# 如果客户端提交的是表单数据,则request.data得到的QueryDict类字典 <QueryDict: {'name': ['xiaohong'], 'age': ['20']}>
print(request.query_params)
return Response({"message": "ok"})
Response
rest_framework.response.Response
REST framework提供了一个响应类Response
,使用该类实例化响应对象时,响应的具体数据内容会被renderer(http响应渲染器类)转换成符合前端期望的数据类型。
REST framework提供了Renderer
渲染器,用来根据客户端的请求头中的Accept
(接收数据类型声明)来自动转换响应数据到对应格式。如果前端请求中未进行声明Accept,则会采用Content-Type方式处理响应数据,我们可以通过配置来修改默认响应格式。
可以在rest_framework/settings.py查找所有的drf默认配置项,源码:
# REST_FRAMEWORK 表示字典内部的内容是属于drf独有的配置项, 与django的配置项进行区分。
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ( # 默认http响应渲染类
'rest_framework.renderers.JSONRenderer', # json渲染器,返回json数据
'rest_framework.renderers.BrowsableAPIRenderer', # 浏览器API渲染器,返回api调试界面
)
}
构造方式
Response(data, status=None, template_name=None, headers=None, content_type=None)
drf提供的响应处理类Response和请求处理类Request类不一样,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会根据前端所需类型数据来设置该参数。
response对象的属性
1).data
传给response对象的序列化后,但尚未render处理的数据
2).status_code
状态码的数字
3).content
经过render处理后的响应数据
状态码
为了方便设置状态码,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
视图
Django REST framwork 提供的视图的主要作用:
-
控制序列化器的执行(检验、保存、转换数据)
-
控制数据库模型的操作
普通视图APIView
REST framework 提供了众多的通用视图基类与扩展类,以简化视图的编写。
2个视图基类
APIView基本视图类
rest_framework.views.APIView
APIView
是REST framework提供的所有视图类的基类,继承自Django的View
父类。
APIView
与View
的不同之处在于:
-
传入到视图方法中的是REST framework的
Request
对象,而不是Django的HttpRequeset
对象; -
视图方法可以返回REST framework的
Response
对象,视图会为响应数据设置(renderer)符合前端期望要求的格式; -
任何可以被
APIException
捕获到异常,都将会被APIView处理成合适格式的响应信息返回给客户端;django 的View中所有异常全部以HTML格式显示,不会返回json格式。
drf的APIVIew或者APIView的子类会自动根据客户端的Accept进行错误信息的格式转换。
-
重新声明了一个新的as_view方法并在dispatch()进行路由分发前,会对请求的客户端进行身份认证、权限检查、流量控制。
APIView除了继承了View原有的属性方法意外,还新增了类属性:
-
authentication_classes 值是列表或元组,成员是身份认证类
-
permissoin_classes 值是列表或元组,成员是权限检查类
-
throttle_classes 值是列表或元祖,成员是流量控制类
在APIView
中仍以常规的类视图定义方法来实现get() 、post() 或者其他请求方式的方法。
from rest_framework import status
from rest_framework.views import APIView
from .serializers import StudentModelSerializer, Student
from rest_framework.response import Response
"""
POST /students/ 添加一个学生信息
GET /students/ 获取所有学生信息
GET /students/<pk>/ 获取一个学生信息
PUT /students/<pk>/ 更新一个学生信息
DELETE /students/<pk>/ 删除一个学生信息
一个路由对应一个视图类,所以我们可以把5个API分成2个类来完成
"""
class StudentAPIView(APIView):
def get(self,request):
"""获取所有学生信息"""
# 1. 从数据库中读取学生列表信息
instance_list = Student.objects.all()
# 2. 实例化序列化器,获取序列化对象
serializer = StudentModelSerializer(instance_list, many=True)
# 3. 使用serializer.data实现对数据进行序列化成字典
return Response(serializer.data)
def post(self,request):
"""添加学生信息"""
# 1. 获取客户端提交的数据,实例化序列化器,获取序列化对象
serializer = StudentModelSerializer(data=request.data)
# 2. 反序列化[验证数据、保存数据到数据库]
serializer.is_valid(raise_exception=True)
serializer.save()
# 3. 返回新增的模型数据经过序列化提供给客户端
return Response(serializer.data, status=status.HTTP_201_CREATED)
class StudentInfoAPIView(APIView):
def get(self, request, pk):
"""获取一个学生信息"""
# 1. 使用pk作为条件获取模型对象
instance = Student.objects.get(pk=pk)
# 2. 实例化序列化器对象
serializer = StudentModelSerializer(instance)
# 3. 序列化数据并返回结果
return Response(serializer.data)
def put(self,request, pk):
"""更新一个学生信息"""
# 1. 使用pk作为条件获取模型对象
instance = Student.objects.get(pk=pk)
# 2.获取客户端提交的数据,实例化序列化器,获取序列化对象
serializer = StudentModelSerializer(instance, request.data)
# 3. 反序列化[验证数据、保存数据到数据库]
serializer.is_valid(raise_exception=True)
serializer.save()
# 4. 返回更新后的模型数据经过序列化提供给客户端
return Response(serializer.data)
def delete(self, request, pk):
"""删除一个学生信息"""
# 1. 根据PK值获取要删除的数据并删除
Student.objects.filter(pk=pk).delete()
# 2. 影响删除结构
return Response(status=status.HTTP_204_NO_CONTENT)
GenericAPIView[通用视图类]
通用视图类在继承了APIView的所有功能以外,还提供了几个属性和方法让我们可以把视图中独特的代码抽取出来作为类属性,让视图方法中的代码变得更加通用,方便把通用代码进行简写。
rest_framework.generics.GenericAPIView
继承自APIView
,主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类(混入类)的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类。
提供的关于序列化器使用的1个属性与2个方法
-
属性:
-
serializer_class=A序列化器 指明视图使用的序列化器类
-
-
方法:
-
get_serializer_class(self)
当出现一个视图类中调用多个序列化器时,那么可以通过条件判断在get_serializer_class方法中通过返回不同的序列化器类名就可以让视图方法执行不同的序列化器对象了。
返回序列化器类,默认返回
serializer_class
,可以重写,例如:class Student2GenericAPIView(GenericAPIView): # 整个视图类只使用一个序列化器的情况 # serializer_class = StudentModelSerializert # 整个视图类中使用多个序列化器的情况 def get_serializer_class(self): if self.request.method.lower() == "put": return StudentModelSerializer else: return Student2ModelSerializer queryset = Student.objects.all() def get(self, request, pk): """获取一个模型信息""" serializer = self.get_serializer(instance=self.get_object()) return Response(serializer.data) def put(self, request, pk): """更新一个模型信息""" serializer = self.get_serializer(instance=self.get_object(), data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data)
-
get_serializer(self, args, *kwargs)
返回序列化器对象,主要用来提供给Mixin扩展类使用,如果我们在视图中想要获取序列化器对象,也可以直接调用此方法。
注意,该方法在提供序列化器对象的时候,会向序列化器对象的context属性补充三个数据:request、format、view,这三个数据对象可以在序列化器内部使用。
-
request 当前视图的请求对象
-
view 当前请求的类视图对象
-
format 当前请求期望返回的数据格式
-
-
提供的关于数据库查询的1个属性与2个方法
-
属性:
-
queryset 指明使用的数据查询的结果集
-
-
方法:
-
get_queryset(self)
返回视图使用的查询集QuerySet,主要用来提供给Mixin扩展类使用,是列表视图与详情视图获取数据的基础,默认返回
queryset
属性,可以重写,当获取数据时需要设置一些查询条件,则可以重写如下,例如:def get_queryset(self): user = self.request.user return user.course_list.all()
-
get_object(self)
返回详情视图所需的1个模型类数据对象,主要用来提供给Mixin扩展类使用。
在试图中可以调用该方法获取详情信息的模型类对象。
若详情访问的模型类对象不存在,会返回404异常。
该方法会默认使用APIView提供的check_object_permissions方法检查当前客户端是否有权限访问当前模型对象。
举例:
# url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view()), class BookDetailView(GenericAPIView): queryset = BookInfo.objects.all() serializer_class = BookInfoSerializer def get(self, request, pk): """获取一本书的信息""" book = self.get_object() # get_object()方法 本质上就是 self.queryset.get(pk=pk) serializer = self.get_serializer(book) # 本质上 self.serializer_class(book) return Response(serializer.data)
-
其他可以设置的属性
-
pagination_class 指明分页控制类
-
filter_backends 指明数据过滤控制后端,允许客户端通过地址栏传递过滤参数
from rest_framework.generics import GenericAPIView
"""
APIView中的api接口代码,除了部分涉及到调用模型和序列化器的代码以外,其他代码几乎都是固定写法。
所以,当我们将来针对增删查改的通用api接口编写时,完全可以基于原有的代码进行复用,
那么,drf也考虑到了这个问题,所以提供了一个GenericAPIView(通用视图类),让我们可以把接口中独特的代码单独提取出来作为类属性存在。
rest_framework.generics.GenericAPIView是APIView的子类,在APIView的基础上进行属性扩展提供了2个属性,4个方法,方便我们针对通用接口进行编写。
"""
class StudentGenericAPIView(GenericAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get(self,request):
"""获取所有学生信息"""
# 1. 从数据库中读取学生列表信息
instance_list = self.get_queryset()
# 2. 实例化序列化器,获取序列化对象
# serializer = self.serializer_class(instance_list, many=True)
serializer = self.get_serializer(instance_list, many=True)
# 3. 使用serializer.data实现对数据进行序列化成字典
return Response(serializer.data)
def post(self,request):
"""添加学生信息"""
# 1. 获取客户端提交的数据,实例化序列化器,获取序列化对象
serializer = self.get_serializer(data=request.data)
# 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 = StudentModelSerializer
def get(self, request, pk):
"""获取一个学生信息"""
# 1. 使用pk作为条件获取模型对象
instance = self.get_object()
# 2. 实例化序列化器对象
serializer = self.get_serializer(instance)
# 3. 序列化数据并返回结果
return Response(serializer.data)
def put(self,request, pk):
"""更新一个学生信息"""
# 1. 使用pk作为条件获取模型对象
instance = self.get_object()
# 2.获取客户端提交的数据,实例化序列化器,获取序列化对象
serializer = self.get_serializer(instance, request.data)
# 3. 反序列化[验证数据、保存数据到数据库]
serializer.is_valid(raise_exception=True)
serializer.save()
# 4. 返回更新后的模型数据经过序列化提供给客户端
return Response(serializer.data)
def delete(self, request, pk):
"""删除一个学生信息"""
# 1. 根据PK值获取要删除的数据并删除
self.get_object().delete()
# 2. 影响删除结构
return Response(status=status.HTTP_204_NO_CONTENT)
5个视图扩展类
也叫混入类,作用:提供了几种后端视图(对数据资源进行增删改查)处理流程的实现,如果需要编写的视图属于这五种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。
这五个扩展类需要搭配GenericAPIView通用视图基类,因为五个扩展类的实现需要调用GenericAPIView提供的序列化器与数据库查询的方法。
1)ListModelMixin
列表视图扩展类,提供list(request, *args, **kwargs)
方法快速实现列表视图,返回200状态码。
该Mixin的list方法会对数据进行过滤和分页。
源代码:
class ListModelMixin(object):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
# 过滤
queryset = self.filter_queryset(self.get_queryset())
# 分页
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
# 序列化
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
举例:
"""
通用视图类 + 模型扩展类 实现5个基本API接口
获取多条数据 GenericsAPIView+Mixins.ListModelMixin
"""
from rest_framework.mixins import ListModelMixin
class StudentListAPIView(GenericAPIView, ListModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get(self, request):
"""获取所有学生信息"""
return self.list(request)
2)CreateModelMixin
创建视图扩展类,提供create(request, *args, **kwargs)
方法快速实现创建资源的视图,成功返回201状态码。
如果序列化器对前端发送的数据验证失败,返回400错误。
源代码:
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
# 获取序列化器
serializer = self.get_serializer(data=request.data)
# 验证
serializer.is_valid(raise_exception=True)
# 保存
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
视图代码:
"""
通用视图类 + 模型扩展类 实现5个基本API接口
获取多条数据 GenericsAPIView+Mixins.ListModelMixin
添加一条数据 GenericsAPIView+Mixins.CreateModelMixin
"""
from rest_framework.mixins import ListModelMixin, CreateModelMixin
class StudentListAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get(self, request):
"""获取所有学生信息"""
return self.list(request)
def post(self, request):
"""添加学生信息"""
return self.create(request)
3)RetrieveModelMixin
详情视图扩展类,提供retrieve(request, *args, **kwargs)
方法,可以快速实现返回一个存在的数据对象。
如果存在,返回200, 否则返回404。
源代码:
class RetrieveModelMixin(object):
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
# 获取对象,会检查对象的权限
instance = self.get_object()
# 序列化
serializer = self.get_serializer(instance)
return Response(serializer.data)
视图代码:
"""
获取一条数据 GenericsAPIView+Mixins.RetrieveModelMixin
"""
from rest_framework.mixins import RetrieveModelMixin
class StudentRetrieveAPIView(GenericAPIView, RetrieveModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get(self,request, pk):
"""获取一个学生信息"""
return self.retrieve(request, pk)
4)UpdateModelMixin
更新视图扩展类,提供update(request, *args, **kwargs)
方法,可以快速实现更新一个存在的数据对象。
同时也提供partial_update(request, *args, **kwargs)
方法,可以实现局部更新。
成功返回200,序列化器校验数据失败时,返回400错误。
源代码:
class UpdateModelMixin(object):
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
视图代码:
from rest_framework.mixins import RetrieveModelMixin,UpdateModelMixin
"""
获取一条数据 GenericsAPIView+Mixins.RetrieveModelMixin
更新一条数据 GenericsAPIView+Mixins.UpdateModelMixin
删除一条数据 GenericsAPIView+Mixins.DestroyModelMixin
"""
class StudentRetrieveAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get(self,request, pk):
"""获取一个学生信息"""
return self.retrieve(request, pk)
def put(self,request, pk):
"""更新一个学生信息"""
return self.update(request, pk)
5)DestroyModelMixin
删除视图扩展类,提供destroy(request, *args, **kwargs)
方法,可以快速实现删除一个存在的数据对象。
成功返回204,不存在返回404。
源代码:
class DestroyModelMixin(object):
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
视图代码:
"""
获取一条数据 GenericsAPIView+Mixins.RetrieveModelMixin
更新一条数据 GenericsAPIView+Mixins.UpdateModelMixin
删除一条数据 GenericsAPIView+Mixins.DestroyModelMixin
"""
from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
class StudentRetrieveAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
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)
整体代码,使用GenericAPIView结合视图扩展类,实现5个基本api接口,视图代码:
"""
通用视图类 + 模型扩展类 实现5个基本API接口
获取多条数据 GenericsAPIView+Mixins.ListModelMixin
添加一条数据 GenericsAPIView+Mixins.CreateModelMixin
"""
from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, \
UpdateModelMixin, DestroyModelMixin
class StudentListAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def get(self, request):
"""获取所有学生信息"""
return self.list(request)
def post(self, request):
"""添加学生信息"""
return self.create(request)
"""
获取一条数据 GenericsAPIView+Mixins.RetrieveModelMixin
更新一条数据 GenericsAPIView+Mixins.UpdateModelMixin
删除一条数据 GenericsAPIView+Mixins.DestroyModelMixin
"""
class StudentRetrieveAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
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)
路由代码:
from django.urls import path,re_path
from . import views
urlpatterns = [
# APIView
path("students/", views.StudentAPIView.as_view()),
re_path("^students/(?P<pk>\d+)/$", views.StudentInfoAPIView.as_view()),
# GenericAPIView
path("students1/", views.StudentGenericAPIView.as_view()),
re_path("^students1/(?P<pk>\d+)/$", views.StudentInfoGenericAPIView.as_view()),
# GenericAPIView + Mixins
path("students2/", views.StudentListAPIView.as_view()),
re_path("^students2/(?P<pk>\d+)/$", views.StudentRetrieveAPIView.as_view()),
]
9个视图子类
1)ListAPIView
提供了get视图方法,内部调用了模型扩展类的list方法
继承自:GenericAPIView、ListModelMixin
2)CreateAPIView
提供了post视图方法,内部调用了模型扩展类的create方法
继承自: GenericAPIView、CreateModelMixin
3)RetrieveAPIView
提供了get视图方法,内部调用了模型扩展类的retrieve方法
继承自: GenericAPIView、RetrieveModelMixin
4)DestroyAPIView
提供了delete视图方法,内部调用了模型扩展类的destroy方法
继承自: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)RetrieveDestroyAPIView
提供 get、delete方法
继承自:GenericAPIView、RetrieveModelMixin、DestroyModelMixin
9)RetrieveUpdateDestroyAPIView
提供 get、put、patch、delete方法
继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin
视图代码:
"""
上面的接口代码还可以继续更加的精简,drf在使用GenericAPIView和Mixins进行组合以后,还提供了视图子类。
视图子类是通用视图类 和 模型扩展类 的子类,提供了各种的视图方法调用mixins操作
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
"""
from rest_framework.generics import ListAPIView, CreateAPIView, \
RetrieveAPIView, UpdateAPIView, DestroyAPIView, \
ListCreateAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView
# class StuListAPIView(ListAPIView, CreateAPIView):
class StuListAPIView(ListCreateAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# class StuRetrieveAPIView(RetrieveAPIView, UpdateAPIView, DestroyAPIView):
# class StuRetrieveAPIView(RetrieveUpdateAPIView, DestroyAPIView):
# class StuRetrieveAPIView(RetrieveDestroyAPIView, UpdateAPIView):
class StuRetrieveAPIView(RetrieveUpdateDestroyAPIView):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
视图集ViewSet
使用视图集ViewSet,可以将一系列视图相关的代码逻辑和相关的http请求动作封装到一个类中:
-
list() 提供一组数据
-
retrieve() 提供单个数据
-
create() 创建数据
-
update() 保存数据
-
destory() 删除数据
ViewSet视图集类不再限制视图方法名为get/post...等这种情况了,而是实现允许开发者根据自己的需要定义自定义视图方法名,例如 list() 或get_all()、create() 等,然后经过路由中使用http请求动作和这些视图方法名action进行绑定映射调用。
视图集在使用as_view()方法时,设置字典参数允许我们将代表视图方法名与具体http请求进行绑定。
常用视图集父类
1) ViewSet
继承自APIView
与ViewSetMixin
,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。
ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典{“http请求”:“视图方法”}的映射处理工作,如{'get':'list'},
在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。
上面代码如果想要合并成一个视图类,实现一个视图类提供5个甚至更多的API接口,则需要继承视图集(ViewSet) 为什么要继承视图集(ViewSet)? 1. 上面使用的视图类都是基于APIView实现的,而APIView基于django提供的View视图基类, 而django的视图类View在内部实现时,dispatch方法中限制了当前视图必须采用get/post/put/delete/patch等http请求动作作为方法名,但是 如果要实现5个api接口,必然要实现2个get请求,分别是获取多条数据与获取一条数据,一个类中肯定不存在同名的方法。 2. 5个接口api接口中,只有删除、获取、更新一条数据时需要地址栏传递ID,而添加一条数据与获取多条数据,实际上并不需要ID。 所以,路由有决定我们要分开写5个接口。 drf为了解决上面的2个问题,提供了视图集和路由集。 视图集就可以帮我们实现一个视图类响应多种重复的http请求 路由集就可以帮我们实现自动根据不同的视图方法来生成不同参数的路由地址。 from rest_framework.viewsets import ViewSet # ViewSet是APIView的子类,是所有drf中的视图集的父类
"""使用ViewSet基本视图集实现5个基本api接口"""
from rest_framework.viewsets import ViewSet
class StudentViewSet(ViewSet):
def list(self,request):
"""获取多个数据"""
# 1. 从数据库中读取模型列表信息
instance_list = Student.objects.all()
# 2. 实例化序列化器,获取序列化对象
serializer = StudentModelSerializer(instance_list, many=True)
# 3. 使用serializer.data实现对数据进行序列化成字典
return Response(serializer.data)
def create(self, request):
"""添加数据"""
# 1. 获取客户端提交的数据,实例化序列化器,获取序列化对象
serializer = StudentModelSerializer(data=request.data)
# 2. 反序列化[验证数据、保存数据到数据库]
serializer.is_valid(raise_exception=True)
serializer.save()
# 3. 返回新增的模型数据经过序列化提供给客户端
return Response(serializer.data, status=status.HTTP_201_CREATED)
def retrieve(self, request, pk):
"""获取一个学生信息"""
# 1. 使用pk作为条件获取模型对象
instance = Student.objects.get(pk=pk)
# 2. 实例化序列化器对象
serializer = StudentModelSerializer(instance)
# 3. 序列化数据并返回结果
return Response(serializer.data)
def update(self,request, pk):
"""更新一个学生信息"""
# 1. 使用pk作为条件获取模型对象
instance = Student.objects.get(pk=pk)
# 2.获取客户端提交的数据,实例化序列化器,获取序列化对象
serializer = StudentModelSerializer(instance, request.data)
# 3. 反序列化[验证数据、保存数据到数据库]
serializer.is_valid(raise_exception=True)
serializer.save()
# 4. 返回更新后的模型数据经过序列化提供给客户端
return Response(serializer.data)
def destroy(self, request, pk):
"""删除一个学生信息"""
# 1. 根据PK值获取要删除的数据并删除
Student.objects.filter(pk=pk).delete()
# 2. 影响删除结构
return Response(status=status.HTTP_204_NO_CONTENT)
在设置路由时,我们可以如下操作
from django.urls import path,re_path
from . import views
urlpatterns = [
# ViewSet
# 视图集的路由注册格式
# path("访问路径", views.视图集类名.as_view({"http请求方法名": "视图方法名", "http请求方法名": "视图方法名", ...})),
path("students4/", views.StudentViewSet.as_view({"get": "list", "post": "create"})),
re_path("^students4/(?P<pk>\d+)/$", views.StudentViewSet.as_view({
"get": "retrieve",
"put": "update",
"delete": "destroy",
})),
]
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
class StudentGenericViewSet(GenericViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
def list(self,request):
"""获取多个数据"""
# 1. 从数据库中读取模型列表信息
instance_list = self.get_queryset()
# 2. 实例化序列化器,获取序列化对象
serializer = self.get_serializer(instance_list, many=True)
# 3. 使用serializer.data实现对数据进行序列化成字典
return Response(serializer.data)
def create(self, request):
"""添加数据"""
# 1. 获取客户端提交的数据,实例化序列化器,获取序列化对象
serializer = self.get_serializer(data=request.data)
# 2. 反序列化[验证数据、保存数据到数据库]
serializer.is_valid(raise_exception=True)
serializer.save()
# 3. 返回新增的模型数据经过序列化提供给客户端
return Response(serializer.data, status=status.HTTP_201_CREATED)
def retrieve(self, request, pk):
"""获取一个数据"""
# 1. 使用pk作为条件获取模型对象
instance = self.get_object()
# 2. 实例化序列化器对象
serializer = self.get_serializer(instance)
# 3. 序列化数据并返回结果
return Response(serializer.data)
def update(self,request, pk):
"""更新一个数据"""
# 1. 使用pk作为条件获取模型对象
instance = self.get_object()
# 2.获取客户端提交的数据,实例化序列化器,获取序列化对象
serializer = self.get_serializer(instance, request.data)
# 3. 反序列化[验证数据、保存数据到数据库]
serializer.is_valid(raise_exception=True)
serializer.save()
# 4. 返回更新后的模型数据经过序列化提供给客户端
return Response(serializer.data)
def destroy(self, request, pk):
"""删除一个数据"""
# 1. 根据PK值获取要删除的数据并删除
self.get_object().delete()
# 2. 影响删除结构
return Response(status=status.HTTP_204_NO_CONTENT)
路由代码:
path("students5/", views.StudentGenericViewSet.as_view({"get": "list", "post": "create"})),
re_path("^students5/(?P<pk>\d+)/$", views.StudentGenericViewSet.as_view({
"get": "retrieve",
"put": "update",
"delete": "destroy",
})),
结合我们上面学习的模型扩展类,实现简写操作,视图代码:
"""
使用通用视图集类+模型扩展类,直接达到简写api代码的目的
"""
class StudentGenViewSet(GenericViewSet,
ListModelMixin,
CreateModelMixin,
RetrieveModelMixin,
UpdateModelMixin,
DestroyModelMixin
):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
ModelViewSet
继承自GenericViewSet
,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。
"""
ModelViewSet = GenericViewSet + ListModelMixin + CreateModelMixin+
RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin
"""
from rest_framework.viewsets import ModelViewSet
class StudentModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# ModelViewSet 模型视图集
path("students7/", views.StudentModelViewSet.as_view({"get": "list", "post": "create"})),
re_path("^students7/(?P<pk>\d+)/$", views.StudentModelViewSet.as_view({
"get": "retrieve",
"put": "update",
"delete": "destroy",
})),
ReadOnlyModelViewSet
继承自GenericViewSet
,同时包括了ListModelMixin、RetrieveModelMixin。
"""
ReadOnlyModelViewSet = RetrieveModelMixin + ListModelMixin + GenericViewSet
"""
from rest_framework.viewsets import ReadOnlyModelViewSet
class StudentReadOnlyModelViewSet(ReadOnlyModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# ReadOnlyModelViewSet 只读视图集
path("students8/", views.StudentReadOnlyModelViewSet.as_view({"get": "list"})),
re_path("^students8/(?P<pk>\d+)/$", views.StudentReadOnlyModelViewSet.as_view({"get": "retrieve"})),
路由集Routers
对于视图集ViewSet,我们除了可以自己手动指明请求方式与视图方法之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息。如果是非视图集,不需要使用路由集routers。
REST framework提供了两个router类,使用方式一致的。结果多一个或少一个根目录url地址的问题而已。
-
SimpleRouter 线上运营项目
-
DefaultRouter 本地开发,项目上线前
使用方法
1) 创建router对象,并注册视图集。
demo/urls.py,代码:
"""使用路由集给视图集生成url路由"""
from rest_framework.routers import DefaultRouter, SimpleRouter
# 实例化路由对象
router = SimpleRouter()
# 注册视图集[每次注册一个视图集类,就需要调用register]
router.register("students9", views.StuModelViewSet, basename="student9")
router.register("students10", views.StuModelViewSet, basename="students10")
# 所有被注册的实体集生成的路由信息,全部会被集中到router.urls路由列表中,所以我们要把urls拼接到urlpatterns
urlpatterns += router.urls
register(prefix, viewset, basename)
-
prefix 该视图集的路由前缀
-
viewset 视图集
-
basename 路由别名的前缀
如上述代码会形成的路由如下:
url: ^students9/$ basename: students9-list
url: ^students9/(?P<pk>[^/.]+)/$ basename: students9-detail
2)把路由对象生成的视图集的路由列表添加到django的路由中可以有两种方式:
urlpatterns = [
...
]
urlpatterns += router.urls
或
from django.urls import include,re_path
urlpatterns = [
...
path("", include(router.urls))
]
视图集中附加action的声明
在视图集中,如果想要让Router自动帮助我们为自定义视图方法生成对应路由信息,需要使用rest_framework.decorators.action
装饰器。action装饰器可以让开发者在视图中绑定要路由集生成的url地址
action装饰器可以接收两个参数:
-
methods: 声明该action对应的请求方式,列表参数
-
detail: 声明该action的路径是否为单一资源,也就是使用生成附带pk值的url路径
-
True 表示路径格式是
<prefix>/<pk>/<url_path>/
-
False 表示路径格式是
<prefix>/<url_path>/
-
-
url_path:声明该action的路由尾缀。默认就是视图方法名
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
class StuModelViewSet(ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentModelSerializer
# @action(methods=["GET", "POST"], detail=False, url_path="abc")
@action(methods=["GET", "POST"], detail=False)
def login(self, request):
print(self.action) # 获取当前视图方法名
return Response("用户登录")
@action(methods=["GET"], detail=True)
def logout(self,request, pk): # 当action装饰的参数detail值为True时,表示当前视图必须接收一个pk参数
return Response("注销登录")
由路由器自动为此视图集自定义action方法形成的路由会是如下内容:
url: ^students9/login/$ basename: students9-login
url: ^students9/(?P<pk>[^/.]+)/logout/$ basename: students9-logout