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'>