drf-认证、权限、频率

0 django转换器、配置文件

0.1 django2转换器

  • str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式

  • int,匹配正整数,包含0。

  • path,匹配任何非空字符串,包含了路径分隔符(/)

  • slug,匹配字母、数字以及横杠、下划线组成的字符串。

  • uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。

#  django转换器:django 2.x以后,为了取代 re_path
    -int    path('books/<str:name>')   ---->/books/1----> name=1---> 当参数传入视图类的方法中
    -str
    -path
    -slug
    -uuid

0.2 django的配置文件

1 djagno项目要运行,优先执行配置文件的内容,做一下配置加载工作
    -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_day07.settings')

2 任何一个django项目都有两套配置:
    -一套是项目自己的,自己有那个配置参数,优先用自己的
    -一套是内置的(django内置的)
        
 # pathlib 模块,处理路径

配置文件中参数的作用

from pathlib import Path

# 1  项目的根路径
BASE_DIR = Path(__file__).resolve().parent.parent

# 2 密钥---> djagno中涉及到加密的,大部分都会用这个密钥,每个项目的密钥都不同。
SECRET_KEY = 'django-insecure-eh=o(kt_70%8wj4r+le-7*$7t+fn%_2rfh61f09(2^&3q-5vk)'

# 3 是否开启调试模式,上线一定关闭
# 只要是调试模式:
# 访问的路径不存在,他会显示出所有能访问的路径
# 视图类出了异常,浏览器中能看到
DEBUG = False

# 4 项目是要部署在某个服务器上,这个列表中写,部署服务器的ip地址, * 表示任意地址都可以
ALLOWED_HOSTS = ['*']

# 5 所有应用:内置app,我们自己写的app
# from django.contrib import auth

INSTALLED_APPS = [
    'django.contrib.admin',  # 后台管理---> 很多表不是它的,是别的app的
    'django.contrib.auth',  # auth 模块,UsrInfo表----> 有6个表
    'django.contrib.contenttypes',  # 有个django_content_type表是,这个app的
    'django.contrib.sessions',  # session相关的
    'django.contrib.messages',  # 消息框架
    'django.contrib.staticfiles',  # 静态文件开启的app
    'app01.apps.App01Config',  # app01
    'rest_framework'  # drf
]

# 6  中间件
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  # session相关
    'django.middleware.common.CommonMiddleware',  # 公共
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# 7 根路由
ROOT_URLCONF = 'drf_day07.xxx'

# 8 模板文件所在路径
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # 必须是个列表
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# 9 项目上线,运行application,后面再说
WSGI_APPLICATION = 'drf_day07.wsgi.application'

# 10 数据库配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

# 密码认证相关,忽略

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# 11 做国际化
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# 13 静态文件
STATIC_URL = '/static/'

# 14 表中,默认可以不写id,id主键自增,之前全是AutoField,长度很短
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

一 登录功能

因为接下来的学习中需要使用到登陆功能。我们先写一个登录类。

表模型

class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    password = models.CharField(max_length=64)


class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to=UserInfo, on_delete=models.CASCADE)

视图类

# from rest_framework.views import APIView
# 要重新写路由,必须要继承ViewSetMixin,然而ViewSetMixin+APIView=ViewSet,所以直接继承ViewSet即可
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from .models import UserInfo, UserToken
# from rest_framework.decorators import action
import uuid  # 生成[理论上]永不重复的随机字符串


"""
为什么没有继承GenericAPIView:
    如果跟数据库有关系,又要用序列化类(序列化给前端)时才会继承GenericAPIView。
    而登录接口不需要使用序列化类,就直接继承APIVIew就行
"""

