7

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

 3 years ago
source link: https://www.cnblogs.com/jgx0/p/16110561.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

上期内容回顾

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

# 鸭子类型:
	不需要显示继承一个类,只要多个类中有同样的属性或方法,我们把它们称之为一种类,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'}
	全局使用
	局部使用
	局部禁用

今日内容概要

# 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)),
]
# 按照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']
"""
# 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

新建 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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK