|
# 1、restful规范---10条
# 2、django上写符合restful规范的接口
# 3、drf写接口
# 4、APIView--》继承了原生View--》get,post方法
-(为什么get请求来了,就会执行get方法:原生View)
-路由配置:视图类.as_view()--->view(闭包函数)的内存地址
-请求来了,就会执行view(request,(路由有参数)分组分出的字段,默认传过来的字段)---》self.dispatch()--》处理的
-APIView重写了dispatch:
包装了request对象,三大组件(权限、认证、频率)、分页、解析器、响应器、全局异常,去掉了csrf认证
# 补充HTTP版本1.0和2.0区别 :可以实现一次请求带多个http请求过去
# 5、Request对象:request._request,多了request.data方法三种格式数据都能接收,重写了__getattr__,request.method--->去原生的request中拿
-前端传过来的数据从哪里取?
-地址栏里:request.GET/query_params(drf)
-请求体中的数据:request.data/POST(json合适解析不了)--->本质是request.body中取
-请求头中数据:request.META.get(HTTP_变成大写key),为什么有的数据放到请求头中,get请求时在body内带不过去,地址栏不安全,所以才放到头里面例如jwt的认证
-特殊取法:request.COOKIE/POST/GET比较常用,单独从request.body取了出来
# 6、Response对象--》封装了原生的HttpResponse,Response(data[这个放在响应体中是一个字典转给到前端为json格式],headers={},status=1/2/3/4/5开头的)
-data存放在响应体中
-header存放在响应头中
# 7、自己封装了Response对象
class APIResponse(Response):
def __init__(self,code=100,msg='成功',data=None,status=None,headers=None,**kwargs):
dic = {'code': code, 'msg': msg}
if data:
dic = {'code': code, 'msg': msg,'data':data}
dic.update(kwargs)
super().__init__(data=dic, status=status,headers=headers)
[用法]
return APIResponse(data={"name":'lqz'},token='dsafsdfa',aa='dsafdsafasfdee')
return APIResponse(data={"name":'lqz'})
return APIResponse(code='101',msg='错误',data={"name":'lqz'},token='dsafsdfa',aa='dsafdsafasfdee',header={})
# 8、序列化类:(第一重要)
-serializer
-写字段,字段名要和表的字段相对应,想不对应(source可以修改),有属性,read_only,write_only,max_length...
-SerializerMethodField必须配套一个get_字段名,返什么,前端看到的就是什么
authors=serializers.SerializerMethodField() #它需要有个配套方法,方法名叫get_字段名,返回值就是要显示的东西
def get_authors(self,instance):
# instance是book对象,为什么是book,在视图中要序列化放进来的对象
authors=instance.authors.all() # 取出所有作者
ll=[]
for author in authors:
ll.append({'name':author.name,'age':author.age})
return ll
-ModelSerializer
-class Meta:
表对应
取出字段(__all__,列表) fields=[非数据库字段],非数据库字段只参与序列化不参与反序列化,比如model中的property方法字段写了进来等
排除的字段(用的比较少,exclude)
extra_kwargs会给字段的属性
-重写某个字段(密码字段)
password=serializers.SerializerMethodField()
def get_password(self,instance):
return "***"
-校验:字段自己的校验,局部钩子,全局钩子
-只要序列化类的对象执行了is_valid(),这些钩子都会走,可以在钩子里写逻辑
-在表模型(model)中写方法,可以直接从上面面取出字段中直接写,但是不参与反序列化
-序列化多条(many=True):本质,ListSerializer内部套了一个个serialzer对象
-重写ListSerializer,让序列化对象和自己写的ListSerializer对应上,根据源码有一个字段class Meta中写_class=自己携带ListSerializer对象就可以了(了解)
-序列化类(instance,data,many,context={'request':request})
-视图函数中给序列化对象传递数据,使用context,传回来,,放进去直接使用序列化对象.context.get()
# 9、视图
-两个视图基类 APIView,GenericAPIView(继承APIView):
-涉及到数据库和系列化类的操作的时候,尽量用GenericAPIView
-不涉及到数据库操作用APIView
-5个视图拓展类(父类都是object)
CreateModelMixin:create
DestroyModelMixin:destory
ListModelMixin:list
RetrieveModelMixin:retrieve
UpdateModelMixin:update
-9个视图子类(GenericAPIView+视图拓展类的其中几个)
RetrieveUpdateDestroyAPIView
CreateAPIView
RetrieveAPIView
DestroyAPIView
RetrieveUpdateAPIView
ListCreateAPIView
UpdateAPIView
ListAPIView
RetrieveDestroyAPIView
-视图集
-ModelViewSet:5大接口都有了(内部有5个Mixin方法和GeneriticViewSet方法)
-ReadOnlyModelViewSet:获取一条和获取多条的接口,没有增删改,只有查
-GenericViewSet:ViewSetMixin+GenericAPIView
ViewSet:ViewSetMixin+APIView
ViewSetMixin:类重写了as_view,路由配置就变样了
# 10、路由
-基本配置:跟之前一样
-有action的,必须继承ViewSetMixin
-自动生成:Default和SimpleRouter
-四部曲:导入,实例化得到对象,注册多个,对象.urls(自动生成的路由)
1、from rest_framework.routers import SimpleRouter
2、router=SimpleRouter()
3、router.register('register',views.RegisterView,'register')
4、路由相加urlpatterns+=router.urls/include:path('',include(router.urls))
-视图类中自定义的方法,如何自动生成路由
-在自定义的方法上加装饰器(action)
-@action(methods=['GET','POST'],detail=True)
-两个参数method=[GET,POST],表示这两种请求都唔那个接受
-两个参数detail=True,表示生成带pk的连接
# 11、三大认证(第二重要)
-认证组件:校验用户是否登录
写一个认证类,继承BaseAuthentication类(规范必须重写authenticate方法),重写authenticate,内部写认证逻辑,认证通过返回两个值,第一个是user(可以是对象也可以是自己生成user对象但是不完整节省资源)
认证失败,抛出异常
-全局使用,局部使用,局部禁用(中括号空值)
-自定义认证类
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
# 认证逻辑,如果认证通过,返回两个值
#如果认证失败,抛出AuthenticationFailed异常
token=request.GET.get('token')
if token:
user_token=UserToken.objects.filter(token=token).first()
# 认证通过
if user_token:
return user_token.user,token[user_token.user这个就是为什么request.user可以拿到当前用户的对象,.user跨表到User用户对象里面去了]
else:
raise AuthenticationFailed('认证失败')
else:
raise AuthenticationFailed('请求地址中需要携带token')
------------------------------------------------------------------------------------------------------------------------
-权限:校验用户是否有权限进行后续操作
-写一个类继承BasePermission,重写has_permission,True和False(根据request.method方法判断)
-自定义权限类
class UserPermission(BasePermission):
def has_permission(self, request, view):
# 不是超级用户,不能访问
# 由于认证已经过了,request内就有user对象了,当前登录用户
user=request.user # 当前登录用户
# 如果该字段用了choice,通过get_字段名_display()就能取出choice后面的中文
print(user.get_user_type_display())
if user.user_type==1: 可以继续判断request.method方法在返回True
return True
else:
return False
-全局使用,局部使用,局部禁用(中括号空值)
------------------------------------------------------------------------------------------------------------------------
-频率:限制用户访问频次
-写一个类,不是直接继承BaseThrottle而是继承SimpleRateThrottle,重写get_cache_key,返回什么,就以谁做限制,scop=luffy字段,需要跟setting中的key对应 luffy:3/h(一小时访问三次1)
-需求:发送短信验证码接口,一分钟只能发送一次,局部使用,配置视类上
-自定义ip频率类
class IPThrottle():
#定义成类属性,所有对象用的都是这个
VISIT_DIC = {}
def __init__(self):
self.history_list=[]
def allow_request(self, request, view):
'''
#(1)取出访问者ip
#(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
#(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
#(4)判断,当列表小于3, 说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
#(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
'''
ip=request.META.get('REMOTE_ADDR')
ctime=time.time()
if ip not in self.VISIT_DIC:
self.VISIT_DIC[ip]=[ctime,]
return True
self.history_list=self.VISIT_DIC[ip] #当前访问者时间列表拿出来
while True:
if ctime-self.history_list[-1]>60:
self.history_list.pop() # 把最后一个移除
else:
break
if len(self.history_list)<3:
self.history_list.insert(0,ctime)
return True
else:
return False
def wait(self):
# 当前时间,减去列表中最后一个时间
ctime=time.time()
return 60-(ctime-self.history_list[-1])
# 12、解析器:前端传的编码格式,能不能解析(默认三种全配了,基本不需要修改),可能你携带上传文件接口,局部配置一下,只能传form-data,局部使用:[MultiPartParser]
# 13、响应器:响应的数据,是json还是带浏览器页面的那种(不需要配置),在settings中已经配置好了,根据user_agent判断不同的客户端类型返回不同的页面
# 14、过滤器:借助于第三方django-filter
-注册应用
-setting中配置DjangoFilterBackend或者局部配置
-filter_filed={'age','sex'}
# 15、排序
-全局或者局部配置rest_framework.filter,OrderingFilter
-视图类中配置:ordering_fields={'id','age'}
# 16、分页
-使用:
[继承了APIView的视图类中使用]
自定义分页:
class Mypage:
page_size=2
page_query_params='page'
page=Mypage()
# 在数据库中获取分页的数据
page_list=page.paginate_queryset(queryset对象,request,view=self)
# 对分页进行序列化
ser=BookSerializer1(instance=page_list,many=True)
# return Response(ser.data)
[继承了视图子类的视图中使用]
pagination_class = PageNumberPagination[(配置成自己重写的,可以修改字段)]
-[CursorPagination]
cursor_query_param:默认查询字段,不需要修改
page_size:每页数目
ordering:按什么排序,需要指定
-[LimitOffsetPagination]
default_limit 默认限制,默认值与PAGE_SIZE设置一直
limit_query_param limit参数名,默认’limit’
offset_query_param offset参数名,默认’offset’
max_limit 最大limit限制,默认None
[-PageNumberPagination:最常用的,需要在setting中配置page_size,四个参数]
page_size 每页数目
page_query_param 前端发送的页数关键字名,默认为”page”
page_size_query_param 前端发送的每页数目关键字名,默认为None
max_page_size 前端最多能设置的每页数量
# 17、全局异常
-写一个方法
def exception_handler(exc, context):
# 走drf原来的异常,原来的异常有一些处理
response = drf_exception_handler(exc, context)
# 我们自己处理,drf没有处理,丢给django的异常
if response is None:
if isinstance(exc, DatabaseError):#处理了一下数据库错误
response = Response({'detail': '数据库错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
else: # 全局异常统一这个处理
response = Response({'detail': '未知错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return response
-配置文件中配置(以后所有drf的异常,都会走到这里)
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
# 18 jwt(第一重要)
-是什么json web token 新的认证方式
-三段:头,荷载(用户信息),签名
-使用:最简单方式(在路由中配置)
-path('login/', obtain_jwt_token),
-自定制:(签发)
多方式登录,手动签发token(两个方法)
def validate(self, attrs):
print(self.context)
# 在这写逻辑
username=attrs.get('username') # 用户名有三种方式
password=attrs.get('password')
# 通过判断,username数据不同,查询字段不一样
# 正则匹配,如果是手机号
if re.match('^1[3-9][0-9]{9}$',username):
user=models.User.objects.filter(mobile=username).first()
elif re.match('^.+@.+$',username):# 邮箱
user=models.User.objects.filter(email=username).first()
else:
user=models.User.objects.filter(username=username).first()
if user: # 存在用户
# 校验密码,因为是密文,要用check_password
if user.check_password(password):
# 签发token
payload = jwt_payload_handler(user) # 把user传入,得到payload
token = jwt_encode_handler(payload) # 把payload传入,得到token
self.context['token']=token
self.context['username']=user.username
return attrs
else:
raise ValidationError('密码错误')
else:
raise ValidationError('用户不存在')
-自定制基于jwt的认证类(校验)
-取出token
-调用jwt提供的解析出payload的方法(校验是否过期,是否合法,如果合法,返回荷载信息)
-转成user对象
-返回
- 自定义校验token信息
class MyJwtAuthentication(BaseJSONWebTokenAuthentication): # 有payload字段转换成对象的操作user=self.authenticate_credentials(payload)
class MyJwtAuthentication(BaseAuthentication): # 基于这种方法写的没有把payload转换成字典的方法
def authenticate(self, request):
jwt_value=request.META.get('HTTP_AUTHORIZATION')
if jwt_value:
try:
#jwt提供了通过三段token,取出payload的方法,并且有校验功能
payload=jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
raise AuthenticationFailed('签名过期')
except jwt.InvalidTokenError:
raise AuthenticationFailed('用户非法')
except Exception as e:
# 所有异常都会走到这
raise AuthenticationFailed(str(e))
# 因为payload就是用户信息的字典
print(payload)
# return payload, jwt_value
# 需要得到user对象,
# 第一种,去数据库查
# user=models.User.objects.get(pk=payload.get('user_id'))
# 第二种不查库
user=models.User(id=payload.get('user_id'),username=payload.get('username'))
return user,jwt_value
# 没有值,直接抛异常
raise AuthenticationFailed('您没有携带认证信息')
# 19 RBAC:基于角色的权限控制(django默认的auth就是给你做了这个事),公司内部权限管理
对外的权限管理就是用三大认证
-用户表
-用户组表
-权限表
-用户对用户组中间表
-用户组对权限中间表
-用户对权限中间表
|
|