drf过滤和排序及异常处理和Response的包装
过滤和排序(4星)
查询所有才需要过滤(根据过滤条件),排序(按某个规律排序) 使用前提: 必须继承的顶层类是GenericAPIView, 所有指定的过滤字段都是和queryset对象里有的字段有关(即和对象所在的model表中所有的字段有关),和写序列化类里的字段无关
模糊查询
内置过滤类
使用方法
设置
from rest_framework.filters import SearchFilter
class BookAPIView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookModelSerializer
# 在视图类中配置 最顶层的类至少是GenericAPIView
filter_backends = [SearchFilter, ] # 设置这个
# 过滤条件,写多个就是过滤出多个字段中所有符合条件的数据
search_fields = ['title', 'price'] # 取决于models里的字段 模糊查询
查询
http://127.0.0.1:8000/books/?search=22 # 书名中或者价格中有22就能查询出来
你还可以在查找API中使用双下划线符号对ForeignKey或ManyToManyField执行相关查找:
# profile是外键 双下划线查询profession字段
search_fields = ('username', 'email', 'profile__profession')
默认情况下,搜索将使用不区分大小写的部分匹配。 搜索参数可以包含多个搜索项,其应该是空格和/或逗号分隔。 如果使用多个搜索术语,则仅当所有提供的术语都匹配时才在列表中返回对象。
可以通过在search_fields
前面添加各种字符来限制搜索行为。
- '^' 以指定内容开始.
- '=' 完全匹配
- '@' 全文搜索(目前只支持Django的MySQL后端)
- '$' 正则搜索
例如:
search_fields = ('=username', '=email')
默认情况下,搜索参数名为'search'
,但这可以通过使用SEARCH_PARAM
设置覆盖。
第三方插件过滤(可以有and关系)
下载模块pip3 install django-filter 并且这个是精准查询, 可以指定表模型中的字段来精准查询, 多个字段连用是and关系 使用前提: 必须继承的顶层类是GenericAPIView
使用方法
注册
INSTALLED_APPS = [
...
'django_filters',
]
设置
from django_filters.rest_framework import DjangoFilterBackend
class BookAPIView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookModelSerializer
filter_backends = [DjangoFilterBackend, ] # 写上面导入的类
# 过滤条件,按名字过滤
filter_fields =['name', 'price']
查询
http://127.0.0.1:8000/books/?name=红 # 返回空列表
http://127.0.0.1:8000/books/?name=红楼梦
http://127.0.0.1:8000/books/?name=红楼梦&price=33 # &符号是and关系
http://127.0.0.1:8000/books/?name=红楼梦&price=32 # 返回空列表
自定义过滤类
使用方法
写一个类,继承BaseFilterBackend 基类,重写filter_queryset方法,然后返回的queryset对象,就是过滤后的对象 使用前提: 必须继承的顶层类是GenericAPIView
# 在自己创建的untils.py中
from rest_framework.filters import BaseFilterBackend
class BookFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
query = request.query_params.get('title')
if query:
queryset = queryset.filter(title__contains=query) # 根据过滤条件筛选数据
return queryset
# 在views.py中
from .untils import BookFilter
class BookAPIView(GenericViewSet, ListModelMixin):
queryset = Book.objects.all()
serializer_class = BookModelSerializer
filter_backends = [BookFilter, ] # 设置自定义过滤类 自己定义的过滤类就不需要写类属性了
查询
http://127.0.0.1:8000/books/?title=红 # 模糊匹配 ,自己定义的
自定义模糊查询且有and关系
class MyFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
# 因为我们不知道筛选条件是什么 所以要用Q方法来把用户输入的字段变成过滤条件
q = Q()
# 从get请求中获取用户输入的筛选字段及条件
for field, query in request.query_params.items():
# 如果该字段不在model表中则报错
if not hasattr(Book, field):
raise APIException('没有%s字段' % field)
# 有则字段加模糊查询
field += '__contains'
q.children.append((field, query))
queryset = Book.objects.filter(q)
return queryset
源码分析
#### 源码分析----》GenericAPIView----》查询所有,调用了list---》self.filter_queryset(self.get_queryset())----》查看GenericAPIView的filter_queryset方法:
def filter_queryset(self, queryset):
for backend in list(self.filter_backends): # 去视图类里取自定义过滤类名
# 自定义过滤类名实例化得到对象调用filter_queryset方法 而这个就是我们自己写的方法,返回值就是过滤后的数据
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
内置排序类(既有排序,又有过滤)
使用前提: 必须继承的顶层类是GenericAPIView
使用方法
设置
from rest_framework.filters import OrderingFilter,SearchFilter
class BookAPIView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookModelSerializer
filter_backends = [SearchFilter, OrderingFilter]
# 过滤条件,按名字过滤
search_fields = ['title', 'price']
# 按哪个字段排序
ordering_fields = ['price', 'id']
如果在view中设置了ordering属性,则将把它用作默认排序。
通常,你可以通过在初始queryset上设置order_by来控制此操作,但是使用view中的ordering参数允许你以某种方式指定排序,然后可以将其作为上下文自动传递到呈现的模板。如果它们用于排序结果的话就能使自动渲染不同的列标题成为可能。
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = (filters.OrderingFilter,)
ordering_fields = ('username', 'email')
ordering = ('username',)
ordering属性可以是字符串或字符串的列表/元组。
查询
http://127.0.0.1:8000/books/?ordering=price,-id
# 上面这句话的意思是按价格升序,如果遇到相同的,就按id降序
过滤加排序
过滤和排序可以同时用--->因为他们本质是for循环一个个执行,所有优先使用过滤,再使用排序
filter_backends = [SearchFilter,OrderingFilter]
ordering_fields=['price','id']
search_fields=['name','author']
查询
http://127.0.0.1:8000/books/?search=22&ordering=-id
异常处理(4星)
全局统一捕获异常,返回固定的格式 {code:999,msg:'未知错误'}(类似这种格式)
使用方法
在一个新的py文件内:
from rest_framework.views import exception_handler # 默认没有配置,出了异常会走它
from rest_framework.response import Response
from utils.logging import logger # 把之前配的日志对象导过来
def common_exception_handler(exc, context):
# 取出出错的IP,可以不用
print(context['request'].META.get('REMOTE_ADDR'))
######################################重点##########################################
# exception_handler方法会返回Response对象或者None
res = exception_handler(exc, context)
if res: # 这表示已经处理了异常,它只处理APIExcepiton的异常
res = Response(data={'code': 998, 'msg': res.data.get('detail', '服务器异常')})
else: # 返回None,表示是出了不知道的异常
res = Response(data={'code': 999, 'msg': str(exc)})
# 注意:在这里,可以记录日志---》只要走到这,说明程序报错了,记录日志,以后查日志---》尽量详细
# 出错时间,错误原因,哪个视图类出了错,什么请求地址,什么请求方式出了错
request = context.get('request') # 这个request是当次请求的request对象
view = context.get('view') # 这个viewt是当次执行的视图类对象
# 记录错误日志
logger.error('错误原因:%s,错误视图类:%s,请求地址:%s,请求方式:%s' % (str(exc), str(view), request.path, request.method))
return res
配置文件配置:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.untils.common_exception_handler'
}
源码分析
二次封装Response
在utils/response.py文件内
from rest_framework.response import Response
class APIResponse(Response):
def __init__(self, code=100, msg='成功', status=None, headers=None, exception=False,
content_type=None, template_name=None, **kwargs):
res_data = {
'code': code,
'msg': msg,
}
# 如果传了其他的数据,组织成字典添加或更新到res_data字典内
if kwargs:
res_data.update(kwargs)
# 调用Response的__init__,把数据都传进去,其本质就是把返回数据用字典包装了一下
super().__init__(data=res_data, status=status, headers=headers, exception=exception,
content_type=content_type, template_name=template_name)
在视图类内
from rest_framework.views import APIView
from utils.response import APIResponse
class TestAPIView(APIView):
def get(self, request):
return APIResponse(res=[{'name': 'lqz'}, {'name': 'egon'}], token='sadasd')