class UserView(ViewSet):
    # @action(methods=['POST'], detail=False)
    def login(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        # 使用用户名和密码查询用户是否存在
        user_obj = UserInfo.objects.filter(username=username, password=password).first()
        if user_obj:
            # 登录成功
            # 1 生成一个随机字符串 token
            token = str(uuid.uuid4())  # token是<class 'uuid.UUID'>类型,需要转换成字符串

            # 2 把token存到表中,UserToken表有值就更新,没有值就新增

            # 方式一:if判断
            # res = UserToken.objects.filter(user=user_obj)
            # if res:
            #     # res['token'] = token
            #     # res.save()
            #     UserToken.objects.filter(user=user_obj).update(token=token)
            # else:
            #     UserToken.objects.create(user=user_obj, token=token)

            # 方式二:使用update_or_create
            # 根据user查询,如果能查到,就更新,如果查不到,就新增
            # defaults={'token': token},字典中写需要新增或者更改的字段
            # 其余字段都是查询的字段,传到kwargs中
            UserToken.objects.update_or_create(user=user_obj, defaults={'token': token})

            return Response({'code': 100, 'msg': '登录成功', 'token': token})
        else:
            return Response({'code': 101, 'msg': '用户名或密码错误'})

        
        
        
# 回顾
    UserToken表中有user字段
    拿到了一个UserToken表的对象  
    	user_token.token 就是字符串
        user_token.user  基于对象的跨表查询,拿到的是user对象  user_token.user.password
        user_token.user_id  隐藏了这个字段,是可以用的,它是管理的user对象的id号
    # 查询功能
     UserToken.objects.filter(user=user对象)
     UserToken.objects.filter(user_id=user.id)

路由

# 方式一:
path('login/', views.UserView.as_view({'post':'login'})),
# 地址:http://127.0.0.1:8000/login/----post请求就会执行
# 路由如果这样写,是不需要使用action装饰器


# 方式二:自动生成路由---> 视图类中一定要用action装饰器
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('user', views.UserView, 'user')
urlpatterns = [
	]
# http://127.0.0.1:8000/user/login/
urlpatterns += router.urls


视图类中:
class UserView(ViewSet):
    @action(methods=['POST'], detail=False)  # 需要把action导入,从decorators中
    def login(self, request):

二 认证组件Authentication

登录认证:某些接口必须登录后才能访问:

1. 如何就算登录了 ---> 携带我们给的token串 ---> 校验通过了,就ok
2. 请求携带在哪里?
    -请求地址
    -请求体
    -请求头:token=request.META.get('HTTP_TOKEN')
    # 一个大前提:前端过来的数据都在request中
    # 有关于请求头的数据,基本全在request.META这个字典中

# APIView执行流程
    -在视图视图类的方法之前,执行了三大认证
    	-认证,权限,频率
        
# 认证:登录认证
    -登录认证---> 控制,某个接口必须登录后才能访问
    
    
# 认证组件使用步骤(固定用法)
    0 重新开一个py文件
    1 写一个类,继承BaseAuthentication
    2 在类中写:authenticate
    3 在方法中,完成登录认证,如果 不是登录的,抛异常
    4 如果是登录的,返回登录用户和token                                                                             
    5 在视图类中,使用认证类(局部使用)
    class BookView(APIView):
    	authentication_classes = [LoginAuth, ] 
     
    6 全局使用:
    ### 重点:不要在配置文件中,导入莫名其妙的包,会导致项目报错
    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': [
            'app01.auth.LoginAuth'
        ],

    }
    
    7 全局使用后,局部禁用
    class UserView(ViewSet):
        # 局部禁用
        authentication_classes = []
    
    8 认证类的使用顺序
    	-优先用视图类配置的
        -其次用项目配置文件
        -最后用drf默认的配置
        
        
        
# 小重点;一旦通过认证,在request中就有当前登录用户
    def get(self, request):
        # 一旦通过认证,在request中就有当前登录用户
        print(request.user.name,'访问了接口')

代码

视图层

# 2 认证组件
# 让查询所有接口,必须登录后才能访问
# 如何算登录?
# 登录成功后,返回给前端一个token随机字符串,只要携带我给的随机字符串,我就能定位到是谁,就是这个人登录了


from rest_framework.views import APIView
from .auth import LoginAuth


class BookView(APIView):
    authentication_classes = [LoginAuth, ]

    def get(self, request):
        # 一旦通过认证,在request中就有当前登录用户(是个对象),保存在request.user中
        # print(request.user)  # UserInfo object (1)
        print(request.user.username, '访问了接口')
        return Response({'code': 100, 'msg': '成功', 'data': '假设有很多数据'})

认证文件

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import UserToken


