在讲DRF框架之前先简单了解一下下面内容:
在WEB开发应用中,有2种应用模式:前后端不分离、前后端分离;
RESTful API通常根据 GET/POST/PUT/DELETE 来区分操作资源的动作,是一种风格,而不是一种约束和规约
统一接口:
GET(SELECT):从服务器查询,可以在服务器通过请求的参数区分查询的方式。
POST(CREATE):在服务器新建一个资源,调用insert操作。
PUT(UPDATE):在服务器更新资源,调用update操作。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。(目前jdk7未实现,tomcat7也不行)。
DELETE(DELETE):从服务器删除资源,调用delete语句。
状态码和返回数据:
主要分为五大类:
1xx:相关信息
2xx:操作成功
3xx:重定向
4xx:客户端错误
5xx:服务器错误
如果当前url是 http://localhost:8080/User
那么用户只要请求这样同一个URL就可以实现不同的增删改查操作,例如
http://localhost:8080/User?_method=get&id=1001 这样就可以通过get请求获取到数据库 user 表里面 id=1001 的用户信息
http://localhost:8080/User?_method=post&id=1001&name=zhangsan 这样可以向数据库 user 表里面插入一条记录
http://localhost:8080/User?_method=put&id=1001&name=lisi 这样可以将 user表里面 id=1001 的用户名改为lisi
http://localhost:8080/User?_method=delete&id=1001 这样用于将数据库 user 表里面的id=1001 的信息删除
这样定义的规范我们就可以称之为restful风格的API接口,我们可以通过同一个url来实现各种操作。
DRF框架全称是:Django REST framework,它建立在Django基础之上的WEB应用开发框架,基于前后端分离的开发模式,可以快速的开发REST API接口应用;
在REST framework中,提供了序列化器Serialzier的定义,可以帮助我们简化序列化与反序列化的过程,不仅如此,还提供丰富的类视图、扩展类、视图集来简化视图的编写工作。REST framework还提供了认证、权限、限流、过滤、分页、接口文档等功能支持。REST framework提供了一个API 的Web可视化界面来方便查看测试接口。
特点:
- 提供了定义序列化器Serializer的方法,可以快速根据 Django ORM 或者其它库自动序列化/反序列化;
- 提供了丰富的类视图、Mixin扩展类,简化视图的编写;
- 丰富的定制层级:函数视图、类视图、视图集合到自动生成 API,满足各种需要;
- 多种身份认证和权限认证方式的支持;[jwt]
- 内置了限流系统;
- 直观的 API web 界面;
- 可扩展性,插件丰富
使用DRF框架的优势:
在使用Django写接口代码时,会有很多痛点:
a.代码冗余非常严重,不够简洁
b.数据校验极为麻烦(往往需要嵌套多级if条件语句),校验逻辑的复用性较差
c.代码没有统一的规范,代码维护性较差
d.获取项目列表数据时,有很多功能缺失
- 没有提供分页功能
- 没有提供过滤功能
- 也没有提供排序功能
e.整个项目的痛点
- 没有提供认证授权功能
- 没有提供限流功能
- 传递参数形式单一,只支持json格式的参数,不支持x-www-form-urlencoded
- 5个接口无法放在同一个类视图中
然而,django restframwork框架解决以上问题
官方文档:https://www.django-rest-framework.org/
安装:
pip install djangorestframework
添加rest_framework应用:
在settings.py的INSTALLED_APPS中添加’rest_framework’。
INSTALLED_APPS = [
...
'rest_framework',
]
DRF框架中实现序列化、反列化、数据操作;
序列化与反列化的定义:
序列化:如果前端是GET请求,则构造查询集,将结果返回,这个过程为序列化;
反序列化:如果前端是POST请求,要对数据库进行改动,则需要拿到前端发来的数据,进行校验,校验通过将数据写入数据库,这个过程称为反序列化;
在子应用中创建serializers.py文件,文件名推荐命名为serializers.py;
class ProjectSerializer(serializers.Serializer):
# id = serializers.IntegerField(label='项目id', help_text='项目id', max_value=1000, min_value=1)
# name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5, write_only=True)
name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5)
# leader = serializers.CharField(label='项目负责人', help_text='项目负责人', allow_null=True)
leader = serializers.CharField(label='项目负责人', help_text='项目负责人', default='阿名')
is_execute = serializers.BooleanField()
# update_time = serializers.DateTimeField()
注意点:
1.必须得继承Serializer类或者Serializer子类
2.定义的序列化器类中,字段名要与模型类中的字段名保持一致;
3.定义的序列化器类的字段(类属性)为Field子类
4.默认定义哪些字段,那么哪些字段就会返回前端,同时也必须的输入(前端需要传递)
5.常用的序列化器字段类型
IntegerField -> int
CharField -> str
BooleanField -> bool
DateTimeField -> datetime
6.可以在序列化器字段中指定不同的选项
label和help_text,与模型类中的verbose_name和help_text参数一样
IntegerField,可以使用max_value指定最大值,min_value指定最小值
CharField,可以使用max_length指定最大长度,min_length指定最小长度,error_messages用来自定义错误信息
7.定义的序列化器字段中,required默认为True,说明默认定义的字段必须得输入和输出
8.如果在序列化器字段中,设置required=False,那么前端用户可以不传递该字段(校验时会忽略改该字段,所以不会报错)
9.如果未定义模型类中的某个字段,那么该字段不会输入,也不会输出
10.前端必须的输入(反序列化输入)name(必须得校验),但是不会需要输出(序列化器输出)?
如果某个参数指定了write_only=True,那么该字段仅仅只输入(反序列化输入,做数据校验),不会输出(序列化器输出),
默认write_only为False
11.前端可以不用传递,但是后端需要输出?
如果某个参数指定了read_only=True,那么该字段仅仅只输出(序列化器输出),不会输入(反序列化输入,做数据校验),
默认read_only为False
12.在序列化器类中定义的字段,默认allow_null=False,该字段不允许传递null空值,如果指定allow_null=True,那么该字段允许传递null;
13.在序列化器类中定义CharField字段,默认allow_blank=False,该字段不允许传递空字符串,如果指定allow_blank=True,那么该字段允许传递空字符串
14.在序列化器类中定义的字段,可以使用default参数来指定默认值,如果指定了default参数,那么前端用户可以不用传递,会将default指定的值作为入参
get请求(数据查询):数据库操作(读取所有项目数据) -> 序列化输出操作(将模型对象转化为Python中的基本类型),可以使用序列化器进行序列化输出操作:
def get(self, request):
queryset = Projects.objects.all()
serializer = ProjectSerilizer(instance=queryset, many=True)
return JsonResponse(serializer.data, safe=False)
post请求(数据插入):数据校验(规范传入的参数) —> 反序列化输入操作(将json格式的数据转化为复杂的类型) -> 数据库操作(创建项目数据)-> 序列化输出操作(将模型对象转化为Python中的基本类型):
# views.py
class ProjectsView(View):
def post(self, request):
try:
python_data = json.loads(request.body)
except Exception as e:
return JsonResponse({'msg': '参数有误'}, status=400)
serializer11 = ProjectSerilizer(data=python_data)
# 校验数据
if not serializer11.is_valid():
return JsonResponse(serializer11.errors, status=400)
# project_obj = Projects.objects.create(**serializer11.validated_data)
serializer11.save()
return JsonResponse(serializer11.data, status=201)
注意:
return JsonResponse(serializer11.data, status=201)serializer11.data这种写法是在两个前提下,否则会报错:
反序列化过程会对数据进行校验
校验顺序:对字段类型进行校验 -> 依次验证validators列表中的校验规则 -> 从右到左依次验证其他规则 -> 调用单字段校验方法 -> 调用多字段联合校验方法validate方法
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from interfaces.models import Interfaces
from .models import Projects
# 类外函数
def is_contains_keyword(value):
if '项目' not in value:
raise serializers.ValidationError('项目名称中必须得包含“项目”关键字')
class ProjectSerilizer(serializers.Serializer):
# 1、对字段类型进行校验
# 2、依次验证validators列表中的校验规则,validators=[UniqueValidator(queryset=Projects.objects.all())]
# 3、从右到左依次验证其他规则 max_length=20, min_length=5...等
name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5,
error_messages={
'min_length': '项目名称不能少于5位',
'max_length': '项目名称不能超过20位'
}, validators=[UniqueValidator(queryset=Projects.objects.all(), message='项目名称不能重复'),
is_contains_keyword])
leader = serializers.CharField(label='项目负责人', help_text='项目负责人', default='阿名')
is_execute = serializers.BooleanField()
# 调用单字段校验方法
# 单字段校验
def validate_name(self, attr: str):
if not attr.endswith('项目'):
raise serializers.ValidationError('项目名称必须得以“项目”结尾')
return attr
# 调用多字段校验方法
# 多字段联合校验
def validate(self, attrs: dict):
if len(attrs.get('leader')) <= 4 or not attrs.get('is_execute'):
raise serializers.ValidationError('项目负责人名称长度不能少于4位或者is_execute参数为False')
return attrs
以上代码为例的校验顺序为:
1)可以在序列化器字段上使用validators指定自定义的校验规则
2)validators必须得为序列类型(列表),在列表中可以添加多个校验规则
3)DRF框架自带UniqueValidator校验器,必须得使用queryset指定查询集对象,用于对该字段进行校验
4)UniqueValidator校验器,可以使用message指定自定义报错信息
5)类外自定义校验函数is_contains_keyword,可以在类外自定义校验函数
- 第一个参数为待校验的值
- 如果校验不通过,必须得抛出serializers.ValidationError(‘报错信息’)异常,同时可以指定具体的报错信息
- 需要将校验函数名放置到validators列表中
1)可以任意序列化器字段上使用error_messages来自定义错误提示信息;
2)使用校验选项名(校验方法名)作为key,把具体的错误提示信息作为value;
3)DateTimeField可以使用format参数指定格式化字符串,update_time =serializers.DateTimeField(label=‘更新时间’,format=‘%Y年%m月%d日 %H:%M:%S’)
1)可以在序列化器类中对单个字段进行校验;
2)单字段的校验方法名称,必须把validate_作为前缀,加上待校验的字段名,如:validate_待校验的字段名;
3)如果校验不通过,必须得返回serializers.ValidationError(‘具体报错信息’)异常;
4)如果校验通过,往往需要将校验之后的值,返回;
5)如果该字段在定义时添加的校验规则不通过,那么是不会调用单字段的校验方法;
1)可以在序列化器类中对多个字段进行联合校验;
2)使用固定的validate方法,会接收上面校验通过之后的字典数据;
3)当所有字段定义时添加的校验规则都通过,且每个字段的单字段校验方法通过的情况下,才会调用validate;
PrimaryKeyRelatedField字段:
# interfaces_set = serializers.PrimaryKeyRelatedField(label='项目所属接口id', help_text='项目所属接口id', many=True, read_only=True)
interfaces_set = serializers.PrimaryKeyRelatedField(label='项目所属接口id', help_text='项目所属接口id',many=True, queryset=Interfaces.objects.all(), write_only=True)
StringRelatedField字段:
interfaces_set = serializers.StringRelatedField(many=True)
SlugRelatedField字段:
interfaces_set = serializers.SlugRelatedField(slug_field='name', many=True, queryset=Interfaces.objects.all())
创建数据create
# views.py
serializer11 = ProjectSerilizer(data=python_data)
serializer11.save()
# 调用save方法时会自动调用serializers.py中的create方法
#---分隔线----
# serializers.py
class ProjectSerializer(serializers.Serializer):
name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5,
error_messages={"min_length": "最短不能少于5位"},
validators=[UniqueValidator(queryset=Projects.objects.all(), message="项目名称不能重复"),
is_contains_keyword])
leader = serializers.CharField(label='项目负责人', help_text='项目负责人')
is_execute = serializers.BooleanField(write_only=True)
def create(self, validated_data):
myname = validated_data.pop('myname')
myage = validated_data.pop('myage')
validated_data.pop('sms_code')
project_obj = Projects.objects.create(**validated_data)
return project_obj
更新数据update
def put(self, request, pk):
try:
project_obj = Projects.objects.get(id=pk)
python_data = json.loads(request.body)
except Exception as e:
return JsonResponse({'msg': '参数有误'}, status=400)
serializer = ProjectSerilizer(instance=project_obj, data=python_data)
if not serializer.is_valid():
return JsonResponse(serializer.errors, status=400)
serializer.save()
return JsonResponse(serializer.data, status=201)
def update(self, instance, validated_data: dict):
# for key, value in validated_data.items():
# setattr(instance, key, value)
instance.name = validated_data.get('name') or instance.name
instance.leader = validated_data.get('leader') or instance.leader
instance.is_execute = validated_data.get('is_execute') or instance.is_execute
instance.desc = validated_data.get('desc') or instance.desc
instance.save()
return instance
允许改变我们反序列化的输出,处理反序列化的输入数据
class ProjectSerilizer(serializers.Serializer):
def to_internal_value(self, data):
# 1、to_internal_value方法,是所有字段开始进行校验时入口方法(最先调用的方法)
# 2、会依次对序列化器类的各个序列化器字段进行校验:
# 对字段类型进行校验 -> 依次验证validators列表中的校验规则 -> 从右到左依次验证其他规则 -> 调用单字段校验方法
# to_internal_value调用结束 -> 调用多字段联合校验方法validate方法
tmp = super().to_internal_value(data)
# 对各个单字段校验结束之后的数据进行修改
return tmp
允许我们改变序列化的输出;
class ProjectSerilizer(serializers.Serializer):
def to_representation(self, instance):
# 1、to_representation方法,是所有字段开始进行序列化输出时的入口方法(最先调用的方法)
tmp =super().to_representation(instance)
return tmp
模型类序列化器ModelSerilizer,跟表模型有对应关系
class ProjectModelSerializer(serializers.ModelSerializer):
"""
定义模型序列化器类
1、继承serializers.ModelSerializer类或者其子类
2、需要在Meta内部类中指定model、fields、exclude类属性参数
3、model指定模型类(需要生成序列化器的模型类)
4、fields指定模型类中哪些字段需要自动生成序列化器字段
5、会给id主键、指定了auto_now_add或者auto_now参数的DateTimeField字段,添加read_only=True,仅仅只进行序列化输出
6、有设置unique=True的模型字段,会自动在validators列表中添加唯一约束校验
class Meta:
model = Projects
# fields指定模型类中哪些字段需要自动生成序列化器字段
# a.如果指定为"__all__",那么模型类中所有的字段都需要自动转化为序列化器字段
# b.可以传递需要转化为序列化器字段的模型字段名元组
# c.fields元组中必须指定进行序列化或者反序列化操作的所有字段名称,指定了'__all__'和exclude除外
fields = "__all__"
# fields = ('id', 'name', 'leader')
# c.exclude指定模型类中哪些字段不需要转化为序列化器字段,其他的字段都需要转化
# exclude = ('create_time', 'update_time')
tips:主动查看生成的字段方法:然后到terminal下执行 python manage.py shell,然后执行from projects.serializers import ProjectsModelSerializer,然后执行ProjectsModelSerializer();
方式一:
# 重新定义模型类中同名的字段
name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5,error_messages={'min_length': '项目名称不能少于5位','max_length': '项目名称不能超过20位'}, validators=[UniqueValidator(queryset=Projects.objects.all(),message='项目名称不能重复'), is_contains_keyword])
interfaces_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
# 新定义的字段,只做序列化输出,输入时不会校验;
token = serializers.CharField(read_only=True)
方式二:
# 重新定义模型类中同名的字段
name = serializers.CharField(label='项目名称', help_text='项目名称', max_length=20, min_length=5,error_messages={'min_length': '项目名称不能少于5位','max_length': '项目名称不能超过20位'}, validators=[UniqueValidator(queryset=Projects.objects.all(),message='项目名称不能重复'), is_contains_keyword])
interfaces_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
# 新定义的字段,只做序列化输出,输入时不会校验;
token = serializers.CharField(read_only=True)
extra_kwargs = {
'leader': {
'label': '负责人',
'max_length': 15,
'min_length': 2,
# 'read_only': True
# 'validators': []},
'name': {'min_length': 5}}
# 可以将需要批量需要设置read_only=True参数的字段名添加到Meta中read_only_fields元组
read_only_fields = ('leader', 'is_execute', 'id')
def create(self, validated_data):
"""
a.继承ModelSerializer之后,ModelSerializer中实现了create和update方法
b.一般无需再次定义create和update方法
c.如果父类提供的create和update方法不满足需要时,可以重写create和update方法,最后再调用父类的create和update方法
:param validated_data:
:return:
"""
validated_data.pop('myname')
validated_data.pop('myage')
instance = super().create(validated_data)
instance.token = "xxxxxxx"
return instance
class ProjectsView(APIView):
"""
继承APIView父类(Django中View的子类)
a.具备View的所有特性
b.提供了认证、授权、限流功能
"""
def get(self, request):
queryset = Projects.objects.all()
serializer = ProjectSerilizer(instance=queryset, many=True)
# 在DRF中Response为HTTPResponse的子类
# a.data参数为序列化之后的数据(一般为字典或嵌套字典的列表)
# b.会自动根据渲染器来将数据转化为请求头中Accept需要的格式进行返回
# c.status指定响应状态码
# d.content_type指定响应头中的Content-Type,一般无需指定,会根据渲染器来自动设置
return Response(serializer.data, status=status.HTTP_200_OK, content_type='ap')
def post(self, request):
# a.一旦继承APIView之后,request是DRF中Request对象
# b.Request是在HttpRequest基础上做了拓展
# c.兼容HttpRequest的所有功能
# d.前端传递的查询字符串参数:GET、query_params
# e.前端传递application/json、application/x-www-form-urlencoded、multipart/form-data参数
# 可以根据请求头中Content-Type,使用统一的data属性获取
# try:
# python_data = json.loads(request.body)
# except Exception as e:
# return JsonResponse({'msg': '参数有误'}, status=400)
# python_data = request.data
serializer = ProjectModelSerializer(data=request.data)
# if not serializer.is_valid():
# # return JsonResponse(serializer11.errors, status=400)
# return Response(serializer.errors, status=400)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
parsers=self.get_parsers()就是解析模块源码的入口
解析器和渲染器类的修改
# DRF中的解析器(类)
# 1、可以根据请求头中的Content-Type来自动解析参数,使用统一的data属性来获取即可
# 2、默认JSONParser、FormParser、MultiPartParser三个解析器类
# JSONParser:media_type = 'application/json'
# FormParser:media_type = 'application/x-www-form-urlencoded'
# MultiPartParser: media_type = 'multipart/form-data'
# 3、可以在全局配置文件(settings.py)中修改DRF全局参数,把REST_FRAMEWORK作为名称
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
# 'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser'
],
# 4、局部配置
# 默认全局配置是因为我们写的视图继承自APIView,APIView中配置了类属性parser_classes,所以我们自己编写的视图函数中,也设置个类属性,并且导入JSONParser解析器
from rest_framework.parsers import JSONParser
class TestView(APIView):
# 局部解析类配置,只加JSONParser配置后,就只能application/json提交数据,其他方式会报错
parser_classes = [JSONParser]
def post(self, request, *args, **kwargs):
print(request.data)
return Response("drf post ok")
# DRF中的渲染器(类)
# 1、可以根据请求头中的Accept参数来自动渲染前端需要的数据格式
# 2、默认的渲染器为JSONRenderer、BrowsableAPIRenderer
# 3、如果前端请求头未指定Accept参数或者指定为application/json,那么会自动返回json格式的数据
# 4、如果前端请求头指定Accept参数为text/html,那么默认会返回可浏览的api页面(api进行管理)
# 5、可以在DEFAULT_RENDERER_CLASSES中指定需要使用的渲染器
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
# 'rest_framework.renderers.BrowsableAPIRenderer',
],
}
继承自APIVIew,增加了对于列表视图和详情视图可能用到的通用支持方法。通常使用时,可搭配一个或多个Mixin扩展类。
注意:查询结果集是会被缓存的,相当于,当我们执行模型类.objects.all()后,数据会放到缓存中
GenericAPIView提供了两个类属性,queryset(指定查询集)和serializer_class(指定序列化器类)
# 需继承GenericAPIView
#class ProjectViews(APIView):
class ProjectViews(GenericAPIView):
queryset = Projects.objects.all()
serializer_class = serializers.ProjectModelSerializer
def get(self, request: Request):
...
# 一般不用self.queryset获取,而使用self.get_queryset获取类属性queryset
queryset = self.get_queryset()
# 同上,使用self.get_serializer获取类属性serializer_class
self.get_serializer(instance=queryset)
get_object可以获取模型对象,无需传递外键值,会自动获取url中传入的pk值(若自定义名字,则需指定lookup_field=‘ww’,那么url中的变量也要是ww,如‘projects/int:ww’)
project_obj = self.get_object()
源码如下:
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
########此处lookup_url_kwarg为url传过来的pk
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
a、指定过滤器
第一种方式:全局指定
在django的settings设置以下过滤引擎类,在全局DEFAULT_FILTER_BACKENDS指定使用的过滤引擎类(SearchFilter为搜索引擎类)
REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': [
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter']}
第二种方式:在视图中指定
from rest_framework.filters import SearchFilter
class ProjectViews(GenericAPIView):
...
filter_backends = [SearchFilter]
class ProjectViews(GenericAPIView):
queryset = Projects.objects.all()
serializer_class = serializers.ProjectModelSerializer
search_fields = ['^name', '=leader']
def get(self, request: Request):
# filter_queryset需要传递查询集对象作为参数
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(instance=queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
b、修改参数
注意:http://127.0.0.1:8000/projects/?search=dj,必须用search,如果制定别的关键字需在settings中指定SEARCH_PARAM=‘s’
现在我们修改query string参数的默认key“search”
REST_FRAMEWORK = {
...
'SEARCH_PARAM': 's',
}
此时,前端需要传query string参数?s=xxx才能使用过滤功能ordering_fields = [‘id’, ‘-name’, ‘leader’]
a、指定过滤器
第一种方式:全局指定
REST_FRAMEWORK = {
...
'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.OrderingFilter'],
}
第二种方式:在视图中指定
from rest_framework.filters import OrderingFilter
class ProjectViews(GenericAPIView):
...
queryset = Projects.objects.all()
serializer_class = serializers.ProjectModelSerializer
filter_backends = [OrderingFilter]
# 指定需要过滤的字段
ordering_fields = ['id', 'name', 'leader']
# 指定默认排序字段
ordering = ['name']
...
前端传query string参数:http://127.0.0.1:8000/projects/?ordering=-name,id,就可以使用过滤功能了(默认生序,若降序在字段前加-号,如果指定多个排序字段,使用英文逗号进行分割)。
b、修改参数
修改query string参数的默认key“ordering”
REST_FRAMEWORK = {
...
'ORDERING_PARAM': 'o',
}
此时,前端需要传query string参数?o=xxx才能使用过滤功能。
a、直接引用PageNumberPagination
引用DRF框架自带的类rest_framework.pagination.PageNumberPagination
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
}
b、自定义PageNumberPagination类
自定义PageNumberPagination类,然后在settings.py的配置中修改
# settings.py
REST_FRAMEWORK = {
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'DEFAULT_PAGINATION_CLASS': 'utils.pagination.PageNumberPagination',
}
# utils/pagination.py
from rest_framework.pagination import PageNumberPagination as _PageNumberPagination
# 导入DRF中的PageNumberPagination类时重命名为_PageNumberPagination
class PageNumberPagination(_PageNumberPagination):
# 指定默认每一页显示3条数据
page_size = 3
# 前端用于指定页码的查询字符串参数名称
page_query_param = 'pp'
# 前端用于指定页码的查询字符串参数描述
page_query_description = '获取的页码'
# 前端用于指定每一页显示的数据条数,查询字符串参数名称
page_size_query_param = 'ss'
page_size_query_description = '每一页数据条数'
# 当接口uri中传入的size大于当前设置的max_page_size,则按max_page_size中的数据返回数据个数
max_page_size = 50
invalid_page_message = '无效页码'
# 重写父类的方法给返回的数据增加页号字段
def get_paginated_response(self, data):
# 调用父类的实现
response = super().get_paginated_response(data)
# 增加页号字段
response.data['current_num'] = self.page.number
response.data['max_num'] = self.page.paginator.num_pages
# 返回新增后的字典
return response
c、应用
views.py,修改视图类添加分页功能
class ProjectViews(GenericAPIView):
queryset = Projects.objects.all()
serializer_class = serializers.ProjectModelSerializer
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['^name', '=leader']
def get(self, request: Request):
queryset = self.filter_queryset(self.get_queryset())
# 使用paginate_queryset方法,进行分页操作;需要接收查询集参数;如果返回的数据为空,说明不进行分页操作,否则需要进行分页操作
page = self.paginate_queryset(queryset)
if page is not None:
# 调用get_serializer,将page作为参数传给instance
serializer = self.get_serializer(instance=page, many=True)
# 分页必须调用get_paginated_response方法返回
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(instance=queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
GenericAPIView主要的属性:
GenericAPIView主要的方法
class ProjectsView(GenericAPIView):
"""
继承GenericAPIView父类(GenericAPIView子类)
a.具备View的所有特性
b.具备了APIView中的认证、授权、限流功能
c.还支持对于获取列表数据接口的功能:搜索、排序、分页
"""
# 一旦继承GenericAPIView之后,往往需要指定queryset、serializer_class类属性
# queryset指定当前类视图的实例方法需要使用的查询集对象
queryset = Projects.objects.all()
# serializer_class指定当前类视图的实例方法需要使用的序列化器类
serializer_class = ProjectSerilizer
# 可以在类视图中指定分页引擎类,优先级高于全局
pagination_class = PageNumberPagination
# filter_backends在继承了GenericAPIView的类视图中指定使用的过滤引擎类(搜索、排序)
# 优先级高于全局
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
# 2、在继承了GenericAPIView的类视图中,search_fields类属性指定模型类中需要进行搜索过滤的字段名
# 3、使用icontains查询类型作为过滤类型
# 4、可以在字段名称前添加相应符号,指定查询类型
# '^': 'istartswith',
# '=': 'iexact',
# '$': 'iregex',
search_fields = ['^name', '=leader', 'id']
# ordering_fields类属性指定模型类中允许前端进行排序的字段名称
# 前端默认可以使用ordering作为排序功能查询字符串参数名称,默认改字段的升序
# 如果在字段名称前添加“-”,代表改字段降序
# 如果指定多个排序字段,使用英文逗号进行分割
ordering_fields = ['id', 'name', 'leader']
def get(self, request: Request):
# name_param = request.query_params.get('name')
# if name_param:
# queryset = Projects.objects.filter(name__exact=name_param)
# else:
# queryset = Projects.objects.all()
# queryset = Projects.objects.all()
# 1、在实例方法中,往往使用get_queryset()方法获取查询集对象
# 2、一般不会直接调用queryset类属性,原因:为了提供让用户重写get_queryset()
# 3、如果未重写get_queryset()方法,那么必须得指定queryset类属性
# 4、调用self.get_queryset()方法时,类属性必须要有queryset类属性
# queryset = self.queryset
# queryset = self.get_queryset()
# filter_queryset对查询对象进行过滤操作
queryset = self.filter_queryset(self.get_queryset())
# serializer = ProjectSerilizer(instance=queryset, many=True)
# 调用paginate_queryset方法对查询集对象进行分页
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(instance=page, many=True)
return self.get_paginated_response(serializer.data)
# 1、在实例方法中,往往使用get_serializer()方法获取序列化器类
# 2、一般不会直接调用serializer_class类属性,原因:为了让用户重写get_serializer_class()
# 3、如果未重写get_serializer_class()方法,那么必须得指定serializer_class类属性
# 4、调用self.get_serializer()方法时,类属性必须要有serializer_class类属性
# serializer = self.serializer_class(instance=queryset, many=True)
serializer = self.get_serializer(instance=queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request):
# serializer = ProjectModelSerializer(data=request.data)
# serializer = self.serializer_class(data=request.data)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
Mixin拓展类,拓展类只提供了action方法(mixins库下的各个类);
action方法:
list --> 获取列表数据(ListModelMixin类的list方法)
retrieve --> 获取详情数据(RetrieveModelMixin类的retrieve方法)
create --> 创建数据(CreateModelMixin类的create方法)
update --> 更新数据(完整)(UpdateModelMIxin类的update方法)
partial_update --> 更新数据(部分)(UpdateModelMIxin类的update方法)
destroy --> 删除数据(DestroyModelMixin类的destroy方法)
列表视图扩展类,提供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)
实际使用:
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin
from book.models import BookInfo
from book.serializers import BookInfoModelSerializer
class BookListGenericMixinView(ListModelMixin,GenericAPIView):
# 设置查询结果集属性
queryset = BookInfo.objects.all()
# 设置序列化器属性
serializer_class = BookInfoModelSerializer
def get(self,request):
#调用Mixin抽取好的list方法
return self.list(request)
# ------------分割线-------------------------------------------
# 上述BookListGenericMixinView引用的父类可以合并使用ListAPIView替换
from rest_framework.generics import ListAPIView
class BookListGenericMixinView(ListAPIView):
queryset = BookInfo.objects.all()
serializer_class = BookInfoModelSerializer
# ------------分割线-------------------------------------------
# 我们来看下ListAPIView的源码,就知道为啥可以替换了
# ListAPIView源码如下:
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
创建视图扩展类,提供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 {}
具体使用:
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin
from book.models import BookInfo
from book.serializers import BookInfoModelSerializer
class BookListGenericMixinView(CreateModelMixin,GenericAPIView):
# 设置查询结果集属性
queryset = BookInfo.objects.all()
# 设置序列化器属性
serializer_class = BookInfoModelSerializer
def post(self,request):
# 调用Mixin抽取好create的方法
return self.create(request)
详情视图扩展类,提供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)
具体使用:
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import RetrieveModelMixin,UpdateModelMixin,DestroyModelMixin
from book.models import BookInfo
from book.serializers import BookInfoModelSerializer
class BookDetailGenericMixinView(RetrieveModelMixin, GenericAPIView):
# 设置查询结果集属性
queryset = BookInfo.objects.all()
# 设置序列化器属性
serializer_class = BookInfoModelSerializer
def get(self, request,pk):
# 调用Mixin抽取好的retrieve方法
return self.retrieve(request)
更新视图扩展类,提供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.generics import GenericAPIView
from rest_framework.mixins import UpdateModelMixin
from book.models import BookInfo
from book.serializers import BookInfoModelSerializer
class BookDetailGenericMixinView(UpdateModelMixin, GenericAPIView):
# 设置查询结果集属性
queryset = BookInfo.objects.all()
# 设置序列化器属性
serializer_class = BookInfoModelSerializer
def put(self, request,pk):
# 调用Mixin抽取好update的方法
return self.update(request)
删除视图扩展类,提供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()
具体使用:
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import DestroyModelMixin
from book.models import BookInfo
from book.serializers import BookInfoModelSerializer
class BookDetailGenericMixinView(DestroyModelMixin, GenericAPIView):
# 设置查询结果集属性
queryset = BookInfo.objects.all()
# 设置序列化器属性
serializer_class = BookInfoModelSerializer
def delete(self,request,pk):
调用Mixin抽取好destory的方法
return self.destroy(request)
类视图将Mixin拓展类和GenericAPIView结合,比如generics.ListAPIView直接继承Mixin拓展类((只提供了action方法))和GenericAPIView类,而generics.ListAPIView中将方法与action连接起来,如下:
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
类视图识别action方法对应情况如下(generics库下的各个类):
get --> list (ListAPIView类下的get方法)
get --> retrieve (RetrivevAPIView类下的get方法)
post --> create (CreateAPIView下的post方法)
put --> update (UpdateAPIView类下的put方法)
patch --> partial_update (UpdateAPIView类下的patch方法)
delete --> destroy (DestroyAPIView类下的delete方法)
除此之外还有ListCreateAPIView、RetrivevUpdateAPIView、RetrivevDestroyAPIView、RetrivevUpdateDestroyAPIView方法,具体可以查看源码
ListAPIView ====〉mixins.ListModelMixin, GenericAPIView
CreateAPIView ==== 〉mixins.CreateModelMixin, GenericAPIView
RetrieveAPIView==== 〉mixins.RetrieveModelMixin, GenericAPIView
DestroyAPIView ====〉mixins.DestroyModelMixin, GenericAPIView
UpdateAPIView ====〉mixins.UpdateModelMixin, GenericAPIView
ListCreateAPIView ==== 〉mixins.ListModelMixin,mixins.CreateModelMixin, GenericAPIView
RetrieveUpdateAPIView ====〉mixins.RetrieveModelMixin, mixins.UpdateModelMixin, GenericAPIView
RetrieveDestroyAPIView ====〉mixins.RetrieveModelMixin, mixins.DestroyModelMixin, GenericAPIView
RetrieveUpdateDestroyAPIView ====〉mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView
# class ProjectsDetailView(
# mixins.RetrieveModelMixin,
# mixins.UpdateModelMixin,
# mixins.DestroyModelMixin,
# GenericAPIView):
# 为了进一步优化代码,需要使用具体的通用视图XXXAPIView
class ProjectsDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Projects.objects.all()
serializer_class = ProjectSerilizer
genaric.py源码
class CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for creating a model instance.
"""
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
class RetrieveAPIView(mixins.RetrieveModelMixin,
GenericAPIView):
"""
Concrete view for retrieving a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
class DestroyAPIView(mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for deleting a model instance.
"""
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class UpdateAPIView(mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for updating a model instance.
"""
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
视图集有4个类(ViewSet/GenericViewSet/ReadOnlyModelViewSet/ModelViewSet)具体参考源码。
GenericViewSet源码如下:
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass
class ViewSetMixin:
"""
This is the magic.
Overrides `.as_view()` so that it takes an `actions` keyword that performs
the binding of HTTP methods to actions on the Resource.
For example, to create a concrete view binding the 'GET' and 'POST' methods
to the 'list' and 'create' actions...
view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
"""
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
# The name and description initkwargs may be explicitly overridden for
# certain route configurations. eg, names of extra actions.
cls.name = None
cls.description = None
# The suffix initkwarg is reserved for displaying the viewset type.
# This initkwarg should have no effect if the name is provided.
# eg. 'List' or 'Instance'.
cls.suffix = None
# The detail initkwarg is reserved for introspecting the viewset type.
cls.detail = None
# Setting a basename allows a view to reverse its action urls. This
# value is provided by the router through the initkwargs.
cls.basename = None
# actions must not be empty
if not actions:
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
# sanitize keyword arguments
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r" % (
cls.__name__, key))
# name and suffix are mutually exclusive
if 'name' in initkwargs and 'suffix' in initkwargs:
raise TypeError("%s() received both `name` and `suffix`, which are "
"mutually exclusive arguments." % (cls.__name__))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if 'get' in actions and 'head' not in actions:
actions['head'] = actions['get']
# We also store the mapping of request methods to actions,
# so that we can later set the action attribute.
# eg. `self.action = 'list'` on an incoming GET request.
self.action_map = actions
# Bind methods to actions
# This is the bit that's different to a standard view
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
self.request = request
self.args = args
self.kwargs = kwargs
# And continue as usual
return self.dispatch(request, *args, **kwargs)
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
# We need to set these on the view function, so that breadcrumb
# generation can pick out these bits of information from a
# resolved URL.
view.cls = cls
view.initkwargs = initkwargs
view.actions = actions
return csrf_exempt(view)
def initialize_request(self, request, *args, **kwargs):
"""
Set the `.action` attribute on the view, depending on the request method.
"""
request = super().initialize_request(request, *args, **kwargs)
method = request.method.lower()
if method == 'options':
# This is a special case as we always provide handling for the
# options method in the base `View` class.
# Unlike the other explicitly defined actions, 'metadata' is implicit.
self.action = 'metadata'
else:
self.action = self.action_map.get(method)
return request
def reverse_action(self, url_name, *args, **kwargs):
"""
Reverse the action for the given `url_name`.
"""
url_name = '%s-%s' % (self.basename, url_name)
namespace = None
if self.request and self.request.resolver_match:
namespace = self.request.resolver_match.namespace
if namespace:
url_name = namespace + ':' + url_name
kwargs.setdefault('request', self.request)
return reverse(url_name, *args, **kwargs)
@classmethod
def get_extra_actions(cls):
"""
Get the methods that are marked as an extra ViewSet `@action`.
"""
return [_check_attr_name(method, name)
for name, method
in getmembers(cls, _is_extra_action)]
def get_extra_action_url_map(self):
"""
Build a map of {names: urls} for the extra actions.
This method will noop if `detail` was not provided as a view initkwarg.
"""
action_urls = OrderedDict()
# exit early if `detail` has not been provided
if self.detail is None:
return action_urls
# filter for the relevant extra actions
actions = [
action for action in self.get_extra_actions()
if action.detail == self.detail
]
for action in actions:
try:
url_name = '%s-%s' % (self.basename, action.url_name)
url = reverse(url_name, self.args, self.kwargs, request=self.request)
view = self.__class__(**action.kwargs)
action_urls[view.get_view_name()] = url
except NoReverseMatch:
pass # URL requires additional arguments, ignore
return action_urls
a.可以继承视图集父类ViewSet
b.在定义url路由条目时,支持给as_view传递字典参数(请求方法名与具体调用的action方法名的一一对应关系)(如{‘get’:‘list’})
c.ViewSet继承了ViewSetMixin, views.APIView
d.具备APIView的所有功能,提供了身份认证、权限校验、流量管理等
e.继承ViewSetMixin,所有具备持给as_view传递字典参数(请求方法名与具体调用的action方法名的一一对应关系)
ViewSet视图集类不再实现get()、post()等方法,而是实现动作 action 如 list() 、create() 等:
视图集只在使用as_view()方法的时候,才会将action动作与具体请求方式对应上。如:
from rest_framework.response import Response
from demo.models import ClassInfo
from demo.serializers import ClassInfoSerializer
from rest_framework import viewsets, status
class ClassViewSet(viewsets.ViewSet):
# 获取所有班级数据
def list(self, request):
Classes = ClassInfo.objects.all()
serializer = ClassInfoSerializer(Classes, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
Class = ClassInfo.objects.get(id=pk)
except ClassInfo.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = ClassInfoSerializer(Class)
return Response(serializer.data)
from django.urls import path
from demo import views
urlpatterns = [
······
path('classes/', views.ClassViewSet.as_view({'get': 'list'})),
path('classes//' , views.ClassViewSet.as_view({'get': 'retrieve'})),
]
继承自APIView与ViewSetMixin,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。
ViewSet主要通过继承ViewSetMixin来实现在调用as_view()时传入字典(如{‘get’:‘list’})的映射处理工作。
在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法
使用ViewSet通常并不方便,因为list、retrieve、create、update、destory等方法都需要自己编写,而这些方法与前面讲过的Mixin扩展类提供的方法同名,所以我们可以通过继承Mixin扩展类来复用这些方法而无需自己编写。但是Mixin扩展类依赖与GenericAPIView,所以还需要继承GenericAPIView;
GenericViewSet就帮助我们完成了这样的继承工作,继承自GenericAPIView与ViewSetMixin,在实现了调用as_view()时传入字典(如{‘get’:‘list’})的映射处理工作的同时,还提供了GenericAPIView提供的基础方法,可以直接搭配Mixin扩展类使用
举例:
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
class ClassInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
queryset = ClassInfo.objects.all()
serializer_class = ClassInfoSerializer
urlpatterns = [
······
path('classes_set/', views.ClassInfoViewSet.as_view({'get': 'list'})),
path('classes_set//' , views.ClassInfoViewSet.as_view({'get': 'retrieve'})),
]
# a.ModelViewSet是一个最完整的视图集类
# b.提供了获取列表数据接口、获取详情数据接口、创建数据接口、更新数据接口、删除数据的接口
# c.如果需要多某个模型进行增删改查操作,才会选择ModelViewSet
# d.如果仅仅只对某个模型进行数据读取操作(取列表数据接口、获取详情数据接口),一般会选择ReadOnlyModelViewSet
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Projects.objects.all()
serializer_class = ProjectModelSerializer
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['=name', '=leader', '=id']
ordering_fields = ['id', 'name', 'leader']
@action(methods=['GET'], detail=False)
def names(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@action(detail=True)
def interfaces(self, request, *args, **kwargs):
project = self.get_object()
interfaces_qs = project.interfaces_set.all()
interfaces_data = [{'id': interface.id, 'name': interface.name} for interface in interfaces_qs]
return Response(interfaces_data, status=200)
源码:
# 源码
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `list()` and `retrieve()` actions.
"""
pass
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
class ProjectViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
"""
a.可以重写父类的get_serializer_class方法,用于为不同的action提供不一样的序列化器类
b.在视图集对象中可以使用action属性获取当前访问的action方法名称
:return:
"""
if self.action == 'names':
return ProjectsNamesModelSerailizer
else:
# return self.serializer_class
return super().get_serializer_class()
def retrieve(self, request, *args, **kwargs):
response = super().retrieve(request, *args, **kwargs)
response.data.pop('id')
response.data.pop('create_time')
return response
# def filter_queryset(self, queryset):
# if self.action == "names":
# return self.queryset
# else:
# return super().filter_queryset(queryset)
def paginate_queryset(self, queryset):
if self.action == "names":
return
else:
return super().paginate_queryset(queryset)
def get_queryset(self):
if self.action == "names":
return self.queryset.filter(name__icontains='2')
else:
return super().get_queryset()
如何定义类视图?类视图的设计原则?
在视图集中,除了上述默认的方法动作外,还可以添加自定义动作。
添加自定义动作需要使用rest_framework.decorators.action装饰器。
以action装饰器装饰的方法名会作为action动作名,与list、retrieve等同。
action装饰器可以接收两个参数:
methods: 该action支持的请求方式,列表传递,加上后可以自动获取路由,不写就默认为get
detail: 表示是action中要处理的是否是视图资源的对象(即是否通过url路径获取主键)
(1)True 表示使用通过URL获取的主键对应的数据对象
(2)False 表示不使用URL获取主键
@action(methods=['GET'], detail=False)
def names(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@action(detail=True)
def interfaces(self, request, *args, **kwargs):
project = self.get_object()
interfaces_qs = project.interfaces_set.all()
interfaces_data = [{'id': interface.id, 'name': interface.name} for interface in interfaces_qs]
return Response(interfaces_data, status=200)
使用routers自动生成路由条目:
# -*- coding: utf-8 -*-
from django.urls import path, include
from rest_framework import routers
from . import views
router = routers.SimpleRouter()
# DefaultRouter与SimpleRouter功能类似,仅有的区别为:DefaultRouter会自动生成一个根路由(显示获取数据的入口)
# router = routers.DefaultRouter()
router.register(r'projects', views.ProjectViewSet)
urlpatterns = [
# 7、加载路由条目
# 方式一:
# 路由器对象.urls属性可获取生成的路由条目
path('', include(router.urls)),
]
# 方式二:
# router.urls为列表
# urlpatterns += router.urls
注意配置1:在配置文件settings.py中重新指定schema_class的配置
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
# 新版drf schema_class默认用的是rest_framework.schemas.openapi.AutoSchema
}
注意配置2:在setting文件的INSTALLED_APPS加入rest_framework,具体如下:
INSTALLED_APPS = [
‘rest_framework’,]
然后urls中制定访问路由
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='test接口文档', description='哈哈哈哈哈哈,这是哥的第一个接口文档'))
]
INSTALLED_APPS = [‘drf_yasg’,]
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
# 固定写法
schema_view = get_schema_view(
openapi.Info(
title="Lemon API接口文档平台", # 必传
default_version='v1', # 必传
description="这是一个美轮美奂的接口文档",
terms_of_service="http://api.keyou.site",
contact=openapi.Contact(email="[email protected]"),
license=openapi.License(name="BSD License"),
),
public=True,
)
urlpatterns = [
re_path(r'^swagger(?P\.json|\.yaml)$' , schema_view.without_ui(cache_timeout=0), name='schema-json'),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
可以通过在类视图下注解描述,即可在接口文档中显示
class ProjectViewSet(viewsets.ModelViewSet):
"""
list:
获取项目列表数据
retrieve:
获取项目详情数据
update:
更新项目信息
names:
获取项目名称
"""
需要在settings.py中定义logging和loggers,
然后log = logging.getLogger(‘wl’)获取logger
# 指定允许使用哪些地址访问当前系统
ALLOWED_HOSTS = ['*']
LOGGING = {
# 指定日志版本
'version': 1,
# 指定是否禁用其他日志,False为不禁用
'disable_existing_loggers': False,
# 定义日志输出格式
'formatters': {
# 简单格式
'simple': {
'format': '%(asctime)s - [%(levelname)s] - [msg]%(message)s'
},
# 复杂格式
'verbose': {
'format': '%(asctime)s - [%(levelname)s] - %(name)s - [msg]%(message)s - [%(filename)s:%(lineno)d ]'
},
},
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
# 指定日志输出渠道(日志输出的地方)
'handlers': {
# 指定在console控制台(终端)的日志配置行李箱
'console': {
# 指定日志记录等级
'level': 'DEBUG',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
# 指定在日志文件的配置信息
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR, "logs/mytest.log"), # 日志文件的位置
'maxBytes': 100 * 1024 * 1024,
'backupCount': 10,
'formatter': 'verbose',
'encoding': 'utf-8'
},
},
# 定义日志器
'loggers': {
# 指定日志器的名称
'dev07': { # 定义了一个名为mytest的日志器
'handlers': ['console', 'file'],
'propagate': True,
'level': 'DEBUG', # 日志器接收的最低日志级别
},
}
}
实际使用:
# log使用
log = logging.getLogger('wl')
log.info("qqqqq")
settings.py中的rest_framework内指定DEFAULT_AUTHENTICATION_CLASSES和DEFAULT_PERMISSION_CLASSES
# 指定使用的认证类
# a.在全局指定默认的认证类(指定认证方式)
'DEFAULT_AUTHENTICATION_CLASSES': [
# b.Session会话认证
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
],
# 指定使用的权限类
# a.在全局指定默认的权限类(当认证通过之后,可以获取何种权限)
'DEFAULT_PERMISSION_CLASSES': [
# AllowAny不管是否有认证成功,都能获取所有权限
# IsAdminUser管理员(管理员需要登录)具备所有权限
# IsAuthenticated只要登录,就具备所有权限
# IsAuthenticatedOrReadOnly,如果登录了就具备所有权限,不登录只具备读取数据的权限
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
# 在全局路由表中添加rest_framework.urls子路由
# a.rest_framework.urls提供了登录和登出功能(返回的是一个HTML页面,并不是接口)
path('api/', include('rest_framework.urls'))
安装
pip install djangorestframework-jwt
settings.py中增加配置 DEFAULT_AUTHENTICATION_CLASSES和JWT_AUTH
REST_FRAMEWORK = {
# 指定使用的认证类
# a.在全局指定默认的认证类(指定认证方式)
'DEFAULT_AUTHENTICATION_CLASSES': [
# 1)指定使用JWT TOKEN认证类
# 2)在全局路由表中添加obtain_jwt_token路由(可以使用用户名和密码进行认证)
# 3)认证通过之后,在响应体中会返回token值
# 4)将token值设置请求头参数,key为Authorization,value为JWT token值
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
# b.Session会话认证
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
],
}
JWT_AUTH={
# 修改JWT TOKEN认证请求头中Authorization value值的前缀,默认为JWT
# 'JWT_AUTH_HEADER_PREFIX': 'bearer',
# 指定TOKEN过期时间,默认为5分钟,可以使用JWT_EXPIRATION_DELTA指定
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
# 修改处理payload的函数
'JWT_RESPONSE_PAYLOAD_HANDLER':
'utils.handle_jwt_response.jwt_response_payload_handler',
}
注意DEFAULT_AUTHENTICATION_CLASSES加在settings.py中默认全局生效,
但是实际使用时建议放在app的views.py中如下
class ProjectViewSet(viewsets.ModelViewSet):
...
# 在继承了APIView的类视图中,可以使用permission_classes类属性指定权限类,值为列表,可添加多个权限类
permission_classes = [permissions.IsAuthenticated]
...
# urls.py配置
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('user/login/', obtain_jwt_token),
]
安装corsheaders
pip install django-cors-headers
使用
INSTALLED_APPS = [ #注册应用
'corsheaders', #增加这个
]
MIDDLEWARE = [ #中间件
'corsheaders.middleware.CorsMiddleware', #增加这个,放最前面
'django.middleware.security.SecurityMiddleware',
]
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = () #白名单ip,可以不填
# CORS_ORIGIN_WHITELIST指定能够访问后端接口的ip或域名列表
# CORS_ORIGIN_WHITELIST = [
# "http://127.0.0.1:8080",
# "http://localhost:8080",
# "http://192.168.1.63:8080",
# "http://127.0.0.1:9000",
# "http://localhost:9000",
# ]
CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
# 前端需要携带cookies访问后端时,需要设置
# withCredentials=True
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
'VIEW',
)
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
)