过滤、排序、分页、异常处理

上期内容回顾

# 继承一个父类,父类中有方法,在子类中重写方法

# 鸭子类型:
	不需要显示继承一个类,只要多个类中有同样的属性或方法,我们把它们称之为一种类,python,go
    
# 非鸭子类类型语言:
	如果要属于同一类,必须显示的继承某个基类,这样才属于基类这个类型,java


# python语言建议使用鸭子类型(约定),但在实际开发中,我们经常不使用鸭子类型这种特性,出错概率低

# 实际编码:
	要么认为约定必须有哪些方法(符合鸭子类型),可控性低;
	要么强制约定有哪些方法(abc模块,使用抛异常)

# java中:
	重写:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
    
# java中:
	重载:是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同


# 认证
	写一个类,继承BaseAuthentication,重写authenticate,在方法中校验,如果登录了,返回两个值,如果没登陆,抛出异常
	全局使用
	局部使用
	局部禁用

# 权限
	写一个类,继承BasePermission,重写has_permission方法,在方法中判断,如果有权限,返回True,如果没有权限,返回false
	全局使用
	局部使用
	局部禁用

# 频率
	写一个类,继承SimpleRateThrottle
	重写get_cache_key,返回什么就以什么做频率限制
	重写类属性scope = 'minute_3',在配置文件中配置:'DEFAULT_THROTTLE_RATES': {'minute_3': '3/m'}
	全局使用
	局部使用
	局部禁用

今日内容概要

  • 过滤
  • 排序
  • 分页
  • 异常处理

内容详细

1、过滤

# 5个接口中,只有获取所有 需要过滤,其他都不需要

# 过滤有多种:
	内置的过滤类
	第三方过滤类
	自定义过滤类

# 1、内置的过滤类
	### 第一步:导入
from rest_framework.filters import SearchFilter

	### 第二步:在视图类中写
# 在视图类中
# 必须继承GenericAPIView,才有这个类属性
filter_backends = [SearchFilter, ]
# 需要配合一个类属性,可以按name过滤
search_fields = ['name', 'author']

	### 第三步:搜索的时候,模糊搜索
http://127.0.0.1:8000/books/?search=火
http://127.0.0.1:8000/books/?search=田  # 书名或者author中带田就能搜到
        
        
# 2、第三方过滤类
	###第一步:安装模块
pip3 install django-filter

	### 第二步:注册
INSTALLED_APPS = [
    'django_filters',
]

	### 第三步:导入过滤类
from django_filters.rest_framework import DjangoFilterBackend

	### 第四步:在视图类中使用
# django_filters和rest_framework都需要去配置文件中注册
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    # 必须继承GenericAPIView,才有这个类属性
    filter_backends = [DjangoFilterBackend, ]

    # 需要配合一个类属性,
    filter_fields = ['name', 'author']  # 书名或者author中同时过滤

	### 第五步:查询方式
http://127.0.0.1:8000/books/?name=火影忍者
http://127.0.0.1:8000/books/?name=海贼王&author=尾田  # and条件
http://127.0.0.1:8000/books/?author=尾田
        
        
# 3、自定义过滤类
	### 第一步:写一个类,继承BaseFilterBackend 基类,重写filter_queryset方法, 返回qs对象,是过滤后的对象
from rest_framework.filters import BaseFilterBackend

class BookNameFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        query = request.query_params.get('name')
        if query:
            queryset = queryset.filter(name__contains=query)
        return queryset
    
	### 第二步:在视图类中使用
from .filter import BookNameFilter

class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    # 必须继承GenericAPIView,才有这个类属性
    filter_backends = [BookNameFilter, ]
    # 自定义的过滤类完成了过滤,不需要写类属性了
    
	### 第三步:查询方式
http://127.0.0.1:8000/books/?name=忍  # 模糊查询 也可自己定义查询条件
        
"""
### 源码分析: GenericAPIView----》查询所有,调用了list---》self.filter_queryset(self.get_queryset())----》查看GenericAPIView的filter_queryset方法:

    def filter_queryset(self, queryset):
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset
"""

image

image

image

数据准备:models.py:

# 创建一个Book表 添加两条测试数据 迁移数据库

from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
    author = models.CharField(max_length=32)

新建:serializer.py:

from .models import Book
from rest_framework import serializers


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

新建 filter.py:

from rest_framework.filters import BaseFilterBackend


class BookNameFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        query = request.query_params.get('name')
        if query:
            queryset = queryset.filter(name__contains=query)
        return queryset

视图类 views.py:

from .models import Book
from .serializer import BookSerializer
from rest_framework.generics import ListAPIView
from rest_framework.viewsets import ViewSetMixin, GenericViewSet
from rest_framework.filters import SearchFilter
from rest_framework.mixins import ListModelMixin