# 认证类
class LoginAuth(BaseAuthentication):
    def authenticate(self, request):  
        # 校验用户是否登录---> 请求中携带我给的token,就是登录了
        # token在哪携带,是接口规定的--->
        # 1 规定带在请求地址中---> 讲这个
        # 2 规定带在请求头中(这个后面使用多)
        # 3 规定带在请求体中


        # 取出token   
        # 1. 请求地址中
        # token = request.query_params.get('token')  # 地址问号后的参数,用GET取值(或query_params取值)
        # 2. 请求头中的token
        token = request.META.get('HTTP_TOKEN')   

        # 去数据库中,根据token,校验有没有数据
        user_token = UserToken.objects.filter(token=token).first()
        if user_token:  
            user = user_token.user  
            # 一旦认证通过,就会在request中保存用户对象,所以要返回user对象
            return user, token
        else:
            # 说明它带的token不对的,显示错误信息

            # 方式一:使用django自带的错误消息,显示英文,也可以在设置中更改语言,输出中文:LANGUAGE_CODE = 'zh-hans'
            # raise AuthenticationFailed()
            # "detail": "Incorrect authentication credentials." --> 错误的认证凭证

            # 方式二:自定义错误
            raise AuthenticationFailed('您没有登录,不能访问')

django自带的错误消息,显示英文。

路由

path('books/', views.BookView.as_view()),

配置文件

# 局部使用不需要在配置文件中设置
# 全局使用
# 怎么找,在drf的配置文件中
# from rest_framework.settings import  找见后删除掉


REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 直接写我们自己写的认证类的路径

        # 可以先写成下面的样子再转变过去
        # from app01.auth import LoginAuth
        'app01.auth.LoginAuth'  # 字符串形式
    ],
}

### 重点:不要在配置文件中,导入莫名其妙的包,会导致项目报错。因为django先会运行配置文件,遇见from就会导入文件,运行模块文件中的数据,但是django项目还没有起来,很多内容还没有加载好,直接运行模块文件,就会有很多内容找不见

三 权限组件Permissions

介绍及使用步骤

# 大家都登录了,但有的功能(接口),只有超级管理员能做,有的功能所有登录用户都能做----> 这就涉及到权限的设计了

# 权限设计:比较复杂---> 有acl,rbac,abac...

# 咱们现在只是为了先讲明白,drf的权限组件如何用,咱们先以最简单的为例
    -查询所有图书:所有登录用户都能访问(普通用户和超级管理员)
    	-其实没有权限控制
    -删除图书,只有超级管理员能访问
    	-给其它用户设置的权限
        
            
        
# 权限类的使用步骤
    0 重新开一个py文件
    1 写一个类,继承BasePermission
    2 在类中写方法:has_permission
    	-如果有权限,就返回True
        -如果没有权限,就返回False
        -错误信息是self.message='字符串'
        
    3 局部使用
    class BookDetailView(APIView):
        permission_classes = [AdminPermission, ]

    4 全局使用
        REST_FRAMEWORK = {
        'DEFAULT_PERMISSION_CLASSES': [
            'app01.permission.AdminPermission'
            ],
        }

    5 局部禁用
    class BookView(APIView):
    	permission_classes = []

代码

视图层

#### 3.权限组件
# 只有超级管理员有权限删除
# 需要有个字段保存用户类型
from .permission import AdminPermission


class BookDetailView(APIView):
    # permission_classes = [AdminPermission, ]

    def delete(self, request, *args, **kwargs):
        print(kwargs)  # pk是关键字传参
        print(request.user.username, '删除了一条数据')
        return Response({'code': 100, 'msg': '删除成功'})

权限py文件

from rest_framework.permissions import BasePermission


class AdminPermission(BasePermission):
    def has_permission(self, request, view):
        # 如果有权限,就是返回True,没有权限,返回False
        # 判断user_type是不是 1,根据当前登录用户对象
        # request.user  # 就是当前登录用户,一旦来到这里,认证就通过了
        if request.user.user_type == 1:  # 表模型中的数据类型是整型,就用整型。
            return True
        else:
            # 错误信息的固定用法
            self.message = '您好:%s,您没有权限' % request.user.username
            # self.message = 1  # 我们输入的是整型,显示给用户的也是字符串形式
            return False

表模型

    # choice参数映射存值,列表套元组或元组套元组
    user_type = models.IntegerField(choices=((1, '超级管理员'), (2, '普通用户'), (3, '其他用户')), default=3)

