文章目录
- 一、排序组件
- 继承GenericAPIView使用DRF内置排序组件
- 继承APIView编写排序
- 二、过滤组件
- 继承GenericAPIView使用DRF内置过滤器实现过滤
- 使用第三方模块django-filter实现and关系的过滤
- 自定制过滤类
- 排序搭配过滤使用
- 三、分页组件
- 分页器一:Pagination(基本分页)
- 分页器二:LimitOffsetPagination(偏移分页)
- 分页器三:CursorPagination(游标分页)
- 四、异常处理
一、排序组件
一般涉及到了
查询所有才有排序
,对于表模型查询所有
数据时需要根据某个字段进行排序时,我们就可以使用到REST framework
提供的内置排序组件OrderingFilter
来帮助我们快速指名数据按照指定字段进行排序
使用方法
- 导入DRF内置排序组件:
from rest_framework.filters import OrderingFilter
- 在视图类中设置类属性:
filter_backends=[OrderingFilter]
注意这里能使用这个类属性,只有是继承了GenericAPIView才行。否则无法使用
所以像继承APIView则无法使用内置过滤器 - REST framework会在请求的查询字符串参数中检查是否包含了
ordering
参数 - 如果包含了
ordering
参数,则按照ordering参数
指明排序字段对数据集进行排序 - 前端可以传递的ordering参数的可选字段值需要再
ordering_fields
中指明
继承GenericAPIView使用DRF内置排序组件
'需要有表数据,我这里直接沿用上一篇博客中的使用,然后使用自动生成路由的方式'
from rest_framework.generics import ListAPIView
from rest_framework.viewsets import ViewSetMixin
from rest_framework.filters import OrderingFilter
from .serializer import BookSerializer
from . import models
class BookView(ViewSetMixin,ListAPIView):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
'配置属性'
filter_backends = [OrderingFilter]
'必须指定表模型数据字段'
ordering_fields = ['price'] # 可以写多个排序字段
# ordering_fields = ['price','id']
'127.0.0.1:8000/api/v2/books/?ordering=-price 倒序'
'127.0.0.1:8000/api/v2/books/?ordering=price 升序'
继承APIView的是使用不了以上DRF提供的排序组件,需要自己写,自己从请求地址中取出排序规则,然后自己排序
继承APIView编写排序
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.viewsets import ViewSetMixin
from rest_framework.mixins import ListModelMixin
'这里我还是沿用上面自动生成路由的方式,所以需要配置ViewSetMixin'
class BookView(ViewSetMixin, APIView, ListModelMixin):
'由于是APIView需要自己重写list方法,在自动生成路由中是get:list'
def list(self, request, *args, **kwargs):
'从地址栏中取出过滤条件'
print(request.query_params) # ?ordering=-price,id
query_params = request.query_params # {ordering:price}
'''支持多个条件排序ordering=-price,id'''
# try:
'127.0.0.1:8000/api/v2/books/?ordering=-price,id'
if ',' in query_params.get('ordering'):
query = query_params.get('ordering').split(',')
book_list = models.Book.objects.all().order_by(*query)
else:
book_list = models.Book.objects.all().order_by(query_params.get('ordering'))
ser = BookSerializer(instance=book_list, many=True)
return Response(ser.data)
二、过滤组件
restful规范中,要求请求地址中带过滤条件,五个接口中,只有查询所有接口需要过滤和排序。
其实上面排序使用的就是DRF内置过滤器,因为排序本就是过滤的一种特殊情况,所以这里就不在过多介绍了
继承GenericAPIView使用DRF内置过滤器实现过滤
from rest_framework.filters import SearchFilter
from rest_framework.viewsets import ViewSetMixin
from rest_framework.generics import ListAPIView
from rest_framework.filters import OrderingFilter,SearchFilter
class BookView(ViewSetMixin, GenericAPIView, ListModelMixin):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
'SearchFilter,使用的是模糊匹配'
filter_backends = [SearchFilter]
'''只能填写表中字段'''
search_fields = ['name']
search_fields = ['name','price'] 可多字段过滤
# 127.0.0.1:8000/api/v2/books/?search=东
# 127.0.0.1:8000/api/v2/books/?search=46
'因为是多字段过滤,并且SearchFilter是模糊匹配,所以如果多字段中有一样的东西,如1都会被查到'
使用DRF内置过滤器,它搜索结果的关系为
or(或)的关系,不是and关系
。并且只能是使用关键字search
才能使用,如果我们想要用name=东&price=66
这种方式的得使用别的方法来实现
使用第三方模块django-filter实现and关系的过滤
'首先需要安装第三方模块:pip install django-filter'
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.viewsets import ViewSetMixin
from rest_framework.generics import ListAPIView
'使用这个第三方模块可以实现and关系,并且只能是精准匹配。并且暂不支持or关系'
class BookView(ViewSetMixin,ListAPIView):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'price']
# 127.0.0.1:8000/api/v2/books/?name=东游记&price=46 精准匹配
'但是对于django-filter来讲它是支持扩写的,所以是可以支持模糊匹配,具体操作自寻查找'
使用这个django-filter还是有局限性的,无法实现or关键以及模糊匹配,那么我们可以使用自定制过滤类
自定制过滤类
使用步骤:
1.定义一个过滤类,并且继承BaseFilterBackend
2.重写filter_queryset方法,并且在内部完成过滤规则
3.在视图类中配置自定义的过滤类
from rest_framework.filters import BaseFilterBackend
class CommonFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
'''queryset是之前所有数据,models.Book.object.all()'''
# request是当次请求
# query_params = request.query_params
'方式一:'
name = request.query_params.get('name',None)
price = request.query_params.get('price',None)
res = queryset.filter(name__contains=name,price=price)
'方式二:'
if request.query_params.get('name'):
queryset = queryset.filter(name__contains=request.query_params.get('name'))
if request.query_params.get('price'):
'支持链式调用'
queryset = queryset.filter(price=request.query_params.get('price'))
return queryset # 这里返回过滤后的对象 res
from .filters import CommonFilter
class BookView(ViewSetMixin,ListAPIView):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
'无需再配置字段了,因为在自定制类中已经写好了过滤规则,所以在视图类中无需配置'
filter_backends = [CommonFilter]
# 127.0.0.1:8000/api/v2/books/?name=游记&price=666
'这样就实现了模糊匹配name字段并且精准匹配price字段,以及查询单个字段的规则'
排序搭配过滤使用
from .filters import CommonFilter # 使用的是上面的自定制过滤类
from rest_framework.filters import OrderingFilter
class BookView(ViewSetMixin,ListAPIView):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
filter_backends = [OrderingFilter,CommonFilter] # 排序加过滤,从左到右依次执行
ordering_fields = ['price','id']
三、分页组件
分页只针对查询所有的接口,其他四个接口不需要分页。drf内置了三个分页器,对应三种分页方式,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用。一个接口只能有一种分页方式,不能混合分页方式
分页器一:Pagination(基本分页)
通过
自定义Pagination类
,来为视图添加不同分页行为。在视图中通过pagination_clas
属性来指明。
from rest_framework.pagination import PageNumberPagination
class CommonPageNumberPagination(PageNumberPagination):
page_size = 3 # 默认每页显示的条数
page_query_param = 'page' # 使用该关键字指定页码(客户端使用)
page_size_query_param = 'size' # 通过该关键字可以手动指定页面显示的数据条数(客户端使用)
max_page_size = 5 # 只针对客户端手动指定页面显示的数据条数做出一个限制,但不影响page_size属性。
视图类
'导入配置的分页类'
from .pagination import CommonPageNumberPagination
from rest_framework.viewsets import ViewSetMixin
from rest_framework.generics import ListAPIView
from . import models
from .serializer import BookSerializer
from .filters import CommonFilter
from rest_framework.filters import OrderingFilter
class BookView(ViewSetMixin,ListAPIView):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
'注意分页方式一个接口只能使用一种,所以没有加中括号'
pagination_class = CommonPageNumberPagination
'''使用分页并不影响排序和过滤'''
filter_backends = [OrderingFilter,CommonFilter]
filterset_fields = ['name', 'price']
# 127.0.0.1:8000/api/v2/books/?page=2
# 127.0.0.1:8000/api/v2/books/
# 127.0.0.1:8000/api/v2/books/?page=2&size=3
响应时额外携带了3个参数,分别是:
- count:book接口的数据总量
- next:下一页的URL
- previous:上一页的URL,如果没有上一页返回None
当然,我们也可以手动指定页面显示的条数,根据
page_size_query_param
属性值来作为关键字。
可以看到我们需要显示的是6条数据,而后端只响应了5条,这是因为max_page_size属性值的作用:限制了客户端手动获取数据时最大返回的数据条数。
分页器二:LimitOffsetPagination(偏移分页)
偏移分页,该分页组件作用是:从哪一条数据之后开始显示,以及制定数据显示的条数
前端访问形式:
http://127.0.0.1:8080/book/?offset=2&limit=4
,这表示从第3条数据之后显示4条数据
自定义分页类
from rest_framework.pagination import LimitOffsetPagination
class CommonLimitOffsetPagination(LimitOffsetPagination):
# 等同于上面的:page_size
default_limit = 2 # 如果前端没有手动指定获取的数据条数时,使用该属性值
limit_query_param = 'limit' # 前端通过该关键字指定数据条数
'每页显示条数,查询的条数 例子 ?limit=100 意思就是每页显示100条,如果不传应用default_limit的参数'
offset_query_param = 'offset' # 通过该关键字指定从哪条数据之后开始获取数据
'偏移量 举个例子offset=6&limit=30 意思就是从第6条开始,拿30条'
# 等同于上面的:max_page_size
max_limit = 5 # 只限制limit_query_param能够获取的最大数据条数,而不影响default_limit
视图类
'导入配置的分页类'
from .pagination import CommonPageNumberPagination
class BookView(ViewSetMixin,ListAPIView):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
'注意分页方式一个接口只能使用一种,所以没有加中括号'
pagination_class = CommonPageNumberPagination
# http://127.0.0.1:8000/api/v2/books/?limit=6&offset=2 从第2条开始拿6条
从第二条数据之后开始指定获取数据的条数,使用了
limit
指定了获取6条,但是被max_limit=5
给限制了,所以后端只能返回2条数据,如果不使用limit
指定获取的数据条数,那么默认使用default_limit=2
后端会返回2条数据。
分页器三:CursorPagination(游标分页)
自定义分页类
from rest_framework.pagination import CursorPagination
class CommonCursorPagination(CursorPagination):
cursor_query_param = 'cursor' # 按游标查询的查询条件,value值前端是不知道的,只能通过后台返回
page_size = 2 # 每页显示多少条啊
ordering = 'id' # 排序规则,必须是表中的字段
视图类
from .pagination import CommonCursorPagination
class BookView(ViewSetMixin,ListAPIView):
queryset = models.Book.objects.all()
serializer_class = BookSerializer
'注意分页方式一个接口只能使用一种,所以没有加中括号'
'另外使用游标分页的方式后就不能再其中使用排序了,因为其内部已经排好序了'
pagination_class = CommonCursorPagination
'并且只能选择上一页和下一页,不能指定跳转某一页,但是速度快,针对与特别大的数据量,游标分页有优势'
四、异常处理
REST framework提供了异常处理,我们可以自定义异常处理函数。主要是为了DRF在运行过程中发生的错误进行一个捕获,然后响应友好的提示信息给前端。
自定义异常处理函数:(定制一个异常处理统一的返回格式)
from rest_framework.views import exception_handler,Response
# drf的异常处理是exception_handler处理了,到那时没处理非drf的异常
# 自己写个函数,处理drf异常和自己的异常,以后只要是出了异常都会走这里
def Common_exception_handler(exc, context):
# exc:异常信息
# context:执行请求的视图、args、kwargs参数、request请求
res = exception_handler(exc, context)
# 在此处补充自定义的异常处理
if res: # 有值说明是drf的异常,如果没有值说明是自己的异常
# data = {'detail': exc.detail}
detail = res.data.get('detail') or "drf异常,请联系系统管理员"
return Response({'code':666,'message':detail})
else:
return Response({'code':777,'message':f"系统异常,请联系管理{str(exc)}"})
在配置文件中声明自定义的异常处理
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'appo1.exception.Common_exception_handler',
}
如果未声明,会采用默认的方式,如下:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}
视图类
'''全局异常处理'''
from rest_framework.exceptions import AuthenticationFailed,APIException
'''
Drf中无论在三大认证还是视图类中 方法执行报错包括主动抛异常
都会执行一个函数excption_exception处理异常的函数
只要出了异常APIView的dispatch中就可以捕获到
执行配置文件中的'EXCEPTION_HANDLER':'rest_framework.views.exception_handler'
'''
class UserView(ViewSetMixin, APIView):
def create(self, request):
raise Exception('你出错了')
# drf默认能处理自己的异常,它的异常都是继承自APIException的异常
# raise AuthenticationFailed('认证失败')
return Response({'code':100,'msg':'1111'})