DRF
#1.表关系
OneToOneField本质其实就是foreignkey加unique
on_delete :
CASCADE:这就是默认的选项,级联删除,你无需显性指定它。
PROTECT: 保护模式,如果采用该选项,删除的时候,会抛出ProtectedError错误。
SET_NULL: 置空模式,删除的时候,外键字段被设置为空,前提就是blank=True, null=True,定义该字段的时候,允许为空。
SET_DEFAULT: 置默认值,删除的时候,外键字段设置为默认值,所以定义外键的时候注意加上一个默认值。
SET(): 自定义一个值,该值当然只能是对应的实体了
字段建索引,字段唯一
联合索引联合唯一
日期类型 auto_now_add 记录创建时间
auth_now 修改时间
#2.book
单条查询 多条查询 :
写在一个视图类,配路由时候,转到这 取出kwargs判断有没有pk 有就是单条 没有就是多条
单条新增 多条新增:
直接把内容反序列化然后判断is_valid一下,调save保存 在return 一个Response一个对象
单条修改 多条修改:
调用序列化器时候list_serializer_class = MyListSerializer 会走自己定义的MyListSerializer方法里面的update
单条删除,多条删除:
要注意删除修改(is_delete)
#3.频率
自定义频率 (限制ip、id)
-第一步:写一个类,继承SimpleRateThrottle,重写get_cache_key
-第二步:get_cache_key返回什么就以什么做限制,
必须写类属性 scope='字符串'限制了setting里
-第三步:配置文件中配置
'DEFAULT_THROTTLE_RATES': {
'字符串': '3/m', # key:ip_1m_3 对应频率类的scope属性, value: 3/m 一分钟访问3次
#4、分页
PageNumberPagination,
LimitOffsetPagination
CursorPagination
1.频率SimpleRateThrottle源码分析
#先寻找allow_request
if self.rate is None:
return True
#在进入他里面的__init__
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
通过反射去get_rate里面获取到了return self.THROTTLE_RATES[self.scope] 配置文件找key=scope的对应的value(3/min),回到init self.rate=3/min,在通过parse_rate把rate分别给num_requests和duration
再次回到allow_request 获取get_cache_key =key 以什么做限制,在通过获取self.history=self.cache.get(限制条件,[]) 这句话意思是能取到值就是history 取不到就是空列表
然后再通过self.time() 获取当前时间 time里面是time.time的内存地址
然后 判断history有没有值并且当前时间now减去最后一次的时间-history[-1]大于或者等于>=duration pop出去
然后又判断了一下history长度 是否大于num_requests 这个是settings配置的3/min 的3如果大于 直接走throttle_failure 方法 直接return了false
最下面retrun 这个throttle_success
def throttle_success(self):
self.history.insert(0, self.now) #当前时间加入到history
self.cache.set(self.key, self.history, self.duration) #缓存加入
return True
2.自动生成接口文档
#给前端写一个文档
#安装
1.pip3 install coreapi
2 配置路径 主路由
from rest_framework.documentation import include_docs_urls
urlpatterns = [
...
path('docs/', include_docs_urls(title='站点页面标题'))
]
3.文档说明 视图类:自动生成接口文档的是继承APIView及其子类的视图
1)单一方法的视图,可直接使用类视图的文档字符串,如
class BookListView(generics.ListAPIView):
"""
返回所有图书信息.
"""
2)包含多个方法的视图,在类视图的文档字符串中,分开方法定义,如
class BookListCreateView(generics.ListCreateAPIView):
"""
get:
返回所有图书信息.
post:
新建图书.
"""
3)对于视图集ViewSet,仍在类视图的文档字符串中封开定义,但是应使用action名称区分,如
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
"""
list:
返回图书列表数据
retrieve:
返回图书详情数据
latest:
返回最新的图书数据
read:
修改图书的阅读量
"""
#如果报错 settings加入
#AttributeError: 'AutoSchema' object has no attribute 'get_link'
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
# 新版drf schema_class默认用的是rest_framework.schemas.openapi.AutoSchema
}
3.jwt
3.1jwt简单介绍
1.什么是集群: 相同代码部署在多台机器上
2.加密
"""
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
"company": "公司信息",
...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
"user_id": 1,
...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
"head": "头的加密字符串",
"payload": "体的加密字符串",
"secret_key": "安全码"
}
"""
3.校验
"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""
4.drf项目的jwt认证开发流程(重点)
"""
1)用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies中
2)校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
注:登录接口需要做 认证 + 权限 两个局部禁用
"""
第三方写好的:django-rest-framework-jwt
3.2安装jwt
pip3 install djangorestframework-jwt -i https://pypi.douban.com/simple/
3.3使用jwt
1.models 扩展user表 继承AbstractUser 数据库没迁移之前的项目
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
phone=models.CharField(max_length=32)
#image依赖于pillow模块
icon=models.ImageField(upload_to='icon')
2.配置文件设置
#一定不要忘记 app名字.表名小写
AUTH_USER_MODEL='app01.user' 后面指定的是APP名字
#配置图片路径
MEDIA_URL='/media/'
#配置图片文件夹
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
3.数据库迁移
makemigrations
migrate
4.创建超级用户
creatsuperuser
5.简单快速使用(路由层配置)
from rest_framework_jwt.views import ObtainJSONWebToken,VerifyJSONWebToken,\
RefreshJSONWebToken,obtain_jwt_token
#基类:JSONWebTokenAPIView
#ObtainJSONWebToken,VerifyJSONWebToken,RefreshJSONWebToken都继承了JSONWebTokenAPIView
urlpatterns = [
path('admin/', admin.site.urls),
#下面的这两个路由一个意思
# path('login/', ObtainJSONWebToken.as_view()),
path('login/', obtain_jwt_token),
]
6.其他设置
settings app里面要加入
'rest_framework_jwt'
然后下面全局配置认证
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':['rest_framework_jwt.authentication.JSONWebTokenAuthentication']
}
7.写一个认证类
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class BookView(APIView):
def get(self,request):
#局部配置
# 只写这一个认证类,如果没有携带token,并不会限制,需要搭配一个权限类,来完成登录认证
authentication_class=[JSONWebTokenAuthentication,]
# 权限类
permission_classes = [IsAuthenticated,]
return Response('ok')
8测试
3.4jwt源码部分
3.5drf-jwt修改签发响应格式
# 写一个函数,函数的返回值,就是响应的格式
# 在配置文件中配置一下
def jwt_response_payload_handler(token, user=None, request=None):
return {
'code': 100,
'msg': '登录成功',
'token': token,
'username':user.username
}
在配置文件中配置
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.common.jwt_response_payload_handler',
}
3.6drf-jwt自定义用户表签发token
# 自定义签发token
from app01.models import User
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class Login(APIView):
def post(self, request):
response = {'code': 101, 'msg': '用户名或者密码错误'}
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username, password=password).first()
if user:
# 登陆成功签发token 去路由层找obtain_jwt_token,最终找到了JSONWebTokenAPIView里的post方法
# 登录成功,签发token,通过当前登录用户获取荷载(payload)
payload = jwt_payload_handler(user)
# 通过payload生成token串(三段:头,payload,签名)
token = jwt_encode_handler(payload)
# 有这个用户状态码信息修改成正常的
response['code'] = 100
response['msg'] = '登陆成功—自定义的'
response['token'] = token
return Response(response)
#自定义签发token,无法使用自带的认证类,因为自带的认证类return的user是auth_user,
#所以需要自己写认证类
3.7自定制认证类
# 自定义认证类
from rest_framework.authentication import BaseAuthentication
from rest_framework_jwt.settings import api_settings
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
import jwt
from rest_framework.exceptions import AuthenticationFailed
from .models import User
class MyJwtAuthentication(BaseAuthentication):
def authenticate(self, request):
#1.取出token
token=request.META.get('HTTP_TOKEN')
#2.验证token是否合法
if token:
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise AuthenticationFailed('签名过期')
except jwt.DecodeError:
raise AuthenticationFailed('签名认证失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('未知错误')
print(payload)
#认证一次就需要走一次数据库 效率低,
user=User.objects.get(pk=payload['user_id'])
#效率上来了 但是可能会有问题,以后request.user是用户id 如果想用其他属性,
# 需要通过id查找对象 在点属性
# user=User(id=payload['user_id'],username=payload['username'])
return (user, token)
else:
raise AuthenticationFailed('没有携带token')
# 全局使用,在配置文件中配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':['app01.auth.JwtAuthentication'], # 全局生效
}
# 全局使用后,局部禁用
class LoginView(APIView):
authentication_classes = []
# 注意:配置的所有认证,权限,频率。。。优先用视图类自己的,再用配置文件的,最后用drf内置的
# 补充:token过期时间很快,改改过期时间,配置7天过期
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.common.jwt_response_payload_handler',
# 过期时间7天
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
}