• DRF学习之三大认证


    一、认证

    1、自定义认证

    在前面说的 APIView 中封装了三大认证,分别为认证、权限、频率。认证即登录认证,权限表示该用户是否有权限访问接口,频率表示用户指定时间内能访问接口的次数。整个请求最开始的也是认证。

    (1)需求

    • 登陆认证
    • 用户登陆成功–》签发token
    • 以后需要登陆才能访问的接口,必须写到当时登陆签发的token过来
    • 后端验证–》验证通过–》继续后续操作

    (2)定义模型表

    from django.contrib.auth.models import User
    from django.db import models
    
    
    class Book(models.Model):
        name = models.CharField(max_length=64)
        price = models.IntegerField()
        publish = models.CharField(max_length=64)
    
    class UserInfo(AbstractUser):
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=32)
        user_type=models.IntegerField(choices=((1,'注册用户'),(2,'普通管理员'),(3,'超级管理员')),default=1)
        
        @property
        def is_authenticated(self):
            return True
    
        class Meta:
            verbose_name_plural = '用户表'
    
    
    class UserToken(models.Model):
        # 用于存放uuid
        token = models.CharField(max_length=64)  # 存用户登陆状态【作用等同于session表】
        user = models.OneToOneField(to='UserInfo', on_delete=models.CASCADE)  # 跟UserInfo表是一对一
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    (3)登陆接口

    import uuid
    from rest_framework.viewsets import ViewSet
    from django.contrib import auth
    
    # 继承 ViewSet,可以自动添加路由,也可以使用 action 装饰器自定义请求方法
    class LoginView(ViewSet):
    	# 登录功能不需要认证,设为空列表
        authentication_classes = []
        permission_classes = []
    
        @action(methods=['post', ], detail=False)
        def login(self, request):
            username = request.data.get('username')
            password = request.data.get('password')
            # 验证用户名以及密码是否正确
            user = auth.authenticate(request, username=username, password=password).first()
            if not user:
                return Response({'code': 10001, 'msg': '用户名或密码错误'})
            else:
                token = str(uuid.uuid4())
                # defaults 是需要更新的数据, user可以理解为是筛选的条件
                # UserToken.objects.filter(user=user).update('token':token)
                models.UserToken.objects.update_or_create(defaults={'token': token}, user=user)
                return Response({'code': 10000, 'msg': '登录成功', 'token': token})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    (4) 认证类

    • 自定义认证表需要创建认证类,首先继承拓展 BaseAuthentication

    • 导入from rest_framework.authentication import BaseAuthentication

    from rest_framework.authentication import BaseAuthentication
    from .models import UserToken
    from rest_framework.exceptions import AuthenticationFailed  # 异常
    '''
    1 写个类,继承BaseAuthentication
    2 重写 authentication 方法
    3 在authentication 中完成用户登陆的校验
        -用户携带token--》能查到,就是登陆了
        -用户没带,或查不到,就是没登录
    4 如果校验通过,返回当前登录用户和token
    5 如果没校验通过,抛AuthenticationFailed
    '''
    class LoginAuth(BaseAuthentication):
        # 重写 BaseAuthentication 中的 authenticate 方法
        def authenticate(self, request):
            # request 是新的request,从request中取出用户携带的token
            # 用户带在哪了? 后端定的:能放哪? 请求地址 ?token   请求体中  请求头中
            # 放在请求头中
            token=request.META.get('HTTP_TOKEN')
            # 校验
            user_token=UserToken.objects.filter(token=token).first()
            if user_token:
                # 返回的第一个值是当前登录用户,第二个值是 token
                user=user_token.user
                return user,token
            else:
                raise AuthenticationFailed('很抱歉,您没有登陆,不能操作')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    (5)使用

    ① 局部使用
    • 局部使用,只需要在视图类里加入:
    class BookView(GenericViewSet, ListModelMixin, CreateModelMixin, DestroyModelMixin):
        # 写个认证类,在视图类上配置即可
        authentication_classes = [LoginAuth]
    
    • 1
    • 2
    • 3
    ② 全局使用
    • 在配置文件中添加配置,DEFAULT_AUTHENTICATION_CLASSES 的值为列表,其包含认证类路径
    REST_FRAMEWORK = {
        # 权限认证
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'utils.authentication.LoginAuth'
        ],
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 如果想局部禁用,也可以实现
    class UserView(ViewSet):
        authentication_classes = [] # 列表为空就行了,因为局部优先
    
    • 1
    • 2
    ③ 选择使用
    • 可以选择某一些方法可以认证,在视图类中添加 get_authenticators
    def get_authenticators(self):
        if self.request.method != 'GET':
            return [LoginAuth(), ]
    
    • 1
    • 2
    • 3

    (6)总结流程

    • 继承 BaseAuthentication
    • 重写 authenticate 方法,方法中编写逻辑,存在需要返回元组,为登录用户和 token,不存在则报 AuthenticationFailed 认证异常
    • 编写好认证类可以在局部使用,也可以在全局使用

    2、内置认证类

    • SessionAuthentication 之前老的 session 认证登录方式用,后期不用
    • BasicAuthentication 基本认证方式
    • TokenAuthentication 使用 token 认证方式,也可以自己写

    可以在配置文件中配置全局默认的认证方案

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.SessionAuthentication',  # session认证
            'rest_framework.authentication.BasicAuthentication',   # 基本认证
        )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    也可以在每个视图中通过设置authentication_classess属性来设置

    from rest_framework.authentication import SessionAuthentication, BasicAuthentication
    from rest_framework.views import APIView
    
    class ExampleView(APIView):
        authentication_classes = [SessionAuthentication, BasicAuthentication]
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二、权限

    登录认证成功后,还需要认证权限,有一些接口需要指定权限才能访问。所以权限需要和登录认证相关联。每个人的权限在表中默认设为普通用户。

    注意:如果设置了权限,那么必需先通过用户认证才能进行权限校验。

    1、自定义权限

    • 自定义权限需要继承 BasePermission 编写权限类
    • 导入语句:from rest_framework.permissions import BasePermission
    from rest_framework.permissions import BasePermission
    from .models import UserToken, UserInfo
    
    class UserPermission(BasePermission):
    	# message 为认证失败提示信息
        message = ''
    
    	# 需要重写 has_permission 方法,原方法默认返回 True
        def has_permission(self, request, view):
        	# 获取当前登录用户
            user = request.user
            # 获取当前用户权限类型
            user_type = user.user_type
            if user_type == 1:
            	# 权限符合返回 True
                return True
            else:
            	# 权限不符合,添加提示信息,并返回 False
                # 只要使用了choice,可以通过get_字段名_display()拿到数字对应的中文
                self.message = '你是: %s,权限不够' % user.get_user_type_display()
                return False
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2、使用

    (1)局部使用

    • 局部使用,只需要在视图类里加入该权限类即可:
    permission_classes = [UserPermission,]
    
    • 1

    (2)全局使用

    • 全局使用也是在配置文件中添加
    REST_FRAMEWORK = {
        # 用户认证
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'app01.authentication.LoginAuth'
        ],
        'DEFAULT_Permission_CLASSES': [
            'utils.permission.UserPermission'
        ],
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 如果想局部禁用,也可以实现
    # 例如局部禁用---》登录接口
    class PublishView(GenericViewSet):
        permission_classes = []
    
    • 1
    • 2
    • 3

    (3)选择使用

    • 在视图类中添加 get_permissions 判断如果请求方式符合就去认证
    def get_permissions(self):
        if self.request.method == 'DELETE':
            return [UserPermission(), ]
    
    • 1
    • 2
    • 3

    3、总结流程

    • 继承 BasePermission
    • 重写 has_permission 方法,方法中编写逻辑,有权限返回 True,没有权限可以添加 message 并返回 False
    • 编写好权限类可以在局部使用,也可以在全局使用

    4、内置权限类

    from rest_framework.permissions import AllowAny,IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly
    
    -AllowAny 					允许所有用户
    -IsAdminUser  				校验是不是 auth 的超级管理员权限
    -IsAuthenticated 			后面用,验证用户是否登录,登录后才有权限,没登录就没有权限
    -IsAuthenticatedOrReadOnly 	了解即可
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (1)局部使用

    • 可以在具体的视图中通过 permission_classes 属性来设置,如下
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.views import APIView
    
    class ExampleView(APIView):
        permission_classes = [IsAuthenticated,]
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (2)全局使用

    • 也可以在配置文件中全局设置默认的权限管理类,如下
    REST_FRAMEWORK = {
        ....
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated',
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 如果未指明,则采用如下默认配置
    'DEFAULT_PERMISSION_CLASSES': [
       'rest_framework.permissions.AllowAny',
    ]
    
    • 1
    • 2
    • 3

    三、 频率

    作用:限制接口的访问频率

    1、 频率类

    from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
    class CommonThrottle(SimpleRateThrottle):
        rate = '3/m'  # 一分钟3次
        def get_cache_key(self, request, view):
            # 返回什么,就会以什么做限制--》ip地址限制;用户id
            return request.META.get('REMOTE_ADDR')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、使用

    (1)局部使用

    在视图类中添加

    from .throttling import CommonThrottle
    class PublishView(GenericViewSet):
        throttle_classes = [CommonThrottle]
    
    • 1
    • 2
    • 3

    (2)全局使用

    同样,局部禁用只要赋值空列表即可

    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': [
            'app01.throttling.CommonThrottle'
        ],
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (3)装饰器使用

    # permission_classes 装饰器通常用于给 Django REST framework 视图函数或视图类添加权限控制。这个装饰器可以用在函数视图和类视图上,不仅限于类视图。
    
    from rest_framework.decorators import permission_classes
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.response import Response
    from rest_framework.views import APIView
    
    @permission_classes([IsAuthenticated])
    def my_function_view(request):
        # Your view logic here
        return Response("Hello, World!")
    
    
    # 在类视图中,你也可以像下面这样使用 permission_classes 装饰器:
    
    from rest_framework.decorators import permission_classes
    from rest_framework.permissions import IsAuthenticated
    from rest_framework.response import Response
    from rest_framework.views import APIView
    
    @permission_classes([IsAuthenticated])
    class MyAPIView(APIView):
        def get(self, request):
            # Your GET method logic here
            return Response("Hello, World!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3、总结流程

    • 继承 SimpleRateThrottle 类

    • 重写 get_cache_key 方法,该方法返回的值是限制的依据,例如按照 IP 地址来作为限制条件,设置每个 IP 地址访问的次数。需要注意的是,IP 地址在 request.META 中获取 'REMOTE_ADDR'

    • 接下来需要配置 setting ,需要设置访问的频率。首先需要设置属性 scope,该属性的值会作为频率的键名,在 setting 配置文件 REST_FRAMEWORK 中的 DEFAULT_THROTTLE_RATES 配置,键名是 scope,键值是字符串,格式为 ‘x/y’,x 表示访问的次数,y 表示访问的时间区间(可以为 s(秒)、m(份)、h(时)、d(天))

    • 编写好频率类可以在局部使用,也可以在全局使用

    4、 内置频率类

    (1)AnonRateThrottle

    AnonRateThrottle 内置频率类的功能:对于登录用户不限制次数,只未登录用户限制次数,限制的次数需要在配置文件中配置。使用也支持全局和局部。

    • setting.py
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.AnonRateThrottle',
        ],
        'DEFAULT_THROTTLE_RATES': {
            'anon': '5/day',
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (2)UserRateThrottle

    UserRateThrottle 内置频率类的功能:限制登录用户的频率,限制的次数需要在配置文件中配置。也支持全局和局部使用。

    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.UserRateThrottle',
        ],
        'DEFAULT_THROTTLE_RATES': {
            'user': '1000/day',
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5、自定义频率类

    VISIT_RECORD = {}
    
    class CommonThrottle(BaseThrottle):
        def __init__(self):
            self.history = None
    
        def allow_request(self, request, view):
            # 实现限流的逻辑
            # 以IP限流
            # 访问列表 {IP: [time1, time2, time3]}
            # 1、获取请求的IP地址
            ip = request.META.get("REMOTE_ADDR")
            # 2、判断IP地址是否在访问列表
            now = time.time()
    
            if ip not in VISIT_RECORD:
                # (1)不在 需要给访问列表添加key,value
                VISIT_RECORD[ip] = [now, ]
                return True
                # (2)在 需要把这个IP的访问记录 把当前时间加入到列表
            history = VISIT_RECORD[ip]
            history.insert(0, now)
    
            # 3、确保列表里最新访问时间以及最老的访问时间差 是1分钟
            while history and history[0] - history[-1] > 60:
                history.pop()
            self.history = history
            # 4、得到列表长度,判断是否是允许的次数
            if len(history) > 3:
                return False
            else:
                return True
    
        def wait(self):
            # 返回需要再等多久才能访问
            if not self.history:
                return 0
    
            time = max(0, 60 - (self.history[0] - self.history[-1]))
            return time
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    在这段代码中,history 是一个记录特定 IP 地址访问时间戳的列表。这个列表存储了特定 IP 地址在一分钟内的访问时间戳,用于限制该 IP 地址的访问频率。

    在 allow_request 方法中,当一个请求到达时,会检查该请求的 IP 地址是否已经存在于 VISIT_RECORD 中。如果存在,会将当前时间戳插入到该 IP 地址对应的历史访问时间戳列表中,并且清理超过一分钟的旧时间戳。

    在 wait 方法中,会计算当前时间与最新访问时间戳的时间差,以确定需要等待多久才能再次允许该 IP 地址的访问。如果 self.history 为空,表示该 IP 地址尚未有访问记录,因此返回等待时间为 0。

    因此,self.history 在这段代码中用于跟踪特定 IP 地址的访问时间戳列表,以便在限制访问频率时进行判断和计算。

    6、其他频率类

    • AnonRateThrottle
      • 限制所有匿名未认证用户,使用 IP 区分用户。
      • 使用 DEFAULT_THROTTLE_RATES[‘anon’] 来设置频次
    • UserRateThrottle
      • 限制认证用户,使用 User id 来区分。
      • 使用 DEFAULT_THROTTLE_RATES[‘user’] 来设置频次
    • ScopedRateThrottle
      • 限制用户对于每个视图的访问频次,使用 ip 或 user id

    四、排序

    一般涉及到了查询所有才有排序,对于表模型查询所有数据时需要根据某个字段进行排序时,我们就可以使用到REST framework提供的内置排序组件OrderingFilter来帮助我们快速指名数据按照指定字段进行排序,遵循了restful规范中:地址栏中带过滤条件。

    • http://127.0.0.1:8008/app01/api/v1/books/?ordering=price 升序
    • http://127.0.0.1:8008/app01/api/v1/books/?ordering=price 倒序

    1、使用步骤

    from rest_framework.filters import OrderingFilter
    # 在视图类中配置
    class BookListView(GenericViewSet,ListModelMixin):
        # 必须是继承 GenericAPIView 的视图类---》继承APIView是不能这么配置的
        filter_backends = [OrderingFilter]
        # 必须指定表模型数据字段,可以写多个排序字段
        ordering_fields=['price','name']  # 默认是按id排序
    
    # 访问
    http://127.0.0.1:8008/app01/api/v1/books1/?ordering=-price
    http://127.0.0.1:8008/app01/api/v1/books1/?ordering=price
    http://127.0.0.1:8008/app01/api/v1/books1/?ordering=-price,-name
    
    # 定制返回格式--》需要重写list--》那重写了list,过滤还生效吗?
    	-如果纯自己写了:不生了
        -如果还是使用父类的list方法:生
        -半自己写:只要有他就会生效 self.filter_queryset()
        
    	-filter_queryset(get_queryset(){code:100,msg:成功,results:[]}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注意:继承APIView的是使用不了以上DRF提供的排序组件,需要自己写,自己从请求地址中取出排序规则,然后自己排序

    2、继承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):
        # 由于是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'''
    
            # http://127.0.0.1:8008/app01/api/v1/books/?ordering=price
            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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    五、过滤

    restful规范中,要求请求地址中带过滤条件,五个接口中,只有查询所有接口需要过滤和排序。其实排序也是过滤的一种,所以过滤跟排序并不冲突,是可以同时使用多个的。但是要注意一个大原则:把一次性过滤掉很多的往前放,因为我们猜测过滤的内部实现也就是for循环一个个过滤类,执行过滤类的filter_queryset方法。另外,实现过滤也有三种方式:

    1、继承GenericAPIView使用DRF内置过滤器实现过滤

    首先导入:from rest_framework.filters import SearchFilter

    • views.py
    # 过滤和排序组合使用
    http://127.0.0.1:8008/app01/api/v1/books1/?search=&ordering=price
    # 只要出版社或名字带海都能查出来---》search_fields=['name','publish']
    http://127.0.0.1:8008/app01/api/v1/books1/?search=# 使用方式
    from rest_framework.filters import OrderingFilter,SearchFilter
    class BookListView(GenericViewSet,ListModelMixin):
        filter_backends = [OrderingFilter,SearchFilter]
        ordering_fields = ['price', 'name']
        # 也可以多个字段模糊匹配
        search_fields=['name','publish']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    使用DRF内置过滤器,它搜索结果的关系为or(或)的关系,不是and关系。并且只能是使用关键字search才能使用,如果我们想要用name=东&price=66这种方式的得使用别的方法来实现。

    2、使用第三方模块django-filter实现and关系的过滤

    • 首先需要安装第三方模块:
    pip install django-filter
    
    • 1
    • 然后再视图类中配置
    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']
        
        # 但是对于django-filter来讲它是支持扩写的,所以是可以支持模糊匹配,具体操作自寻查找
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 访问
    # 查询价格为66的图书
    http://127.0.0.1:8008/app01/api/v1/books1/?price=66
    
    # 查询价格为66的图书 并且名字为 两天 的图书
    http://127.0.0.1:8008/app01/api/v1/books1/?price=66&name=两天  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 此外,django-filter还有更强大的搜索,感兴趣的可以深入了解一下

    3、自定制过滤类

    (1)使用步骤

    • 先定义一个过滤类,并且继承BaseFilterBackend
    • 然后重写filter_queryset方法,并且在内部完成过滤规则
    • 最后在视图类中配置自定义的过滤类

    (2)过滤类

    from rest_framework.filters import BaseFilterBackend
    from django.db.models import Q
    class CommonFilter(BaseFilterBackend):
        def filter_queryset(self, request, queryset, view):
            # 完成过滤,返回 qs对象
            # 查询价格为66 或者 名字中包含海的数据
            # http://127.0.0.1:8008/app01/api/v1/books1/?price=66&name=海
            price = request.query_params.get('price', None)
            name = request.query_params.get('name', None)
            if price and name:
                queryset = queryset.filter(Q(price=price) | Q(name__contains=name))
            if price:
                queryset=queryset.filter(price=price)
            if name:
                queryset = queryset.filter(name__contains=name)
            return queryset
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (3)视图函数

    from .filters import CommonFilter  
    class BookView(ViewSetMixin,ListAPIView):
        queryset = models.Book.objects.all()
        serializer_class = BookSerializer
        # 无需再配置字段了,因为在自定制类中已经写好了过滤规则,所以在视图类中无需配置
        filter_backends = [CommonFilter]
        # 这样就实现了模糊匹配name字段并且精准匹配price字段,以及查询单个字段的规则
    
    # 访问:127.0.0.1:8000/api/v2/books/?name=游记&price=666
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4、排序搭配过滤使用

    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']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    六、分页

    分页也是只针对查询所有的接口,其他四个接口不需要分页。drf内置了三个分页器,对应三种分页方式,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用。一个接口只能有一种分页方式,不能混合分页方式。

    另外,分页跟过滤排序不冲突,都可以一起使用。分页在web端、移动端都有涉及,后端一定会需要配合实现分页接口。

    使用步骤:

    • 写个类,继承某个分页类
    • 在视图类中配置:视图类必须继承 GenericAPIView

    1、分页器一:Pagination(基本分页)

    • 通过自定义Pagination类,来为视图添加不同分页行为。在视图中通过pagination_clas属性来指明。
    	from rest_framework.pagination import PageNumberPagination
    
    	class CommonPageNumberPagination(PageNumberPagination):
    	    page_size = 3 # 默认每页显示的条数
            # http://127.0.0.1:8008/app01/api/v1/books1/?page=2
    	    page_query_param = 'page' # 使用该关键字指定页码(客户端使用)
    		# http://127.0.0.1:8008/app01/api/v1/books1/?page=2&size=3  查询第2页,每页显示3条
    	    page_size_query_param = 'size' # 通过该关键字可以手动指定页面显示的数据条数(客户端使用)
    		# 每页最多显示10条
    	    max_page_size = 5 # 只针对客户端手动指定页面显示的数据条数做出一个限制,但不影响page_size属性。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、分页器二: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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 从第二条数据之后开始指定获取数据的条数,使用了limit指定了获取6条,但是被max_limit=5给限制了,所以后端只能返回2条数据,如果不使用limit指定获取的数据条数,那么默认使用default_limit=2后端会返回2条数据。

    3、分页器三:CursorPagination(游标分页)

    • 使用游标分页的方式后就不能再其中使用排序了,因为其内部已经排好序了’
    • 并且只能选择上一页和下一页,不能指定跳转某一页,但是速度快,针对与特别大的数据量,游标分页有优势
    from rest_framework.pagination import CursorPagination
    class CommonCursorPagination(CursorPagination):
        cursor_query_param = 'cursor'  # 按游标查询的查询条件,value值前端是不知道的,只能通过后台返回
        page_size = 2  # 每页显示多少条啊
        ordering = 'id'  # 排序规则,必须是表中的字段
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4、视图类

    class BookListView(GenericViewSet, ListModelMixin):
        queryset = Book.objects.all()
        serializer_class = BookSerializer
        permission_classes = []
        throttle_classes = []
        # pagination_class = CommonPageNumberPagination # 分页方式只能选择一种
        # pagination_class = CommonLimitOffsetPagination # 分页方式只能选择一种
        pagination_class = CommonCursorPagination # 分页方式只能选择一种
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5、了解 基于APIView实现分页功能

    
    from rest_framework.viewsets import ViewSet
    from .pagination import CommonPageNumberPagination,CommonLimitOffsetPagination
    from .serializer import BookSerializer
    from rest_framework.response import Response
    
    class BookView(ViewSet):
        def list(self, reqeust):
            queryset = models.Book.objects.all()
            # 调用咱们写的分页类对象的paginate_queryset方法返回了,分页后的queryset对象
            '''使用PageNumberPagination(普通分页)'''
            # pagenation = CommonPageNumberPagination()  # 实例化得到对象
            '''使用LimitOffsetPagination(偏移分页)'''
            # pagenation = CommonLimitOffsetPagination()  # 实例化得到对象
            '''使用CursorPagination(游标分页)'''
            pagenation = CommonCursorPagination()  # 实例化得到对象
            page = pagenation.paginate_queryset(queryset, reqeust, self)
            serializer = BookSerializer(page, many=True)  # page分页后的对象
            '''使用它自己的方法(三种分页方式都兼容)'''
            # return pagenation.get_paginated_response(serializer.data)
            '''
    	        使用定制的分页返回格式,得去pagination源码里面的对应每种分页方法中的
    	        def get_paginated_response(self, data):方法里面看返回格式怎么写
    	        '''
            '''也可以自己定制PageNumberPagination(普通分页)'''
            return Response({
                'status': 100,
                'message': '查询成功',
                'count': pagenation.page.paginator.count,
                'next': pagenation.get_next_link(),
                'previous': pagenation.get_previous_link(),
                'results': serializer.data,
            })
            '''定制LimitOffsetPagination'''
            return Response({
                'status': 100,
                'message': '查询成功',
                'count': pagenation.count,
                'next': pagenation.get_next_link(),
                'previous': pagenation.get_previous_link(),
                'results': serializer.data,
            })
    
            '''定制CursorPagination'''
            return Response({
                'status': 100,
                'message': '查询成功',
                'next': pagenation.get_next_link(),
                'previous': pagenation.get_previous_link(),
                'results': serializer.data,
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    七、补充

    1、断言

    • 在python库的源码中经常见到断言,那么怎么理解断言呢?
    # 语法:
    assert 条件,'字符串'
    
    # 翻译成if
    if 条件:
        条件成立,继续走后续代码
    else:
        raise Exception('字符串')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 案例:
    a = '1'
    assert isinstance(a,int),'不行,a必须是int'
    
    • 1
    • 2

    在这里插入图片描述

    源码中使用:

    assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、模块与包

    (1)什么是模块?

    • 一个py 文件,如果被导入使用,它就是模块
    • 一个py文件,点右键运行,它就叫 脚本文件

    (2)什么是包?

    • 一个文件夹下 有 init.py ,下面又有很多文件夹和py文件,导入使用的

    (3)报错信息

    ModuleNotFoundError: No module named 'xx' 
    
    • 1
    • 但是这个模块有
    • 那么我们就应该知道是导入的问题:路径不对

    3、相对导入和绝对导入

    在Python中,相对导入和绝对导入是用于导入模块的两种不同方式。它们的主要区别在于导入模块时使用的路径方式。

    (1)绝对导入

    • 绝对导入是指从顶层包开始的完整导入路径,相对于环境变量 sys.path。在绝对导入中,你需要指定完整的包路径来导入模块,从项目的根目录开始一直到目标模块。绝对导入可以通过使用绝对导入语法来实现,例如:

      from package.subpackage import module
      
      # 例如
      ['D:\\PyCharm 2023.2.1\\workspace\\drf05', 'D:\\PyCharm 2023.2.1\\workspace\\drf05', 'D:\\PyCharm 2023.2.1\\plugins\\python\\helpers\\pycharm_display', 'D:\\python\\python311\\python311.zip', 'D:\\python\\python311\\Lib', 'D:\\python\\python311\\DLLs', 'D:\\python\\python311', 'D:\\python\\python311\\Lib\\site-packages', 'D:\\PyCharm 2023.2.1\\plugins\\python\\helpers\\pycharm_matplotlib_backend']
      其中
      	1.项目根路径
          2.site-packages 下载的第三方模块
          3.python内置的,都在环境变量中
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 绝对导入的优点是清晰明确,能够避免模块名冲突,并且在代码重构时更加稳定。

    (2)相对导入

    • 相对导入是指从当前模块所在的位置开始的导入路径。相对导入使用与当前模块相关的路径来导入模块,而不是从顶层包开始。相对导入可以通过使用相对导入语法来实现,例如:

      from . import module
      
      • 1
    • 相对导入的优点是更具灵活性,因为它依赖于模块的相对位置,而不是绝对路径。这使得模块可以更容易地移动或重命名,而无需更改导入语句。

    (3)Python中相对导入的规则

    1. 单个点.表示当前目录。
    2. 两个点..表示父目录。
    3. 相对导入只能在Python包中使用,而不能在脚本中使用。因为如果脚本文件
    4. Python 3中默认禁用隐式相对导入,必须使用显式相对导入。

    在实际开发中,通常建议使用绝对导入,因为它更加明确和稳定。相对导入在某些情况下可能会引起混乱,尤其是当项目结构较为复杂时。然而,相对导入在某些特定情况下也是很有用的,特别是在编写可移植的包或模块时。

    (4)建议

    • django项目,如果在同一个app下
      • 建议:相对导入
      • from .views import index
    • 如果用绝对导入,会相对来说要写很多路径
    • from app01.views import index

    4、修改项目名字出现的问题及解决方案

    (1)改文件夹名改不了

    • 别的进程打开了---->重启电脑

    (2)改项目名改不了

    • 项目路径下的 .idea文件夹----->项目配置
    • 如果打开项目有问题----> 把 .idea删除---->再打开重新运行,它会再次生成的
    • 注意:当我们打包文件的时候,.idea文件夹是不用加进去的,因为每个电脑的配置都不一样

    (3)改项目中某个文件的名字(慎改)

    • 首先模块路径都定好了
    • 如果我们一旦改了----> 项目百分百运行不了
    • 如果改了,那么我们需要右击项目----> 找到replace in files—> 替换掉项目中所有叫原来文件夹名的 字符串

    (4)问题

    • 如果Django项目运行不了了
      • 尝试django-server删除重新创建
      • setting中搜django—> 配置项目路径和配置文件路径

    在这里插入图片描述

    5、浏览器特点

    • 浏览器默认访问http是80端口,https是443端口
    • 先访问某个页面【域: 协议,地址,端口】,然后再访问另一个页面,只要域一样就会把当时存在cookie中的值,带到后端
    • 这种思想方便我们写登录接口
      • 登录接口:自定义用户表,UserToken表[用户登录信息存在后端–》本质就是django-session]
      • 登录成功–》返回给前端随机字符串
      • 目的:后期它再访问,要携带字符串—》我们通过校验–》确认是我们给的,在UserToken表中有记录

    6、什么是重写,什么是派生,什么又是重载?

    • 在继承关系下,子类再写一遍父类中的方法—》称之为重写

      • 重写的目的是定制新功能
    • 在继承关系下,子类写父类中没有的方法—》称之为派生方法

    • 重载就是方法名一样,参数或返回值不一样----》多了参数或者返回值不一样
      iews import index`

    • 如果用绝对导入,会相对来说要写很多路径

    • from app01.views import index

    4、修改项目名字出现的问题及解决方案

    (1)改文件夹名改不了

    • 别的进程打开了---->重启电脑

    (2)改项目名改不了

    • 项目路径下的 .idea文件夹----->项目配置
    • 如果打开项目有问题----> 把 .idea删除---->再打开重新运行,它会再次生成的
    • 注意:当我们打包文件的时候,.idea文件夹是不用加进去的,因为每个电脑的配置都不一样

    (3)改项目中某个文件的名字(慎改)

    • 首先模块路径都定好了
    • 如果我们一旦改了----> 项目百分百运行不了
    • 如果改了,那么我们需要右击项目----> 找到replace in files—> 替换掉项目中所有叫原来文件夹名的 字符串

    (4)问题

    • 如果Django项目运行不了了
      • 尝试django-server删除重新创建
      • setting中搜django—> 配置项目路径和配置文件路径

    [外链图片转存中…(img-KJv7ImPd-1713791287515)]

    5、浏览器特点

    • 浏览器默认访问http是80端口,https是443端口
    • 先访问某个页面【域: 协议,地址,端口】,然后再访问另一个页面,只要域一样就会把当时存在cookie中的值,带到后端
    • 这种思想方便我们写登录接口
      • 登录接口:自定义用户表,UserToken表[用户登录信息存在后端–》本质就是django-session]
      • 登录成功–》返回给前端随机字符串
      • 目的:后期它再访问,要携带字符串—》我们通过校验–》确认是我们给的,在UserToken表中有记录

    6、什么是重写,什么是派生,什么又是重载?

    • 在继承关系下,子类再写一遍父类中的方法—》称之为重写

      • 重写的目的是定制新功能
    • 在继承关系下,子类写父类中没有的方法—》称之为派生方法

    • 重载就是方法名一样,参数或返回值不一样----》多了参数或者返回值不一样

      • python中没有重载
  • 相关阅读:
    django数据库报错汇总:django.db.utils.OperationalError 1045,1049,2003
    Bean装配相关注解使用说明
    某30m小箱梁渠桥结构计算与施工图设计
    一些常见的字符串匹配算法
    服装行业如何做数字化转型?
    FolkMQ 作个简单的消息中间件(最简单的那种), v1.3.1 发布
    MySQL慢查询日志:如何定位执行慢的sql语句
    【docker】拉取人大金仓KingbaseES数据库镜像速度很慢问题
    5.系统设计的工作内容与技能工具有哪些?
    《创业者必学的搞流量营销课》负责百万到年入千万,500W+粉丝操盘经验
  • 原文地址:https://blog.csdn.net/Xiao20000101/article/details/138092426