路由层

path('books/<int:pk>/', views.BookDetailView.as_view()),

配置文件

# 全局使用
# 怎们找,在drf的配置文件中
# from rest_framework.settings import 找见后删除掉

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'app01.permission.AdminPermission',
    ],
}

4 频率组件Throttling

4.1 继承SimpleRateThrottle写频率类

# 限制访问频次
    -比如某个接口,一分钟只能访问5次,超过了就得等
        -按IP地址  限制
        -按用户id  限制
    
# 频率类的使用步骤
    0 重新开一个py文件
    1 写个类,继承:SimpleRateThrottle
    2 重写某个方法:get_cache_key
    	-可以返回ip或用户id
    	-返回什么,就以什么做频率限制
        
    3 写一个类属性,随意命名一个名
    	scope = 'lqz'  # 这里的名字是配置文件中限制的key

    4 在配置文件中配置:
     'DEFAULT_THROTTLE_RATES': {
        'lqz': '3/m' # 一分钟访问3次
    },
        
        
   5 全局用
    'DEFAULT_THROTTLE_CLASSES': [
        'app01.throttling.MyThrottle',
    ],

   6 局部用
    class BookView(APIView):
        throttle_classes = [MyThrottle]

    7 全局使用,局部禁用
    class BookView(APIView):
    	throttle_classes = []

代码

频率py文件

from rest_framework.throttling import SimpleRateThrottle
from rest_framework.exceptions import AuthenticationFailed

class MyThrottle(SimpleRateThrottle):
    scope = 'lqz'  # 这里的名字是配置文件中限制的key

    def get_cache_key(self, request, view):
        # 1.返回客户端ip地址:
        # ip = request.META.get('REMOTE_ADDR')
        # print('客户端的ip是:', ip)
        # return ip
        
        
        # 2.按照用户id限制,用户可能没有登录就来到了频率组件,要先判断
        try:
            if request.user:
            user_id = request.user.id
            print('客户端的id是:', user_id)
            return user_id
        except Exception as e:
            raise AuthenticationFailed('您没有登录,请先登录吧')

配置文件

# 全局使用
# 怎们找,在drf的配置文件中
# from rest_framework.settings import 找见后删除掉

REST_FRAMEWORK = {
    # Throttling
    'DEFAULT_THROTTLE_RATES': {
        # key是频率类中的类属性scope定义的
        # 一分钟只能访问3次。'3/s':一秒钟只能访问3次
        'lqz': '3/m'
    },
    'DEFAULT_THROTTLE_CLASSES': [
        'app01.throttling.MyThrottle',
    ],
}


# 使用 `second`, `minute`, `hour` 或`day`来指明周期。
# 'lqz': '3/d'  #  一天只能访问3次
# 'lqz': '3/h'  #  一小时只能访问3次
# 'lqz': '3/m'  #  一分钟只能访问3次
# 'lqz': '3/s'  #  一秒种只能访问3次

4.2 SimpleRateThrottle源码分析

from rest_framework.throttling import SimpleRateThrottle
    -allow_request:必须有的,频率类,必须重写它
    -get_cache_key:没有具体实现,直接抛了异常,需要子类重写
    -wait:必须有的,返回一个数字,告诉前端,还有多长时间能访问
    -------都是为了给allow_request辅助的-----
    get_rate
    parse_rate
    throttle_failure
    throttle_success

      
# 返回True,不做限制
# 返回False,做限制

源码分析:

-1.allow_request
def allow_request(self, request, view):
    	# 要去init中看---> 第2步
        # self.rate=3/m
        if self.rate is None:  # 如果自己在频率类中写rate=5/m,我们就不需要写scope和配置文件了
            return True  # 没有self.rate不会做频率限制
        
        # self.rate有值,往下走
        # self.get_cache_key()是我们自己写的,返回了一个唯一的值(ip或者用户id)
        # get_cache_key返回了ip:192.168.1.19
        self.key = self.get_cache_key(request, view)
        if self.key is None: # 如果get_cache_key返回None,也不会做频率限制
            return True
        
        # 我们的代码:self.history = self.VISIT_RECORD.get(self.key, [])
        # self.cache 是缓存,保存数据的地方,相当于咱们的self.VISIT_RECORD
        self.history = self.cache.get(self.key, [])
        self.now = self.timer()  # 取出当前时间
        # self.duration ---> 第4步
        # self.duration:配置的1分钟访问3次,self.duration就是60
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        # self.num_requests ---> 第4步
        if len(self.history) >= self.num_requests: # '5/m',num_requests就是5
            # 可扩展性强,后期可以返回False之前记录个日志,用日志分析系统,红线警告,把访问异常的用户ip直接人工封掉
            return self.throttle_failure()  # 返回False
        # 返回True
        return self.throttle_success()  # ---> 第5步