# 过滤:
### 第一种:使用内置过滤类
# class BookView(ViewSetMixin, ListAPIView):
# class BookView(GenericViewSet, ListModelMixin):  # 同上 必须继承自动生成路由的类
#     queryset = Book.objects.all()
#     serializer_class = BookSerializer
#
#     # 必须继承GenericAPIView,才有这个类属性
#     filter_backends = [SearchFilter, ]
#
#     # 需要配合一个类属性,
#     search_fields = ['name', 'author']  # 书名或者author中同时过滤


### 第二种:使用第三过滤类
from django_filters.rest_framework import DjangoFilterBackend

# django_filters和rest_framework都需要去配置文件中注册
# class BookView(GenericViewSet, ListModelMixin):
#     queryset = Book.objects.all()
#     serializer_class = BookSerializer
#
#     # 必须继承GenericAPIView,才有这个类属性
#     filter_backends = [DjangoFilterBackend, ]
#
#     # 需要配合一个类属性,
#     filter_fields = ['name', 'author']  # 书名或者author中同时过滤



### 第三种:使用自定义过滤类
from .filter import BookNameFilter

class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    # 必须继承GenericAPIView,才有这个类属性
    filter_backends = [BookNameFilter, ]
    # 自定义的过滤类完成了过滤,不需要写类属性了

路由修改 urls.py:

from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from app01 import views

router = SimpleRouter()
router.register('books', views.BookView, 'books')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include(router.urls)),
]

image

2、排序

# 按照id排序,按照年龄排序,按照价格排序...
# 使用内置的即可
	### 第一步:导入内置排序类
from rest_framework.filters import OrderingFilter

	### 第二步:在视图类中配置(必须继承GenericAPIView)
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    filter_backends = [OrderingFilter, ]
    ordering_fields = ['price', 'id']  # 先按照price排序 如果price一样 那么按id继续排序
    
	### 第三步:查询
http://127.0.0.1:8000/books/?ordering=price  # 按price正序排    
http://127.0.0.1:8000/books/?ordering=-price  # 倒序排
http://127.0.0.1:8000/books/?ordering=-price,-id # 先按价格倒序排,如果价格一样,再按id倒序排
        
        
"""
### 过滤和排序可以同时用:
    因为他们本质是for循环一个个执行,所有优先使用过滤,再使用排序

    filter_backends = [SearchFilter, OrderingFilter]
    ordering_fields=['price', 'id']
    search_fields=['name', 'author']
"""

image

3、分页

# 5个接口中,只有查询所有,涉及到分页

# pc端是下一个页,下一页的形式,如果在app,小程序中展现形式是下拉加载下一个

# 默认提供了,三种分页方式:
	PageNumberPagination
	LimitOffsetPagination
	CursorPagination
    
    
    
# 1、PageNumberPagination---基本分页 按照页码数,每页显示多少条
	### 第一步:写一个类,继承PageNumberPagination,重写四个类属性
    
    page_size = 3  # 每页显示条数,默认
    page_query_param = 'page'  # 查询条件叫page --> ?page=3
    page_size_query_param = 'size'  # 每页显示的条数的查询条件: ?page=3&size=9 查询第三页,第三页显示9条
    max_page_size = 5  # 每页最大显示多少条:?page=3&size=9,最终还是显示5条
    
	### 第二步:配置在视图类上,必须继承GenericAPIView才有这个类属性
    pagination_class = PageNumberPagination
    
	# 第三步:查询方式
http://127.0.0.1:8000/books/?page=2  # 查询第二页 每页显示默认的三条
http://127.0.0.1:8000/books/?page=2&size=4  # 查询第二页 每页显示四条
    
    
    
# 2、LimitOffsetPagination---偏移分页
	### 第一步:写一个类,继承LimitOffsetPagination,重写四个类属性
    
    default_limit = 2  # 默认一页获取条数
    limit_query_param = 'limit'  # ?limit=3  获取三条,如果不传,就用上面的默认两条
    offset_query_param = 'offset'  # ?limit=3&offset=2  从第2条开始,获取3条    ?offset=3:从第三条开始,获取默认2条
    max_limit = 5  # 最大显示条数 5 条
    
	### 第二步:配置在视图类上,必须继承GenericAPIView才有这个类属性
    pagination_class = CommonLimitOffsetPagination
    
	### 第三步:查询方式
http://127.0.0.1:8000/books/?limit=2&offset=3  # 从第三条开始获取两条

    
    
# 3、CursorPagination---游标分页
	### 第一步:写一个类,继承CursorPagination,重写四个类属性

    page_size = 2  # 每页显示2条
    cursor_query_param = 'cursor'  # 查询条件  ?cursor=sdafdase
    ordering = 'id'  # 排序规则,使用id排序
    
	### 第二步:配置在视图类上,必须继承GenericAPIView才有这个类属性
    pagination_class = CommonCursorPagination
    
	### 第三步:查询方式
http://127.0.0.1:8000/books/?cursor=cD02

image

image

image

新建 page.py:

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination


