- 九个视图子类
-
视图集
- 继承ModelViewSet类写五个接口
- 继承 ReadOnlyModelView编写2个只读接口
-
ViewSetMixin源码分析
- 查找as_view方法
- setattr修改对象的属性
- 任意命名视图类的方法
- 路由写法的三种情况
- 使用步骤
- 直接添加到urlpatterns列表
- 无法自动生成的路由
- 登录接口
九个视图子类
# 两个视图基类
1.APIView 2.GenericAPIView
APIView: renderer_classes响应格式类 parser_classes请求解析类 跟数据库解耦合
GenericAPIView:queryset数据集 serializer_class序列化类 跟数据库耦合
# 5个视图扩展类 (提供方法
ListModelMixin --> list --> 查询所有
RetrieveModelMixin --> retrieve --> 查询一个
CreateModelMixin --> create --> 新增一个
UpdateModelMixin --> update --> 修改一个
DestroyModelMixin --> destroy --> 删除一个
# 9个视图子类
继承关系公式: 视图子类 = n * 视图扩展类 + GenericAPIView
# 示例:
ListAPIView = ListModelMixin + GenericAPIView
RetrieveAPIView = RetrieveModelMixin + GenericAPIView
CreateAPIView = CreateModelMixin + GenericAPIView
...
RetrieveDestroyAPIView = RetrieveModelMixin + DestroyModelMixin + GenericAPIView
RetrieveUpdateDestroyAPIView = RetrieveModelMixin + UpdateModelMixin + DestroyModelMixin + GenericAPIView
'''
总结:9个视图子类都继承GenericAPIView
'''
使用视图子类写五个接口:这里上一节讲过,所以不再赘述。
## 路由
urlpatterns = [
path('books/', views.BookView.as_view(,
path('books/<int:pk>/', views.BookView.as_view(,
]
# 视图类
class BookView(ListCreateAPIView: # 查询所有,新增一个
queryset = Book.objects.all(
serializer_class = BookSerializer
class BookDetailView(RetrieveUpdateDestroyAPIView: # 新增一个,修改一个,删除一个
queryset = Book.objects.all(
serializer_class = BookSerializer
可以通过继承不同的视图类实现。
只要查询所有和删除一个,怎么写?
因为必须先查出来,再修改或删除,所以没有这个组合。
问题:
1.有两个get请求对应一个CBV
2.两个路由路径对应一个CBV
视图集
继承ModelViewSet类写五个接口
# 路由
urlpatterns = [
path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'},
path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'},
]
# 视图类
class BookView(ModelViewSet: # 查询所有,新增一个
queryset = Book.objects.all(
serializer_class = BookSerializer
查看ModelViewSet内部继承关系:
create(
、list(
、update(
、retrieve(
、destroy(
这些方法
但是我们请求来了,还是会调用视图类中的get(
、post(
、put(
、delete(
这些方法呀。
比如一个扩展子类:ListAPIViewModelViewSet内部居然没有写,这是怎么回事?
这是因为ModelViewSet继承的最后一个类GenericViewSet,这是一个魔法类,他重写了as_view。现在需要这样写:
-
books/<int:pk>/这个路由:
get请求 --执行--> retrieve方法
put请求 --执行--> updata方法
delete请求 --执行--> destroy方法
对于books/
这个路由:
get请求 --执行--> list方法
post请求 --执行--> create方法
继承 ReadOnlyModelView编写2个只读接口
# 路由
urlpatterns = [
path('books/', views.BookView.as_view({'get': 'list'},
path('books/<int:pk>/', views.BookView.as_view({'get': 'retrieve'},
]
# 视图类
class BookView(ReadOnlyModelViewSet: # 查询所有,新增一个
queryset = Book.objects.all(
serializer_class = BookSerializer
查看 readonlymodelview内部继承关系:
所以继承这个类就只能写两个只读接口:查询所有、查询一个
ViewSetMixin源码分析
ViewSetMixin是个魔法类,重写了as_view:
查找as_view方法
导致路由写法变了的原因是: ViewSetMixin
当请求来了之后,会执行ViewSetMixin类中的as_view方法的返回值。
# 请求来了,路由匹配成功---》get请求,匹配成功books,会执行 views.BookView.as_view({'get': 'list', 'post': 'create'}(------>读as_view【这个as_view是ViewSetMixin的as_view】
从路由层开始分析,根据继承属性一个一个找as_view方法(从左往右)
ListModelMixin,RetrieveModelMixin
,CreateModelMixin
,UpdateModelMixin
,DestroyModelMixin
这些方法中都没有as_view。
所以会进入到GenericViewSet:
所以会先执行ViewSetMixin的as_view(:
@classonlymethod
def as_view(cls, actions=None, **initkwargs:
# 如果没有传actions,直接抛异常,路由写法变了后,as_view中不传字典,直接报错
if not actions:
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view(` on a ViewSet. For example "
"`.as_view({'get': 'list'}`"
# 。其他代码不用看
def view(request, *args, **kwargs:
self = cls(**initkwargs
if 'get' in actions and 'head' not in actions:
actions['head'] = actions['get']
self.action_map = actions
for method, action in actions.items(:
handler = getattr(self, action
setattr(self, method, handler
return self.dispatch(request, *args, **kwargs
# 去除了csrf校验
return csrf_exempt(view
如果不给actions传参数,直接抛出异常。
也就是不给as_view(传字典,就会抛出异常。
as_view执行完后会返回内层函数view:(这里执行的view是去除了csrf校验的
# 路由匹配成功执行views.BookView.as_view({'get': 'list', 'post': 'create'}(----》本质执
行ViewSetMixin----》as_view----》内的view(---》代码贴过来
def view(request, *args, **kwargs:
#actions 是传入的字典--->{'get': 'list', 'post': 'create'}
self.action_map = actions
# 第一次循环:method:get,action:list
# 第一次循环:method:post,action:create
for method, action in actions.items(:
# 反射:去视图类中反射,action对应的方法,action第一次是list,去视图类中反射list方法
# handler就是视图类中的list方法
handler = getattr(self, action
# 反射修改:把method:get请求方法,handler:list
# 视图类的对象的get方法,变成了list
setattr(self, method, handler
return self.dispatch(request, *args, **kwargs #dispatch是APIView的
# 关于这里self.dipatch的说明
self.dipatch是APIView的dispatch
'''
self.dipatch --进行--> 封装新request, 执行三大认证 --调用--> django view的dispatch
'''
# 关于反射的总结
反射得到的是我们继承的List create方法
反射修改对象的属性 比如将get方法修改为存放list方法
最后的dispatch作用是获取你写的CBV类中的get方法(此时get方法 --> list方法)。
魔法类可以修改对象中的属性所指向的方法。
# 关于整体的总结:
-1 只要继承ViewSetMixin的视图类,路由写法就变了(重写了as_veiw
-2 变成需要需要传入字典映射方法:{'get': 'list', 'post': 'create'}
-只要传入actions,以后访问get就是访问list,访问post,就是访问create
-3 其他执行跟之前一样
-4 以后视图类类中的方法名,可以任意命名,只要在路由中做好映射即可【重要】
setattr修改对象的属性
实际上ModelViewSet中根本没有get方法,我们通过setattr给CBV的对象新增了一个get属性,里面存放的就是list方法。
而这个list方法又是通过反射在CBV的父类获取到的。所以就产生了这么神奇的效果。
我们也可以在自己的CBV中重写list方法,这样getattr获取到的就是我们重写的list方法,然后get请求来了之后,也会执行我们重写的这个list。重写list之后,建议使用super方法调用一下父类的list,这样就可以在父类list的基础上,新增一些功能。
# 示例:
def token_auth(func:
def inner(self, request, *args, **kwargs:
token = request.query_params.get('token'
token_exist = UserToken.objects.filter(token=token
if token_exist:
res = func(self, request, *args, **kwargs
return res
else:
return Response({'code': 100, 'msg': '请先登录'}
return inner
class BookView(ModelViewSet: # 针对 获取一个 修改一个 删除一个 接口添加token验证
queryset = Book.objects
serializer_class = BookSerializer
@token_auth
def retrieve(self, request, *args, **kwargs:
res = super(.retrieve(request, *args, **kwargs
return res
@token_auth
def update(self, request, *args, **kwargs:
res = super(.update(request, *args, **kwargs
return res
@token_auth
def destroy(self, request, *args, **kwargs:
res = super(.update(request, *args, **kwargs
return res
from rest_framework.viewsets包下的类
# from rest_framework.viewsets下有这几个类:
ViewSetMixin:魔法类,重写了as_view,只要继承他,以后路由写法变成了映射方法
ModelViewSet: 5个视图扩展类 + ViewSetMixin(魔法类 + GenericAPIView
ReadOnlyModelViewSet: 2个视图扩展类 + ViewSetMixin(魔法类 + GenericAPIView 只读的两个
ViewSet:ViewSetMixin(魔法类 + APIView
GenericViewSet:ViewSetMixin(魔法类 + GenericAPIView
# 重点
以后,你想继承APIView,但是想变路由写法【视图类中方法名任意命名】,要继承ViewSet
以后,你想继承GenericAPIView,但是想变路由写法【视图类中方法名任意命名】,要继承GenericViewSet
# 总结
只要想变路由,就要继承ViewSetMixin,但是ViewSetMixin不是CBV视图类,他没有list,create等方法,所以要配合APIView, GenericAPIView一起使用,所以会出现ViewSet,GenerucViewSet,帮助我们继承好了。
ViewSet: ViewSetMixin(魔法类 + APIView
GenericViewSet:ViewSetMixin(魔法类 + GenericAPIView
视图层大总结
# 1. 两个视图基类
-APIView,GenericAPIView
# 2. 5个视图扩展类,不是视图类,必须配合GenericAPIView
# 3. 9个视图子类,是视图类,只需要继承其中某一个即可
# 4. 视图集
-ModelViewSet:路由写法变了,只需要写两行,5个接口都有了
-ReadOnlyModelViewSet:路由写法变了,只需要写两行,2个只读接口都有了
-ViewSetMixin:不是视图类,魔法,重写了as_view,路由写法变了,变成映射了
views.BookView.as_view({'get': 'list', 'post': 'create'}
-ViewSet:ViewSetMixin+ APIView
-GenericViewSet:ViewSetMixin+ GenericAPIView
# 举例子:发送短信接口,视图类叫SendView,方法叫send_sms,路由配置变了
get--->send_sms
class SendView(ViewSet:
def send_sms(self,request:
任意命名视图类的方法
映射关系就行。
如何选择视图类
-
9个视图子类 + 魔法类
因为通常我们对一个数据库资源比如:user
对于这些数据资源,我们不一定会提供全部接口,很可能只会写其中的几个接口。
为什么要使用APIview?
其不跟数据库打交道:继承ViewSet
ViewSet = 魔法类 + APIView
因为APIView不需要配置queryset和序列化类
继承GenericViewSet会查数据库,这是一种资源的浪费。
所以跟数据库打交道:继承GenericViewSet
GenericViewSet = 魔法类 + GenericAPIview
路由系统
路由写法的三种情况
# drf 由于继承ViewSetMinxin类,路由写法变了
-原生+drf,以后的路由写法,可能会有如下情况(三种情况
-path('books/', views.BookView.as_view(
# 原生django写法
-path('books/', views.BookView.as_view({'get': 'list', 'post': 'create'}
# 魔法类路由写法
-自动生成 ---> 还有扩展
路由类的使用
使用路由类是为了自动生成路由。
# drf提供了两个路由类,继承ModelViewSet后,路由可以自动生成
# 使用步骤:
# 第一步:导入路由类
# 第二步,实例化得到对象(两个类,一般使用SimpleRouter)
# 第三步:注册:router.register('books', views.BookView, 'books'
# 第四步:在urlpatterns中注册,两种方式
-urlpatterns += router.urls
-include:path('/api/v1/', include(router.urls 方式多一些
# 底层实现:自动生成路由就
-本质是自动做映射,能够自动成的前提是,视图类中要有 5个方法的某要给或多个
get--->list
get---->retrieve
put---->update
post---->create
delete---->destory
-ModelViewSet,ReadOnlyModelViewSet可以自动生成
-9个试图子类+配合ViewSetMixin 才可以自动生成
-GenericAPIView+5个试图扩展类+配合ViewSetMixin 才能自动生成
使用步骤
第一步:导入路由类 使用simplerouter 就生成两个路由 使用DefaultRouter -->生成的路由更多
使用SimpleRouter(常用
from rest_framework.routers import SimpleRouter
router = SimpleRouter(
router.register('books', views.BookView, 'books'
urlpatterns = [
path('admin/', admin.site.urls,
]
urlpatterns += router.urls
关于router.register
:
第二个参数:该路由地址对应的视图类
第三个参数:相当于是一个路由别名
register(
方法有两个强制参数:
-
viewset
- 处理请求的viewset类。
prefix
- 用于此组路由的URL前缀。
- 注意,如果视图集不包括
queryset
属性,那么在注册视图集时必须设置base_name
。
base_name
- 用于创建的URL名称的基本名称。如果不设置该参数,将根据视图集的queryset
属性(如果有)来自动生成基本名称。
SimpleRouter会生成两个接口:
books/另一个是books/pk/
。
使用DefaultRouter
访问根,可以看到有哪些地址:
注册路由的两种方式
直接添加到urlpatterns列表
from rest_framework.routers import SimpleRouter, DefaultRouter
router = DefaultRouter(
router.register('books', views.BookView, 'books'
# router.register('api/v1/books', views.BookView, 'books'
urlpatterns = [
path('admin/', admin.site.urls,
]
urlpatterns += router.urls # 在这里添加
# router.urls也是一个列表:
[<URLPattern '^books/$' [name='books-list']>, <URLPattern '^books/(?P<pk>[^/.]+/$' [name='books-detail']>]
使用路由分发include
from rest_framework.routers import SimpleRouter
router = SimpleRouter(
router.register('books', views.BookView, 'books'
urlpatterns = [
path('admin/', admin.site.urls,
path('api/v1/', include(router.urls
]
自动生成路由底层实现
# 自动生成路由底层实现
-本质是自动做映射,能够自动生成的前提是,视图类中要有 5个方法的某个或多个
get--->list
get---->retrieve
put---->update
post---->create
delete---->destory
- ModelViewSet,ReadOnlyModelViewSet 可以自动生成
- 9个试图子类 + 配合ViewSetMixin 可以自动生成
- GenericAPIView + 5个试图扩展类+配合ViewSetMixin 可以自动生成
什么时候可以自动生成路由?
前提: 有list,create...等方法 有ViewSetMixin魔法类
所以如下这个组合用的多:
9个试图子类 + 配合ViewSetMixin
9个视图子类提供list
,create
...方法 ViewSetMixin反射进行对象属性替换,使得get对应list 。
action装饰器
原路由:send/
装饰器新增的路由:send/方法名/
# action 写在视图类的方法上,可以自动生成路由
# 使用步骤
- 1 写在视图类方法上
class SendView(ViewSet:
# methods指定请求方法,可以传多个
# detail:只能传True和False
-False,不带id的路径:send/send_sms/
-True,带id的路径:send/2/send_sms/
# url_path:生成send后路径的名字,默认以方法名命名
# url_name:别名,反向解析使用,了解即可
@action(methods=['POST'], detail=False
def send_sms(self, request:
# 以后看到的drf路由写法
后期,都是自动生成,一般不在urlpatterns 加入路由了
# 补充:
-1 不同请求方式可以使用不同序列化类
-2 不同action使用不同序列化类
class SendView(GenericViewSet:
queryset = None
serializer_class = '序列化类'
def get_serializer(self, *args, **kwargs:
if self.action=='lqz':
return '某个序列化类'
else:
return '另一个序列化列'
@action(methods=['GET'], detail=True
def send_sms(self, request,pk:
print(pk
# 手机号,从哪去,假设get请求,携带了参数
phone = request.query_params.get('phone'
print('发送成功,%s' % phone
return Response({'code': 100, 'msg': '发送成功'}
@action(methods=['GET'], detail=True
def lqz(self,request: # get
# 序列化类
pass
@action(methods=['GET'], detail=True
def login(self,request: # get
# 序列化类
pass
无法自动生成的路由
我们知道路由自动生成,是实现了请求(get和类中方法(list的对应。
如果我们在类中写list
,create
,...,updata
之外的方法呢?
还能自动生成这些方法的路由吗?
路由怎么写?
自动生成路由,只能映射到list,create...,但是我们需要执行send_sms,并且区分开原来的list方法,所以需要加drf提供的装饰器:
加上这个装饰之后,会新增一个路由send/send_sms/
。
不同action使用不同序列化类
class SendView(GenericViewSet:
queryset = None
serializer_class = '序列化类'
def get_serializer(self, *args, **kwargs:
if self.action=='lqz':
return '某个序列化类'
else:
return '另一个序列化列'
@action(methods=['GET'], detail=True
def send_sms(self, request,pk:
print(pk
# 手机号,从哪去,假设get请求,携带了参数
phone = request.query_params.get('phone'
print('发送成功,%s' % phone
return Response({'code': 100, 'msg': '发送成功'}
@action(methods=['GET'], detail=True
def lqz(self,request: # get
# 序列化类
pass
@action(methods=['GET'], detail=True
def login(self,request: # get
# 序列化类
pass
如何实现不同的方法,使用不同的序列化类?
在ViewSetMixin:
查看self.action:
认证组件前戏
登录接口
# 访问某个接口,需要登陆后才能访问
# 第一步:写个登录功能,用户表
-User表
-UserToken表:存储用户登录状态 [这个表可以没有,如果没有,把字段直接写在User表上也可以]
随机字符串可以放在user表,也可以放在usertoken表里。
用户删掉掉了之后,用户token没有存在的必要。所以可以使用级联删除。
user属性。(反向查询表名小写
登录接口是不需要使用序列化类的。
使用uuid模块生成随机字符串。
时间戳怎么重复?不同机器可能出现同一时间戳,及不同机器同一时间登录。
updata_or_create方法:
根据user去查,如果能查到,就把default里面的token给放进去。也就是如果有token就更新,如果没有就创建。
登录接口:
#### 表模型
class User(models.Model:
username = models.CharField(max_length=32
password = models.CharField(max_length=32
class UserToken(models.Model: # 跟User是一对一
token = models.CharField(max_length=32
user = models.OneToOneField(to='User', on_delete=models.CASCADE, null=True
# user :反向,表名小写,所有有user字段
### 路由
router.register('user', views.UserView, 'user' # /api/v1/user/login post 请求
# 视图类
#### 登录接口 自动生成路由+由于登录功能,不用序列化,继承ViewSet
from .models import User, UserToken
import uuid
class UserView(ViewSet:
@action(methods=['POST'], detail=False
def login(self, request:
username = request.data.get('username'
password = request.data.get('password'
user = User.objects.filter(username=username, password=password.first(
if user:
# 用户存在,登录成功
# 生成一个随机字符串--uuid
token = str(uuid.uuid4( # 生成一个永不重复的随机字符串
# 在userToken表中存储一下:1 从来没有登录过,插入一条, 2 登录过,修改记录
# 如果有就修改,如果没有就新增 (if 自己写
# kwargs 传入的东西查找,能找到,使用defaults的更新,否则新增一条
UserToken.objects.update_or_create(user=user, defaults={'token': token}
return Response({'code': '100', 'msg': '登录成功', 'token': token}
else:
return Response({'code': '101', 'msg': '用户名或密码错误'}