-2 __init__    
def __init__(self):
    # 去频率类中反射rate属性或者方法,发现没有,返回None,执行下面的代码
	if not getattr(self, 'rate', None):
         # ---> 第3步
		self.rate = self.get_rate()  # 这个是 3/m
	self.num_requests, self.duration = self.parse_rate(self.rate)

    
-3 self.get_rate()
    def get_rate(self):
        # self.scope  我们自己写的字符串
        if not getattr(self, 'scope', None):  # 如果scope不写直接抛异常
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            # 有scope走这一步
            # 这个是重点
            # self.scope  我们自己写的字符串
            # self.THROTTLE_RATES配置文件中的我们配置的那个字典
            return self.THROTTLE_RATES[self.scope]  # 字典根据key取值,取出了咱们配置的 3/m
        except KeyError:  # 其余都是安全验证
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)
            

            

-4 __init__中的
# self.num_requests, self.duration = self.parse_rate(self.rate)---> self.rate是3/s
# 得到了:
# self.num_requests = 3
# self.duration = 60
- self.parse_rate(self.rate)
    def parse_rate(self, rate):  # rate是字符串'3/m'
        if rate is None:
            return (None, None)
        # 字符串切分:3/m---->num=3,period=m
        num, period = rate.split('/')
        # num_requests=数字3
        num_requests = int(num)
		# 翻译一下duration
        # d={'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
        # period[0]='m'
        # d[m] ---> 60
        # 防止输入时,是3/method、3/second
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        return (num_requests, duration)  # (3, 60)
    
    
    
-5 throttle_success
    def throttle_success(self):
        # 把当前时间插入到列表第0个位置
        self.history.insert(0, self.now)
        # 把访问的列表又放到缓存中
        self.cache.set(self.key, self.history, self.duration)
        return True

4.3 自定义频率类

'''
自定义的逻辑
    (1)取出访问者ip
    (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
    (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
    (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
    (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
'''

# 访问者 ip 字典,格式是{ip1:[时间4,时间3,时间2,时间1],ip2:[时间,时间],ip3:[当前时间]}


from rest_framework.throttling import BaseThrottle
import time


class MyThrottles(BaseThrottle):
    VISIT_RECORD = {}
    
    def __init__(self):
        self.history = None
        
    def allow_request(self, request, view):
        # self:频率类的对象
        # request:请求
        # view:视图类的对象
        
        #(1)取出访问者ip
        # print(request.META)
        ip = request.META.get('REMOTE_ADDR')
        # 获取当前时间
        ctime = time.time()

        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
        if ip not in self.VISIT_RECORD:
            # {ip地址作为key:[当前时间, ]}
            self.VISIT_RECORD[ip] = [ctime, ]
            return True
        
        # 获取当前ip的列表
        self.history = self.VISIT_RECORD.get(ip)

        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间
        # 只要列表中有值,并且当前时间减去最后一个元素大于60,就会执行while循环
        while self.history and ctime - self.history[-1] > 60:
            self.history.pop()
            
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        if len(self.history) < 3:
            self.history.insert(0, ctime)
            return True
        else:
            return False
        
    def wait(self):
        ctime = time.time()
        # 返回等待时间
        return 60 - (ctime - self.history[-1])

视图类中使用

from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
from .serializer import ReadSerializer
from .models import UserInfo

# 频率类
from .throttle import CommonThrottle


class UserView(ViewSet):
    throttle_classes = [CommonThrottle, ]

    def get(self, request):
        # 输出所有用户的用户名
        user_list = UserInfo.objects.all()
        ser = ReadSerializer(instance=user_list, many=True)
        return Response({'code': 100, 'msg': '查询成功', 'data': ser.data})