class CommonPageNumberPagination(PageNumberPagination):
    # 重写四个类属性
    page_size = 3  # 每页显示条数,默认
    page_query_param = 'page'  # 查询条件叫page --> ?page=3
    page_size_query_param = 'size'  # 每页显示的条数的查询条件: ?page=3&size=9 查询第三页,第三页显示9条
    max_page_size = 5  # 每页最大显示多少条:?page=3&size=9,最终还是显示5条
    
    
class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 2  # 默认一页获取条数
    limit_query_param = 'limit'  # ?limit=3  获取三条,如果不传,就用上面的默认两条
    offset_query_param = 'offset'  # ?limit=3&offset=2  从第2条开始,获取3条    ?offset=3:从第三条开始,获取默认2条
    max_limit = 5  # 最大显示条数 5 条
    
    
class CommonCursorPagination(CursorPagination):
    page_size = 2  # 每页显示2条
    cursor_query_param = 'cursor'  # 查询条件  ?cursor=sdafdase
    ordering = 'id'  # 排序规则,使用id排序

视图类 views.py中:

"""
分页
"""

# 分页种类一:PageNumberPagination  基本分页  用的最多
from .page import CommonPageNumberPagination as PageNumberPagination
from .page import CommonLimitOffsetPagination, CommonCursorPagination

# 分页一 二:
# class BookView(GenericViewSet, ListModelMixin):
#     queryset = Book.objects.all()
#     serializer_class = BookSerializer
#
#     filter_backends = [OrderingFilter, SearchFilter]
#     ordering_fields = ['price', 'id']  # 排序
#     search_fields = ['name', 'author']  # 过滤

    # 不要放在列表中了,分页只能按一种方式,不能按多种方式
    # pagination_class = PageNumberPagination

    # pagination_class = CommonLimitOffsetPagination


# 分页三:
"""
跟上面两种的区别:上面两种,可以从中间位置获取某一页,Cursor方式只能上一页和下一页

上面这两种在获取某一页的时候,都需要从开始过滤到要取的页面数的数据

下面这种方式,先排序,内部维护了一个游标,游标只能选择往前走或往后走,在取某一页的时候,不需要过滤之前的数据
这种分页方式特殊,只能选择上一页和下一页,不能指定某一页,但是速度快,适合大数据量的分页
    适用于:大数据量和app分页---》下拉加载下一页,不需要指定跳转到第几页
"""
class BookView(GenericViewSet, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    filter_backends = [SearchFilter]
    search_fields = ['name', 'author']  # 过滤

    pagination_class = CommonCursorPagination  # 验证此类分页时 视图类中不可以带有排序功能 要注释掉

4、异常处理

# 之前读APIViwe源码的时候,捕获了全局异常
	在执行三大认证,视图类的方法时候,如果出了异常,会被全局异常捕获

# 统一返回格式,无论是否异常,返回的格式统一  ,记录日志(好排查)
	{code:999,msg:服务器异常,请联系系统管理员}
	{code:100,msg:成功,data:[{},{}]}
    
    
### 第一步:写一个函数 新建exception.py文件:

from rest_framework.views import exception_handler  # 默认没有配置,出了异常会走它
from rest_framework.response import Response


def common_exception_handler(exc, context):
    # 第一步,先执行原来的exception_handler
    # 第一种情况,返回Response对象,这表示已经处理了异常,它只处理APIExcepiton的异常,第二种情况,返回None,表示没有处理
    res = exception_handler(exc, context)
    if res:  # exception_handler 已经处理了,暂时先不处理了
        # 998:APIExcepiton
        # res=Response(data={'code':998,'msg':'服务器异常,请联系系统管理员'})
        res = Response(data={'code': 998, 'msg': res.data.get('detail', '服务器异常,请联系系统管理员')})
    else:
        # 999:出了APIExcepiton外的异常
        # res=Response(data={'code':999,'msg':'服务器异常,请联系系统管理员'})
        res = Response(data={'code': 999, 'msg': str(exc)})

    # 注意:咱们在这里,可以记录日志---》只要走到这,说明程序报错了,记录日志,以后查日志---》尽量详细
    # 出错时间,错误原因,哪个视图类出了错,什么请求地址,什么请求方式出了错
    request = context.get('request')  # 这个request是当次请求的request对象
    view = context.get('view')  # 这个viewt是当次执行的视图类对象
    print('错误原因:%s,错误视图类:%s,请求地址:%s,请求方式:%s' % (str(exc), str(view), request.path, request.method))
    return res



### 第二步:把函数配置在配置文件中:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.exception.common_exception_handler'  # 再出异常,会执行这个函数
}

视图类 views.py:

### 全局异常,继承APIView及其子类就可以
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import APIException
class TestView(APIView):
    def get(self, request):
        ## 第一:程序出错
        # l=[1,2,3]
        # print(l[99])

        ## 第二:主动抛异常
        raise Exception('我错了')

        # 第三:主动抛 APIException的异常
        # raise APIException('APIException我错了')

        return Response('ok')

配置测试路由 urls.py:

    path('test/', views.TestView.as_view()),

image

posted @ 2022-04-07 01:25  Deity_JGX  阅读(197)  评论(0编辑  收藏  举报