一、APIView详解
'''
# 1)安装drf:pip3 install djangorestframework
# 2)settings.py注册app:INSTALLED_APPS = [..., 'rest_framework']
# 3)基于cbv完成满足RSSTful规范的接口
'''
# 视图层
from rest_framework.views import APIView
from rest_framework.response import Response
user_list = [{'id': 1, 'name': 'Bob'}, {'id': 2, 'name': 'Tom'}]
class Users(APIView):
def get(self, request, *args, **kwargs):
return Response({
'status': 0,
'msg': 'ok',
'results': user_list
})
def post(self, request, *args, **kwargs):
# request对formdata,urlencoded,json三个格式参数均能解析
name = request.data.get('name')
id = len(user_list) + 1
user = {'id': id, 'name': name}
user_list.append(user)
return Response({
'status': '0',
'msg': 'ok',
'results': user
})
# 路由层
from app import views
urlpatterns = [
url(r'^users/', views.Users.as_view()),
]
1、(APIView) as_view
'''
APIView是继承自View类
APIView内部重写了as_view方法,在该方法的内部,进行了以下几个步骤
1. 调用父类View的as_view方法
2. 存入当前类、参数到view中
3. 调用装饰器csrf_exempt保障后续所有操作无需csrf校验(使用自己的校验方式)
'''
@classmethod
def as_view(cls, **initkwargs):
# 调用父类View的as_view方法,并传入参数(先忽略此参数影响)
view = super().as_view(**initkwargs)
'''
python中一切皆对象,因此可以将view当作对象一般,存入属性或方法
将当前类存入view.cls中
将当前参数存入view.initkwargs中
'''
view.cls = cls
view.initkwargs = initkwargs
'''
对于@csrf_exempt装饰器,我们可以直接添加在视图函数上面,作为不被csrf校验的依据,装饰器的本质原理,就是将被装饰函数作为参数传递给装饰器函数罢了
因此,我们可以直接手动使用装饰器函数,传入需要装饰的函数作为装饰器函数的参数,从而达到@csrf_exempt一样的功能
以下语法,就是对后续的所有执行动作,均不需要csrf校验
'''
return csrf_exempt(view)
2、(View) as_view
'''
上述操作,依然调用了父类View的as_view方法,我们来观察父类as_view方法内部实现
1. 将当前类进行实例化
2. 判断对象中是否存在get方法且不存在head方法,如果是,head则使用get方法
3. 将request,args,kwargs使用对象属性存储
4. 如果对象中不存在request,则抛出异常
5. 调用当前对象中的dispatch方法,并传入request参数
'''
'''
classonlymethod本质就是继承于classmethod
我们说python中一切皆对象,此刻classonlymethod就是继承自classmethod,并在其基础功能上增加了一些功能,原理依然是装饰此函数为类方法
'''
@classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
# 将当前类进行实例化
self = cls(**initkwargs)
# 判断对象中是否存在get方法且不存在head方法,如果是,head则使用get方法
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
'''
setup是将request,args,kwargs使用对象属性存储
self.request = request
self.args = args
self.kwargs = kwargs
'''
self.setup(request, *args, **kwargs)
# 如果对象中不存在request,则抛出异常
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
# 调用当前对象中的dispatch方法,并传入request参数
return self.dispatch(request, *args, **kwargs)
# 存储当前类与initkwargs
view.view_class = cls
view.view_initkwargs = initkwargs
update_wrapper(view, cls, updated=())
update_wrapper(view, cls.dispatch, assigned=())
# 返回view函数
return view
3、(APIView) dispatch
'''
在观察了父类View的as_view方法后,我们发现其内部最终是调用dispatch方法(关键)
对于当前类,最近的dispatch方法,即是APIView中的dispatch方法,我们进行下一步研究:
1. 加工原生request
2. 增添三大认证模块
3. 利用反射调用请求方法的对应方法
4. 使用异常模块收集异常
5. 利用渲染模块,渲染最终数据
'''
def dispatch(self, request, *args, **kwargs):
# 对当前接收参数使用对象属性存储
self.args = args
self.kwargs = kwargs
# 将原生request,传入至initialize_request方法,进行加工后,返回加工后的request
request = self.initialize_request(request, *args, **kwargs)
# 此刻对象属性request存储的为加工后的reqeust
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 该方法内部包含三大认证模块
self.initial(request, *args, **kwargs)
'''
反射:
1. 获取当前对象中的对应方法(get、post、put、patch、delete....)
2. 调用方法,并传入参数
判断当前请求方式,是否存在于http_method_names属性中的方式
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
如果存在,则调用对象中的对应方法,否则使用默认方法http_method_not_allowed
'''
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# 响应模块:执行对应方法,并传入 加工后的request参数
response = handler(request, *args, **kwargs)
except Exception as exc:
# 异常模块:如果出现异常,则调用异常方法,返回对应的异常信息,而不是前端直接报错
response = self.handle_exception(exc)
# 渲染模块:将数据进行加工后,返还给前端精美的页面
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
A、(View) initialize_request
'''
我们进一步查看initialize_request方法内部原理:
1. 返回封装后的request对象
2. Request对象内包含原生request,以及其他功能
'''
def initialize_request(self, request, *args, **kwargs):
'''
parser_context返回一个列表
{
'view': self, 存储当前对象
'args': getattr(self, 'args', ()), 获取当前对象args属性值
'kwargs': getattr(self, 'kwargs', {}) 获取当前对象kwargs属性值
}
'''
parser_context = self.get_parser_context(request)
# 返回Request对象,传入初始化参数
return Request(
request, # 原生request
parsers=self.get_parsers(),
'''
return [parser() for parser in self.parser_classes]
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
'''
authenticators=self.get_authenticators(),
'''
return [auth() for auth in self.authentication_classes]
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
'''
negotiator=self.get_content_negotiator(),
'''
if not getattr(self, '_negotiator', None):
self._negotiator = self.content_negotiation_class()
return self._negotiator
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
'''
parser_context=parser_context # 存储parser_context
)
# Request对象
self._request = request # 原生request
self.parser_context = parser_context
self.parser_context['request'] = self
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
B、(APIView) initial
'''
initial内部存在三个,类似于钩子函数功能的组件:
1. 认证组件
2. 权限组件
3. 频率组件
组件全部通过,才会继续正确执行,否则抛出403异常
'''
def initial(self, request, *args, **kwargs):
'''
认证组件:校验用户 = 游客、合法用户、非法用户
游客:代表校验通过,直接进入下一步校验(权限校验)
合法用户:代表校验通过,将用户存储在request.user中,再进入下一步校验(权限校验)
非法用户:代表校验失败,抛出异常,返回403权限异常结果
'''
self.perform_authentication(request)
'''
权限组件:校验用户权限 - 必须登录、所有用户、登录读写游客只读、自定义用户角色
认证通过:可以进入下一步校验(频率认证)
认证失败:抛出异常,返回403权限异常结果
'''
self.check_permissions(request)
'''
频率组件:限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s)
没有达到限次:正常访问接口
达到限次:限制时间内不能访问,限制时间达到后,可以重新访问
'''
self.check_throttles(request)
二、GenericAPIView
'''
GenericAPIView是继承自APIView,封装后更加适合使用的一个视图类
'''
from rest_framework.generics import GenericAPIView
class xxx(GenericAPIView):
'''
首先,继承GenericAPIView必须写入以下两个属性
queryset:传入queryset对象
serializer_class:传入序列化器
'''
queryset = None
# 例如:queryset = models.xxx.objects.all()
self.get_queryset()
'''
def get_queryset(self):
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
return queryset
源码研究发现,queryset可以填写:(源码会判断加没加all,自动加all)
models.xxx.objects
models.xxx.objects.all()
使用get_queryset()就相当于使用queryset
'''
serializer_class = None
# 例如:serializer_class = ZzwSerializer(定义的筛选器类)
self.get_serializer(instance=None, data=empty, **kwargs, many=True, context={})
'''
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs.setdefault('context', self.get_serializer_context())
return serializer_class(*args, **kwargs)
def get_serializer_class(self):
return self.serializer_class
def get_serializer_context(self):
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
源码研究发现:
1. 使用serializer_class()就相当于使用serializer_class
2. 且serializer_class()会在原基础上增加三个参数,赋给context
3. 将get_serializer当作原来的序列化器,直接赋予序列化器的参数,即可使用
'''
self.get_object()
'''
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
self.check_object_permissions(self.request, obj)
return obj
1. 无需传入参数,直接获取当前pk的模型表数据对象
2. 对于获取指定pk模型表数据、修改指定pk模型表数据、删除指定pk模型表数据,直接可以使用self.get_object()来代替
'''
三、五个视图扩展类(增删改查)
1、ListModelMixin(获取所有)
from rest_framework.mixins import ListModelMixin
class ListModelMixin:
# 标准获取模型表所有数据
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
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 Response(serializer.data)
2、CreateModelMixin(增加)
from rest_framework.mixins import CreateModelMixin
class CreateModelMixin:
# 直接一个完整的添加数据流程
def create(self, request, *args, **kwargs):
# 1. 实例化序列化器,反序列化+校验用户传来的数据
serializer = self.get_serializer(data=request.data)
# 2. 判断数据是否符合校验规则
serializer.is_valid(raise_exception=True)
# 3. 调用perform_create方法,内部调用save()实现数据添加操作
self.perform_create(serializer)
# 4. 额外添加响应头
headers = self.get_success_headers(serializer.data)
# 5. 返回给前端数据
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
3、UpdateModelMixin(修改)
from rest_framework.mixins import UpdateModelMixin
class UpdateModelMixin:
# 标准修改操作
def update(self, request, *args, **kwargs):
# partial存在则删除,并返回被删除的value
# 如果partial不存在,则设置value为False,并返回False
partial = kwargs.pop('partial', False)
# 1. 获取模型表指定pk的单条数据对象
instance = self.get_object()
# 2. 实例化序列化器,并传入 数据对象 和 用户传来的信息
serializer = self.get_serializer(instance, data=request.data, partial=partial)
# 3. 判断反序列化+校验是否通过,raise_exception指定,当未通过抛出异常ValidationError
serializer.is_valid(raise_exception=True)
# 4. 正常校验通过,则调用perform_update方法,内部执行修改操作
self.perform_update(serializer)
·
# 5. 如果数据库记录对象,存在_prefetched_objects_cache字段,则设置为空字典
if getattr(instance, '_prefetched_objects_cache', None):
instance._prefetched_objects_cache = {}
# 6. 返回修改后的数据
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
# 设置partial为True
kwargs['partial'] = True
# 调用update方法
return self.update(request, *args, **kwargs)
四、DestroyModelMixin(删除)
from rest_framework.mixins import DestroyModelMixin
class DestroyModelMixin:
# 标准删除操作
def destroy(self, request, *args, **kwargs):
# 1. 获取模型表指定数据对象
instance = self.get_object()
# 2. 调用perform_destroy方法,内部直接删除模型表指定数据对象
self.perform_destroy(instance)
# 3. 返回空数据,状态码为204
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
五、RetrieveModelMixin(获取单个)
from rest_framework.mixins import RetrieveModelMixin
class RetrieveModelMixin:
# 标准返回模型表中指定pk的单条记录
def retrieve(self, request, *args, **kwargs):
# 1. 获取模型表中指定pk的单条记录
instance = self.get_object()
# 2. 实例化序列化器,进行数据序列化
serializer = self.get_serializer(instance)
# 3. 将序列化后的数据传递给前端
return Response(serializer.data)
四、封装好的九个接口
1、CreateAPIView(创建)
class CreateAPIView(mixins.CreateModelMixin, GenericAPIView):
# 定义post方法,用于响应创建数据请求
def post(self, request, *args, **kwargs):
# 直接调用了CreateModelMixin类中的create方法,用于创建数据
return self.create(request, *args, **kwargs)
2、ListAPIView(获取所有)
class ListAPIView(mixins.ListModelMixin, GenericAPIView):
# 定义get方法,用于响应获取全部数据请求
def get(self, request, *args, **kwargs):
# 直接调用ListModelMixin中的list方法,获取全部数据
return self.list(request, *args, **kwargs)
3、RetrieveAPIView(获取单个)
class RetrieveAPIView(mixins.RetrieveModelMixin, GenericAPIView):
# 定义get方法,用于响应获取单个数据请求
def get(self, request, *args, **kwargs):
# 直接调用RetrieveModelMixin中的retrieve方法,获取指定数据
return self.retrieve(request, *args, **kwargs)
4、DestroyAPIView(删除)
class DestroyAPIView(mixins.DestroyModelMixin,GenericAPIView):
# 定义delete方法,用于响应删除指定数据请求
def delete(self, request, *args, **kwargs):
# 直接调用DestroyModelMixin中的destroy方法,删除指定数据
return self.destroy(request, *args, **kwargs)
5、UpdateAPIView(修改)
class UpdateAPIView(mixins.UpdateModelMixin, GenericAPIView):
# 定义put方法,用于响应删除全局修改数据请求
def put(self, request, *args, **kwargs):
# 直接调用UpdateModelMixin中的update方法,删除指定数据
return self.update(request, *args, **kwargs)
# 定义patch方法,用于响应局部修改指定数据请求
def patch(self, request, *args, **kwargs):
# 直接调用UpdateModelMixin中的partial_update方法
# partial_update方法内部依然是带哦用update方法,从而删除指定数据
return self.partial_update(request, *args, **kwargs)
6、ListCreateAPIView(获取所有+增加)
class ListCreateAPIView(
mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView
):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
7、RetrieveUpdateAPIView(获取单个+修改)
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
GenericAPIView):
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
8、RetrieveDestroyAPIView(获取单个+删除)
class RetrieveDestroyAPIView(
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
GenericAPIView
):
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
9、RetrieveUpdateDestroyAPIView(获取单个+修改+删除)
class RetrieveUpdateDestroyAPIView(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView
):
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
五、ModelViewSet再封装
1、利用接口封装缺陷
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from rest_framework.generics import ListCreateAPIView
from app01 import models
# 包含获取、删除、修改指定记录对象的操作
class Zzw1(RetrieveUpdateDestroyAPIView):
queryset = 模型表对象
# queryset = models.表名.objects
serializer_class = 模型表对象
# serializer_class = ZzwSerializer
# 包括获取所有、增加数据
class Zzw2(ListCreateAPIView):
queryset = 模型表对象
# queryset = models.表名.objects
serializer_class = 模型表对象
# serializer_class = ZzwSerializer
'''
根据drf封装好的九个接口,我们可以完成以上操作,使代码变得及其简单,但是:
1. 能不能进一步封装?以上两个类、内部其实重复代码很多
2. 需要解决重复get方法,才能实现!
'''
2、ModelViewSet源码
from rest_framework.viewsets import ModelViewSet
class ModelViewSet(
mixins.CreateModelMixin, # 增加
mixins.RetrieveModelMixin, # 获取单个
mixins.UpdateModelMixin, # 修改指定
mixins.DestroyModelMixin, # 删除指定
mixins.ListModelMixin, # 获取所有
GenericViewSet
):
pass
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
pass
class ViewSetMixin:
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
'''
重写了as_view,且新增了一个形参actions,此参数需要一个字典,且必填!
actions = {
'get': 'list',
'post': 'create',
}
actions用于设置方法与请求绑定,当post请求,则执行list方法,基于此原理,我们就可以实现如下操作
'''
# 核心代码
def view(request, *args, **kwargs):
for method, action in actions.items():
'''
method:代表请求方式
action:代表执行方法
'''
'''
使用反射,获取action代表的方法的内存地址
例如:
1. action假如是get方法
action ---> list()
2. 而方法和方法名实际上是变量名与内存地址的关系
action = 函数的内存地址
3. getattr如果对一个属性进行使用,那么他的返回结果是属性值(等号左边)
4. 反而思之,如果对一个方法使用,那么将获取该方法的内存地址
handler = action方法的内存地址
'''
handler = getattr(self, action)
'''
使用反射,创建一个新属性,并将函数内存地址赋值给method属性
例如:
1. method加入是get请求
method = handler
handler = action方法的内存地址
action ---> list()
2. 最终将list内存地址赋值给method属性
method = list方法的内存地址
3. 此刻如果是get请求,那么就会根据dispatch中的反射,自动执行get名称的方法/属性,而get名称的属性绑定了list方法的内存地址,所以就是执行list方法
'''
setattr(self, method, handler)
3、基于ViewSetMixin的视图类
# views.py
from rest_framework.viewsets import ViewSetMixin
# 一ViewSetMixin定要放在APIVIew前,才能实现请求--方法的映射绑定
class Book6View(ViewSetMixin,APIView):
def get_all_book(self,request):
print("xxxx")
book_list = Book.objects.all()
book_ser = BookSerializer(book_list, many=True)
return Response(book_ser.data)
# urls.py
#继承ViewSetMixin的视图类,路由可以改写成这样,从而get请求执行get_all_book
path('books6/', views.Book6View.as_view(actions={'get': 'get_all_book'})),
4、ModelViewSet实现五接口
# views.py
from rest_framework.viewsets import ModelViewSet
class xxx(ModelViewSet):
queryset = 模型表对象
# queryset = models.表名.objects
serializer_class = 模型表对象
# serializer_class = ZzwSerializer
# urls.py
path('xxx/', views.xxx.as_view(
actions=
{
'get': 'list', # 获取所有
'post': 'create' # 创建数据
}
))
# 2. 获取、删除、修改指定
re_path('xxx/(?P<pk>\d+)', views.xxx.as_view(
actions=
{
'get': 'retrieve', # 获取指定
'put': 'update', # 修改指定(全局)
'patch': 'partial_update', # 修改指定(局部)
'delete': 'destroy', # 删除指定
}
))
六、视图类整理
# 两个基类:
APIView(View)
from rest_framework.views import APIView
GenericAPIView(APIView)
from rest_framework.generics import GenericAPIView,
# 五个视图扩展类
from rest_framework.mixins import
ListModelMixin
RetrieveModelMixin
CreateModelMixin
UpdateModelMixin
DestoryModelMixin
# 九个子类接口
from rest_framework.generics import
ListAPIView(
GenericAPIView,
mixins.ListModelMixin
)
RetrieveAPIView(
mixins.RetrieveModelMixin,
mixins.RetrieveModelMixin,
mixins.RetrieveModelMixin,
GenericAPIView,
)
CreateAPIView(
mixins.CreateModelMixin,
GenericAPIView,
)
DestroyAPIView(
mixins.DestoryModelMixin,
GenericAPIView,
)
UpdateAPIView(
mixins.UpdateModelMixin
GenericAPIView,
)
ListCreateAPIView(
mixins.ListModelMixin
mixins.CreateModelMixin
GenericAPIView,
)
RetrieveDestroyAPIView(
mixins.RetrieveModelMixin,
mixins.DestoryModelMixin,
GenericAPIView,
)
RetrieveUpdateAPIView(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
GenericAPIView,
)
RetrieveUpdateDestroyAPIView(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestoryModelMixin,
GenericAPIView,
)
# 视图集
from rest_framework.viewsets import
ViewSetMixin
GenericViewSet(
ViewSetMixin,
GenericAPIView
)
ReadOnlyModelViewSet(
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet
)
ModelViewSet(
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet
)
ViewSet(
ViewSetMixin,
views.APIView
)