昨日回顾
# 1.序列化类钩子函数执行位置的源码
-反序列化的校验:字段自己的校验,局部钩子校验,全局钩子校验
# 2.drf的请求
-Request类的对象
-data:put,post提交的数据都在里面
-query_params:get请求提交的数据
-用起来跟django的request一样用
-指定可以解析的编码格式
-默认3种
-局部配置和全局配置
-优先级:视图类---》项目配置文件---》drf配置问题
-json模块是否执行反序列化bytes格式
-Python3.6以后才支持;之前的版本需要先encode编码成字符串,再load()转json格式
-视图类的方法中:self.request,就是当次请求的request
-好处:在视图类中,定义 其他不是请求方式的 函数时,
虽然没有request参数,但是也可以通过self.request取到当前请求的request
# 3.drf的响应
-Response类的对象
-data:返回的数据,字符串,列表,字典
-status:http响应的状态码
-header:http的响应头
-指定响应的格式
-默认两种:json,浏览器
-局部配置和全局配置
-原生django中添加数据到响应头
res = HttpResponse('ok')
res['name'] = 'egon' # 往响应头中写入 name=egon 的键值对
# 4.drf的视图类
-两个视图基类
-APIView:继承了django的View
renderer_classes :配置的响应类
parser_classes :配置的解析类
authentication_classes :配置的认证类
throttle_classes :配置的频率类
permission_classes :配置的权限类
-GenericAPIView:继承了APIViews
queryset: 当前表所有的数据
serializer_class: 序列化类
get_queryset(): 获取所有数据
get_serializer(): 获取序列化的类
get_object: 获取单个,路由分出pk
-源码解析
queryset = self.filter_queryset(self.get_queryset()) # 返回所有数据queryset对象
# lookup_url_kwarg就是pk,路由中有名分组分出来的pk
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
# {pk:4} 4 浏览器地址中要查询的id号http://127.0.0.1:8000/books6/4/
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
# 根据pk=4去queryset中get单个对象
obj = get_object_or_404(queryset, **filter_kwargs)
self.check_object_permissions(self.request, obj)
return obj
# 目前了解
filter_backends:过滤类
pagination_class:分页类
-5个视图扩展类(不是视图类)
ListModelMixin: 继承object,有个list方法,原理写在get中获取所有
CreateModelMixin: 有个create方法,原来的新增post中的代码
RetrieveModelMixin: 有个retrieve
UpdateModelMixin: 有个update
DestroyModelMixin: 有个destroy
-9个视图子类
ListAPIView: GenericAPIView+ListModelMixin+get方法
CreateAPIView: GenericAPIView+CreateModelMixin+post方法
ListCreateAPIView: GenericAPIView+ListModelMixin+CreateModelMixin+post方法+get方法
RetrieveAPIView:
UpdateAPIView:
DestroyAPIView:
RetrieveUpdateAPIView:
RetrieveDestroyAPIView:
RetrieveUpdateDestroyAPIView:
-视图集
-ViewSetMixin:继承object,重写了as_view,继承它及它的子类,路由写法就变了 .as_view({'get':'方法'})
-ViewSet: ViewSetMixin + APIView
-GenericViewSet: ViewSetMixin + GenericAPIView
-ReadOnlyModelViewSet: ViewSetMixin+GenericAPIView+ListModelMixin+RetrieveModelMixin
-ModelViewSet: ViewSetMixin+GenericAPIView+视图扩展类
今日内容
1.drf之路由
1.1 基本使用
# 自动生成路由:
# 第一步:导入
from rest_framework.routers import DefaultRouter, SimpleRouter
# 第二步:实例化得到对象
router = SimpleRouter() # 使用较多
# router = DefaultRouter() # 它自动生成的路由多一条,多个根路径 会显示所有可以访问的路径
# rest_framework自带的根路径页面,故需要进行rest_framewor设置注册
# 第三步:注册路由
# 第一个参数是路径(后面不用再跟 '/',会自动生成)
# 第二个参数是视图类(必须继承ViewSet及子类,且不用.as_view)
# 第三个参数是别名(反向解析使用,最好同名写上)
# 注意:在你的view接口若是没有queryset,不指定basename 会报错
router.register('books', views.BookView, basename='books')
router.register('publish', views.PublishView, basename='publish')
print(router.urls)
urlpatterns = [
path('admin/', admin.site.urls),
# 第四步:把自动生成的路由添加到总路由中(方式二:路由分发方式) 使用较多
path('', include(router.urls)),
]
# 第四步:把自动生成的路由添加到总路由中(方式一:列表相加)
# urlpatterns += router.urls
1.2 actions
# 针对自动生成路由时,action参数怎么指定设置
-使用action装饰器
# 导入action装饰器
from rest_framework.decorators import action
# 1.action装饰器的基本使用
class TestView(ViewSet):
'''
action参数
methods: ['get','post'] 做一个映射,就是哪些http的请求方式映射到当前方法
detail:True或False 表示多条还是单条 是否带查询条件(默认pk)
-False:test/login/
-True: test/pk/login # 注意pk的位置,是在两者之间
url_path:访问的路径,可以不写 默认以方法名作为路径 (记得要加上 url.py的路由前缀)
url_name: 别名 (该方法的别名,用作反向解析)
'''
# 此处表示:通过get请求访问这个路径'test/login' 就能触发login的执行
# @action(methods=['GET'],detail=False,url_path='login',url_name='login')
@action(methods=['GET'],detail=True)
def login(self, request,pk):
print(pk)
return Response('登录成功')
# 2.action装饰器的拓展使用
# 利用action属性,再重写get_serializer_class() 从而可以给不同的方法,使用不同的序列化类
-原理:使用action装饰器时,当前视图类的对象中就有action属性,可以通过该属性判断 当前请求会执行视图类中的哪个方法
class TestView(GenericViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get_serializer_class(self):
if self.action=='login':
return PublishSerializer
else:
return self.serializer_class
@action(methods=['GET'],detail=True)
def login(self, request,pk):
print(self.action)
self.get_serializer() # 此时该方法,使用的不是BookSerializer类,而是PublishSerializer类
return Response('登录成功')
# 路由
router.register('test', views.TestView, basename='testview')
urlpatterns = [
path('', include(router.urls)),
]
# 3.action装饰器的拓展使用--给同一个视图view下,不同的接口,添加不同的类设置
可用action装饰器 参数:6大类_classes=[对应的类]
例:throttle_classes=[SMSThrottle]
class MobileView(ViewSet):
@action(methods=['GET'],detail=False,throttle_classes=[SMSThrottle])
def send_sms(self, request):
pass
# 注意:action的第三项,可以直接替代第二项了
2.drf之认证功能
判断用户是否登录
# 例:某个接口登录后才能访问,就需要使用认证功能
2.1 登录功能
# 如果全局使用了认证类,那么登录也需要认证了,故登录接口需要局部禁用掉
class LoginView(ViewSet):
authentication_classes = [] # 局部配置:为空 表示不使用任何认证类
@action(methods=['POST'], detail=False)
def login(self, request):
# 1.获取前端传入的数据
name = request.data.get('name')
password = request.data.get('password')
# 2.根据数据,在User表查询
user=User.objects.filter(name=name,password=password).first()
if user:
# 3.生成一个随机字符串
# 通过伪随机数生成一个重复概率极低的字符串
token=str(uuid.uuid4())
# 4.把随机字符串存入UserToken表,如果存在,就只更新token值,如果不存在,就新增数据
# .update_or_create(defaults={更新的数据},查询条件) # 更新或新增数据
UserToken.objects.update_or_create(defaults={'token':token},user=user)
# UserToken.objects.create(token=token,user=user) # create() 会一直新增数据
return Response({'code':100,'msg':'登录成功','token':token})
else:
return Response({'code':101,'msg':'用户名或密码错误'})
2.2 认证类定义
# 自定义认证类,要继承BaseAuthentication,且重写authenticate
# 如果认证通过,返回两个值(user, token),如果认证失败,抛认证失败的异常:APIException或者AuthenticationFailed
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 = request.query_params.get('token') # 等于 token = request.GET.get('token')
# 校验token是否合法(是不是我给的): 根据token去UserToken表中查询是否存在,如果存在,就是登陆了,放行
user_token = UserToken.objects.filter(token=token).first()
if user_token:
return user_token.user, token # 当前登录用户(传给了当前请求对象:request.user)和token值
else:
raise AuthenticationFailed('token不合法或没有传token') # 认证异常
2.3 认证类使用
# 可以有多个认证,从左到右依次执行
# 局部使用(在视图类中加入)
# 认证类的局部配置
from app01.auth import LoginAuth
class PublishView(ModelViewSet):
# 局部配置
authentication_classes = [LoginAuth]
# 局部禁用
authentication_classes=[]
# 全局使用
# 认证类的全局配置
REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.auth.LoginAuth",]
}
3.drf之权限功能
判断登录用户是否有相应的权限
# 例:某个接口需要超级用户的权限,就需要使用权限功能
注意:权限类一定是在认证类之后执行(根据源码执行顺序),那么走到权限类代码,就一定是认证通过了
3.1 权限类定义
# 继承BasePermission,重写has_permission,如果有权限,就返回True,没有权限就返回False
from rest_framework.permissions import BasePermission
class PermissionUser(BasePermission):
# message = '你没有访问的权限!'
def has_permission(self, request, view):
# 如果有权限,返回True
if request.user.user_type == 1:
return True # 超级用户允许访问
else:
self.message = '你没有访问的权限!'
return False
# 权限认证失败,返回自定义的中文
-在权限类中配置message属性即可(给对象,类都可以)
3.2 权限类使用
# 局部和全局使用
# 局部配置
from app01.auth import LoginAuth
class PublishView(ModelViewSet):
# 权限类:publish的5个接口,必须超级用户才能访问
permission_classes = [PermissionUser]
# 全局配置
REST_FRAMEWORK={
"DEFAULT_PERMISSION_CLASSES":["app01.auth.PermissionUser",]
}
3.3 内置权限(了解)
# 演示一下内置权限的使用:IsAdminUser,控制是否对网站后台有权限的人
# 1 创建超级管理员
# 2 写一个测试视图类
from rest_framework.permissions import IsAdminUser
from rest_framework.authentication import SessionAuthentication
class TestView3(APIView):
authentication_classes=[SessionAuthentication] # 默认的认证类
permission_classes = [IsAdminUser]
def get(self,request,*args,**kwargs):
return Response('这是测试数据,超级管理员可以看')
# 3 超级用户登录到admin,再访问test3就有权限
# 4 正常的话,普通管理员,没有权限看(判断的是is_staff字段是否为True)
猴子补丁
# 程序运行的过程中,动态的替换对象的属性或方法
自定义封装response对象
# 自己封装一个CommonResponse对象,使用方法如下
return CoomonResponse('100','成功',boo_ser.data)
return CoomonResponse('101','验证失败',boo_ser.errors)
# 1. 自已2b版:太固定了很不好,没办法传原来response的其他参数,使用时也必须.get_dict()
from rest_framework.response import Response
class CommonResponse(Response):
def __init__(self, status, msg, data):
self.status = status
self.msg = msg
self.data = data
super().__init__(self.get_dict)
@property
def get_dict(self):
return {'status': self.status, 'msg': self.msg, 'data': self.data}
# 2.老师通用版
from rest_framework.response import 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['data'] = data
dic.update(kwargs) # 可以灵活的添加,需要返回的键值对
super().__init__(data=dic, status=status, headers=headers)
# 注意:因为ModelViewSet内部的各接口方法,返回的是rest_framework的response对象
故我们也可以将 重写方法的公共类(APIResponseView), 写在该文件里
作业
# 1 图书,出版社,作者 每个的5个接口都写好
-返回的格式都带 {code:100,msg:'成功',data:}
# 解决方案1:
# 1.自定义一个公共类(继承ModelViewSet),重写父类的方法(将源码拿出来),最后返回自定义的response对象
class APIResponseView(ModelViewSet):
def list(self, request, *args, **kwargs):
return APIResponse(data=serializer.data)
def create(self, request, *args, **kwargs):
return APIResponse(data=serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def retrieve(self, request, *args, **kwargs):
return APIResponse(data=serializer.data)
def update(self, request, *args, **kwargs):
return APIResponse(data=serializer.data)
def destroy(self, request, *args, **kwargs):
return APIResponse(status=status.HTTP_204_NO_CONTENT)
# 2.各视图类接口就都继承公共类 (不直接继承ModelViewSet)
class BookView(APIResponseView)
class PublishView(APIResponseView):
class AuthorView(APIResponseView):
# 解决方案2:
# 1. 可以直接将公共类(APIResponseView),也封装到自定义封装response对象的文件中
class APIResponse(Response):
class APIResponseView(ModelViewSet):
# 2.各视图类接口就都继承公共类 (不直接继承ModelViewSet)
class BookView(APIResponseView)
class PublishView(APIResponseView):
class AuthorView(APIResponseView):
# 2 写一个登录功能(action)
# 为了防止每次都要输入用户名和密码,登录之后返回前端一个token,下次判断是否传token,就不用重新登录
# 故 后端需要存储 token表
# 3 除了登录,所有接口必须登录才能访问----认证功能
全局使用,局部禁用(login禁用)
# 4 作者5个接口,必须超级用户才能访问----权限功能
# 5 使用simplerouter自动生成路由
# 6 继承ModelViewSet,获取所有的,只获取前5条 (就是重写内部list方法)
# Create your views here.
class BookView(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
# 重写父类list() 只获取所有的前5条
def list(self, request, *args, **kwargs):
# 只获取所有的前5条
queryset = self.filter_queryset(self.get_queryset())[:5]
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return APIResponse(data=serializer.data)