# 也可以在settings中全局设置

4.4 在中间件中限制频率

# 自定义中间件

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
import time
from django.middleware.security import SecurityMiddleware
from django.shortcuts import reverse


# 它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。

class MiddleThrottle(MiddlewareMixin):
    VISIT_RECORD = {}
    # path_name = ['/books/']  # 直接写
    path_reverse = [reverse('books')]  # 反向解析

    # 仿照其他中间件写init方法
    def __init__(self, get_response=None):
        super().__init__(get_response)
        self.history = None

    def process_request(self, request):
        # 只有这个路径才会走频率分析
        # if request.path in self.path_name:
        #     print(123)
        if request.path not in self.path_reverse:
            return

        # 此时的request是旧的request
        print(request)  # <WSGIRequest: GET '/books/'>
        # 请求来了后先走这里,写自定义频率类的逻辑

        # (1)取出访问者ip
        ip = request.META.get('REMOTE_ADDR')

        ctime = time.time()
        # (2)判断当前ip不在访问字典里,添加进去,表示可以访问,返回None,表示第一次访问,在字典里,继续往下走
        if ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[ip] = [ctime, ]
            print(self.VISIT_RECORD)
            return
        self.history = self.VISIT_RECORD.get(ip)

        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间
        while self.history and self.history[-1] < ctime - 60:
            self.history.pop()
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        if len(self.history) < 3:
            self.history.insert(0, ctime)
            print('访问:', self.VISIT_RECORD)
            return
        # (5)当大于等于3,说明一分钟内访问超过三次
        # 中间件拦截需要返回HttpResponse,这样之后的中间件一律不走了
        # 返回False验证失败
        else:
            print('访问超出3次:', self.VISIT_RECORD)
            msg = '需要等待%02d秒后才能访问' % self.wait()
            return HttpResponse(msg)

    def wait(self):
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

4.5 使用装饰器限制频率

装饰器

from django.shortcuts import HttpResponse
import time

VISIT_RECORD = {}


# 装饰器频率允许执行视图函数,不允许返回HttpResponse对象
def permissive(func):
    def inner(request, *args, **kwargs):
        request.history = None
        # (1)取出访问者ip
        ip = request.META.get('REMOTE_ADDR')
        ctime = time.time()
        # (2)判断当前ip不在访问字典里,添加进去,表示可以访问,执行视图类,表示第一次访问,在字典里,继续往下走
        if ip not in VISIT_RECORD:
            VISIT_RECORD[ip] = [ctime, ]
            print(VISIT_RECORD)
            res = func(request, *args, **kwargs)
            return res
        request.history = VISIT_RECORD.get(ip)

        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间
        while request.history and request.history[-1] < ctime - 60:
            request.history.pop()
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        if len(request.history) < 3:
            request.history.insert(0, ctime)
            print('访问:', VISIT_RECORD)
            res = func(request, *args, **kwargs)
            return res
        # (5)当大于等于3,说明一分钟内访问超过三次
        # 装饰器返回HttpResponse
        else:
            print('访问超出3次:', VISIT_RECORD)
            return HttpResponse('一分钟已经访问超过3次了')

    return inner

在视图类上使用

from django.utils.decorators import method_decorator


class BookView(APIView):
    @method_decorator(permissive)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)


    def get(self, request):
        book_list = Book.objects.all()
        ser = BookSerializer(instance=book_list, many=True)
        return Response({'code': 100, 'msg': '查询所有成功', 'data': ser.data})

五 源码

5.1 权限源码

# 继承了APIView,才有的---> 执行流程---> dispatch中----> 三大认证

    -入口:APIView的dispatch的大约497行self.initial(request, *args, **kwargs)
    
    -1 APIView的initial方法
        def initial(self, request, *args, **kwargs):
            # self是视图类的对象
            self.perform_authentication(request)  # 认证
            self.check_permissions(request)  # 权限
            self.check_throttles(request)  # 频率
        
        
    -2 读权限:APIView的方法self.check_permissions(request)
        def check_permissions(self, request):
            # permission_classes = [AdminPermission]
            
            # self.get_permissions()我们配置再视图类上permission_classes列表中的权限类,一个个的对象
            # self.get_permissions()  是  [AdminPermission(),权限类对象2,]
            for permission in self.get_permissions():  # **找这个**
                # 写的权限类,要重写has_permission方法
                # 猜permission是我们写的权限类的对象
                # self 是视图类的对象(BookView,PublisViwe)
                """
                我们写的权限中重写has_permission方法:
                def has_permission(self, request, view):
                两个参数:
                	request:新的request对象
                	view:BookView的对象
                """
                
                # permission.has_permission(request对象, 视图类的对象)
                # 权限类的对象调has_permission方法
                if not permission.has_permission(request, self): # 权限没通过
                    self.permission_denied(  # 权限拒绝,交给异常处理
                        request,
                        # self.message 错误文字
                        message=getattr(permission, 'message', None),
                        code=getattr(permission, 'code', None)
                    )
                # 权限通过,就不会走上边的if内部,还是接着for循环一个一个权限类对象,继续判断
                    
     -3 读 APIView--->self.get_permissions
        def get_permissions(self):
            # self是视图类的对象
            # 列表生成式
        	return [permission() for permission in self.permission_classes]
        
            # 翻译:
            l=[]
            # self.permission_classes就是自己写的视图类中配置的permission_classes
            for permission in self.permission_classes:
                # permission()是我们配置在权限列表中的一个个权限类的对象
                l.append(permission())
            return l

总结

# 记住:
    - 写的权限类,一定要写一个方法has_permission,返回True或False
    - 配置在视图类上
    
# 自己写的权限类中has_permission方法
    -view:你配置在哪个视图类身上就是哪个视图类的对象
    -如果没有权限的else中可以使用
        self.message
        self.code


# 我写的权限类可以不继承BasePermission,但是为什么约定俗成的都要继承BasePermission呢?
    -防止方法名写错,规范性问题
    -这种就是鸭子类型


# 鸭子类型
我们不需要继承共同的父类,只要我跟这个类有相同的方法和属性,就可以说我跟这个类是同一个类。

5.2 认证源码

# 继承了APIView,才有的---> 执行流程---> dispatch中----> 三大认证

    -入口:APIView的dispatch的大约497行self.initial(request, *args, **kwargs)
    
    -1 APIView的initial方法
        def initial(self, request, *args, **kwargs):
            # self是视图类的对象
            self.perform_authentication(request)  # 认证
            self.check_permissions(request)  # 权限
            self.check_throttles(request)  # 频率
        
    -2 self.perform_authentication(request)
        def perform_authentication(self, request):
            # 执行request.user,应该去哪里找?
            # request对象调个方法,去Request类中找user方法
            request.user  # 这是个方法,包装成了数据属性
            
    -3 Request类的user   220 行
        @property
        def user(self):
            # 这是在Request类中,self是Request类产生的对象
            if not hasattr(self, '_user'):  # 刚开始肯定是没有的
                with wrap_attributeerrors():
                    self._authenticate()
            # 认证只走完第一个后,request对象中就有了request._user
            return self._user
        
   -4 Request类的self._authenticate()   373 行
    def _authenticate(self):
        # self.authenticators 就是你写的认证类列表---> 列表推导式---> [LoginAuth(),]
        for authenticator in self.authenticators:  # ****找这个
            try:
                # user_auth_tuple = 我们写的每个认证类的对象调用authenticate方法(把request对象传入)
                # 对象调用方法,会把对象本身传入进去
                # 所以我们写的方法需要接收两个参数
                """
                我们写的认证类中重写的方法:
                def authenticate(self, request):
                """
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:  # 抛了异常被捕获了
                """
                我们写的主动抛异常是:AuthenticationFailed,这个异常继承了APIException,所以也可以被捕获。
                """
                # 有了异常这行代码还会继续走,程序没有崩掉
                self._not_authenticated()
                # 但是下面加了raise,就会直接显示错误,程序不会往下走了,不会执行第二个了,如果raise注释掉,就会执行下一个认证类
                raise
                

            if user_auth_tuple is not None:
                # 这里就限制了认证类authenticate方法中要么返回None,要么返回两个值,或者直接不ruturn 值(这个返回值就是None)
                # 如果返回了两个值,就会执行这句话
                # self是Request的对象
                # 所以只要认证通过request对象中就会有request.user和request.auth
                self.user, self.auth = user_auth_tuple
                # 认证类是可以有多个的,但只要哪一个类执行到这一步,直接返回了两个值,剩下的认证类就不走了
                # 所以,以后要写多个认证类,必须把返回两个值放到最后一个类中
                return
        self._not_authenticated()
	
    
    -5 authenticators是在Request类中的__init__ 里面,就是request对象的参数传入的
    -5.1 Request类中的__init__
        def __init__(self, request, parsers=None, authenticators=None):
        self.authenticators = authenticators or ()
        
    -5.2 APIView类的dispathch方法中
	# Request类初始化:在APIView---> dispathch中三大认证前面---> 包装了新的request
    request = self.initialize_request(request, *args, **kwargs)  # self是视图类的对象
    
    -5.3 APIView中的initialize_request方法
    # APIView中的initialize_request方法
        def initialize_request(self, request, *args, **kwargs):
            # self是视图类的的对象
        return Request(
            request,
            parsers=self.get_parsers(),
            # 视图类的对象调用了get_authenticators方法
            authenticators=self.get_authenticators(),  # ****
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
    
    -5.4 APIView中的get_authenticators方法
        def get_authenticators(self):
            # self是视图类的对象
            # 列表生成式
            # 循环authentication_classes,每一个认证类加括号得到对象,放到列表中
            # 返回值是: [LoginAuth(), 认证类对象2]
            return [auth() for auth in self.authentication_classes]

总结

 # 总结:
    1 认证类,必须写一个方法authenticate
    2 如果认证通过,可以返回None,也可也返回两个值,但是第一个值,尽量是当前登录用户,第二个值一般放token
    3 认证失败,抛异常AuthenticationFailed,继承了APIException,所以它能捕获
    
    
# 你拿不到request.uesr的原因可能是:
    1.根本没有走认证类
    2.你的认证类中没有返回两个值

5.3 频率源码

# 继承了APIView,才有的---> 执行流程---> dispatch中----> 三大认证

    -入口:APIView的dispatch的大约497行self.initial(request, *args, **kwargs)
    
    -1 APIView的initial方法
        def initial(self, request, *args, **kwargs):
            # self是视图类的对象
            self.perform_authentication(request)  # 认证
            self.check_permissions(request)  # 权限
            self.check_throttles(request)  # 频率
            
	-2 APIView中的check_throttles方法  
    def check_throttles(self, request):
        throttle_durations = []
        # self.get_throttles()就是在视图类上配置的频率类列表中的一个个频率类的对象
        for throttle in self.get_throttles():  # *** 找这个
            # 频率类对象点方法,所以调用我们重写的allow_request方法
            if not throttle.allow_request(request, self):
                # 调用我们重写wait()方法
                throttle_durations.append(throttle.wait())
                
		# throttle_durations内部是等待时间的列表
        if throttle_durations:
            durations = [
                duration for duration in throttle_durations
                if duration is not None
            ]
            
			# duration等待时间最大的
            duration = max(durations, default=None)
            self.throttled(request, duration)
            
            
            
	-3 APIView中的get_throttles方法  
	def get_throttles(self):
        # self是视图类的对象
        # self.throttle_classes是我们的类属性中写的一个个频率类
        return [throttle() for throttle in self.throttle_classes]

总结

    1 频率类,必须写一个方法allow_request
    2 如果频率通过,可以返回True。频率不通过,返回False
    3 可以重写wait()方法

六 补充

6.1 Django中的翻译函数

# 只要做了国际化,会自动翻译成,当前国家的语言
from django.utils.translation import gettext_lazy as _


_('hello')  # 括号内必须放英文

6.2 return和raise完全不一样

  • return 返回数据,返回一个异常程序也不会出错。
  • raise 直接抛异常,程序走到这里就不会往下走了
举例:
def text(a):
    if a == 10:
        return '是10'
    else:
        # raise Exception('不是10')  # 抛异常:Exception: 不是10
        return Exception('不是10')  # 使用return,直接返回Exception对象


res = text(9)
print(res, type(res))  # 不是10 <class 'Exception'>

6.3 Django中的访问不加/的错误

6.4 自动生成的路由

posted @ 2023-05-25 21:14  星空看海  阅读(15)  评论(0编辑  收藏  举报