您的位置:首页 > 其它

rest-framework框架——视图三部曲

2018-09-03 00:59 459 查看

一、mixins类编写视图

1、配置url

urlpatterns = [
...
re_path(r'^authors/$', views.AuthorView.as_view(), name="author"),
re_path(r'^authors/(?P<pk>\d+)/$', views.AuthorDetailView.as_view(), name="detail_author")
]

2、编写Author的序列化类

/app01/serializer.py:

class AuthorModelSerializers(serializers.ModelSerializer):
class Meta:
model = Author
fields = "__all__"

3、编写Author的视图

# Author
from rest_framework import mixins, generics

class AuthorView(mixins.ListModelMixin,        # 扩展了列出查询集功能
mixins.CreateModelMixin,      # 扩展了创建和保存新模型实例功能
generics.GenericAPIView):     # 继承扩展了REST框架的APIView类,为标准列表和详细视图添加了常见的行为
queryset = Author.objects.all()     # 配置queryset:告知这个类这次处理的数据
serializer_class = AuthorModelSerializers     # 告知处理用到的序列化组件

def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)

class AuthorDetailView(mixins.RetrieveModelMixin,   # 扩展在响应中实现返回现有模型实例功能(获取单条数据)
mixins.DestroyModelMixin,    # 扩展现有模型实例的删除功能
mixins.UpdateModelMixin,     # 扩展更新和保存现有模型实例功能
generics.GenericAPIView):
queryset = Author.objects.all()     # 配置queryset:告知这个类这次处理的数据
serializer_class = AuthorModelSerializers     # 告知处理用到的序列化组件

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 delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)

  注意:

(1)queryset和serializer_class变量

  这两个变量是必须的。queryset告知这个类这次处理的数据。serializer_class告知这个类数据处理用到的序列化组件。

(2)五类mixins作用和对应的http方法

  

(3)GenericAPIView

  这个类扩展了REST框架的

APIView
类,为标准列表和详细视图添加了常见的行为。

  提供的每个具体的通用视图都是通过把

GenericAPIView
与一个或多个mixin类进行组合来构建的。

(4)测试验证

    

二、Mixins源码分析

1、CreateModelMixin

class CreateModelMixin(object):
"""Create a model instance ==>创建一个实例"""
def create(self, request, *args, **kwargs):
# 获取相关serializer
serializer = self.get_serializer(data=request.data)

# 进行serializer的验证;raise_exception=True,一旦验证不通过,不再往下执行,直接引发异常
serializer.is_valid(raise_exception=True)

# 调用perform_create()方法,保存实例
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
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 {}

  注意:

(1)perform_create( )对serializer直接进行save保存,当在一些情境下,需要对perform_create( )进行重写。

(2)这个类的运行流程如下所示:

  

2、ListModelMixin

class ListModelMixin(object):
"""List a queryset.==> 列表页获取"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())

# 这是一个分页功能,如果在viewset中设置了pagination_class,那么这里就会起作用
# 获取当前页的queryset,如果不存在分页,返回None
page = self.paginate_queryset(queryset)

if page is not None:
# 分页不为空,那么不能简单的执行Response(serializer.data)
# 还需要将相关的page信息序列化在进行响应
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)

  ListModelMixin一般用来获取列表页,不需要重写方法。

3、RetriveModelMixin

class RetrieveModelMixin(object):
"""
Retrieve a model instance.==> 获取某一个对象的具体信息
"""
def retrieve(self, request, *args, **kwargs):
# 一般访问的url都为/obj/id/这种新式
# get_object()可以获取到这个id的对象
# 注意在viewset中设置lookup_field获取重写get_object()方法可以指定id具体对象是什么~!
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)

4、DestoryModelMixin

class DestroyModelMixin(object):
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)

def perform_destroy(self, instance):
instance.delete()

5、总结

  这一章简要分析了源码的内容以及各个mixins的逻辑,最重要的还是学会重写它们相关的方法。一般情况下,当我们在操作某一个model的时候,涉及到另外一个model中数据的修改,那么就需要对这个mixins下执行save的逻辑的方法进行重写。

三、使用通用的基于类的视图

  通过使用mixin类,使用更少的代码重写了这些视图,但我们还可以再进一步。REST框架提供了一组已经混合好(mixed-in)的通用视图,我们可以使用它来简化我们的

views.py
模块。

from rest_framework import mixins, generics

class AuthorView(generics.ListCreateAPIView):
queryset = Author.objects.all()     # 配置queryset:告知这个类这次处理的数据
serializer_class = AuthorModelSerializers     # 告知处理用到的序列化组件

class AuthorDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Author.objects.all()     # 配置queryset:告知这个类这次处理的数据
serializer_class = AuthorModelSerializers     # 告知处理用到的序列化组件

1、ListCreateAPIView源码

class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)

  可以看到源码中将Mixins混合类和get\post函数都封装进去了。

2、RetrieveUpdateDestroyAPIView源码

class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating or deleting a model instance.
"""
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)

  其他RetrieveDestroyAPIView、RetrieveUpdateAPIView、UpdateAPIView、DestroyAPIView等封装方式完全类似。

四、运用viewsets.ModelViewSet的视图

1、改写urls.py

from django.contrib import admin
from django.urls import path, re_path
from app01 import views

urlpatterns = [
path('admin/', admin.site.urls),
'''代码省略'''
# re_path(r'^authors/$', views.AuthorView.as_view(), name="author"),
# re_path(r'^authors/(?P<pk>\d+)/$', views.AuthorDetailView.as_view(), name="detail_author"),

# as_view参数指定什么请求走什么方法
re_path(r'^authors/$', views.AuthorViewSet.as_view({"get": "list", "post": "create"}), name="author_list"),
re_path(r'^authors/(?P<pk>\d+)/$', views.AuthorViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
}), name="author_detail"),
]

  注意:

(1)as_view参数

  利用参数来指定什么请求方式由哪一个内部方法来执行。尤其注意两种不同get请求用不同的方法来处理。

2、引入viewsets改写视图

from rest_framework import viewsets

class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()     # 配置queryset:告知这个类这次处理的数据
serializer_class = AuthorModelSerializers     # 告知处理用到的序列化组件

五、ModelViewSet源码分析

1、查看viewsets.ModelViewSet源码

class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass

  ModelViewSet继承了所有五个混合类。还继承了GenericViewSet类。

2、查看GenericViewSet源码

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
pass

  可以看到GenericViewSet继承了前面学的generics.GenericAPIView类,这个类扩展了REST框架的

APIView
类,但是它并没有改写dispatch方法,因此url中可以添加参数与它无关。

  GenericViewSet还继承了ViewSetMixin类。

3、查看分析ViewSetMixin源码

class ViewSetMixin(object):
"""
Overrides `.as_view()` so that it takes an `actions` keyword that performs
the binding of HTTP methods to actions on the Resource.
覆盖as_view方法需要接收一个actions参数来实现将HTTP方法绑定到资源。
For example, to create a concrete view binding the 'GET' and 'POST' methods
to the 'list' and 'create' actions...
举例来说,如果要创建一个具体视图绑定'GET'和'POST'方法到'list'和'create'操作。可写为如下格式:
view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
"""
@classonlymethod
def as_view(cls, actions=None, **initkwargs):
"""
Because of the way class based views create a closure around the
instantiated view, we need to totally reimplement `.as_view`,
and slightly modify the view function that is created and returned.
"""
# actions must not be empty   actions来接收{'get': 'list', 'post': 'create'}
if not actions:   # 为空则报错
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")

# sanitize keyword arguments
for key in initkwargs:.....   # 这里的值是空的

def view(request, *args, **kwargs):...

return csrf_exempt(view)    # 执行as_view 最终返回的还是view函数

  注意:

(1)分析函数传参方式:**字典传参等同于关键字传参。

def foo(action=None, **kwargs):
print(action)
print(kwargs)

foo({"a": 1}, b=2, c=3)
"""
{'a': 1}
{'b': 2, 'c': 3}
"""
foo(a=2, b=3)
"""
None
{'a': 2, 'b': 3}
"""
foo(**{"a": 1, "b": 3})
"""
None
{'a': 1, 'b': 3}
"""
foo({"a": 1, "b": 3})
"""
{'a': 1, 'b': 3}
{}
"""

  由此可见as_view传递的参数{"get": "list", "post": "create"}是ViewSetMixin内改写的as_view方法,由actions参数来接收的。

  由于acitons默认值是None,因此not None其实是True,if not actions: 其实是actions为空则报错的意思。

(2)as_view函数最终返回值是view函数

def view(request, *args, **kwargs):
self = cls(**initkwargs)
# We also store the mapping of request methods to actions,
# so that we can later set the action attribute.
# eg. `self.action = 'list'` on an incoming GET request.
self.action_map = actions  # 传入的字典数据{'get': 'list', 'post': 'create'}

# Bind methods to actions
# This is the bit that's different to a standard view
for method, action in actions.items():  # method:请求方式   action:实例方法
handler = getattr(self, action)  # 反射得到self.list  self.create方法
setattr(self, method, handler)  # 给请求方式设置对应的实例方法

if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get

self.request = request
self.args = args
self.kwargs = kwargs

# And continue as usual
return self.dispatch(request, *args, **kwargs)

  在view函数中用self.action_map = actions 来接收传入的字典数据{'get': 'list', 'post': 'create'}。

  循环for method, action in actions.items():   拿到method:请求方式和action:实例方法。

  再通过反射方法:handler = getattr(self, action) 得到self.list self.create方法。

  最后通过反射方法setattr(self, method, handler)  给请求方式设置对应的实例方法:以后再找getattr(self, "get")的时候找到是self.list;找getattr(self, "post")的时候找到是self.create;

(3)反射方法详解

1. getattr()函数是Python自省的核心函数,具体使用大体如下:
class A:
def __init__(self):
self.name = 'zhangjing'
#self.age='24'
def method(self):
print"method print"

Instance = A()
print getattr(Instance , 'name, 'not find') #如果Instance 对象中有属性name则打印self.name的值,否则打印'not find'
print getattr(Instance , 'age', 'not find') #如果Instance 对象中有属性age则打印self.age的值,否则打印'not find'
print getattr(a, 'method', 'default') #如果有方法method,否则打印其地址,否则打印default
print getattr(a, 'method', 'default')() #如果有方法method,运行函数并打印None否则打印default

2. hasattr(object, name)

说明:判断对象object是否包含名为name的特性(hasattr是通过调用getattr(ojbect, name)是否抛出异常来实现的)

3. setattr(object, name, value)

这是相对应的getattr()。参数是一个对象,一个字符串和一个任意值。字符串可能会列出一个现有的属性或一个新的属性。这个函数将值赋给属性的。该对象允许它提供。例如,setattr(x,“foobar”,123)相当于x.foobar = 123。

4. delattr(object, name)

与setattr()相关的一组函数。参数是由一个对象(记住python中一切皆是对象)和一个字符串组成的。string参数必须是对象属性名之一。该函数删除该obj的一个由string指定的属性。delattr(x, 'foobar')=del x.foobar

4、找到view函数执行完后最终返回dispatch方法 并完成分析

  从ModelViewSet——》GenericViewSet——》GenericAPIView——》APIView一路回溯到APIView才找到dispatch方法。

def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers  # deprecate?

try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
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
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

  当访问是走的路由是re_path(r'^authors/$', views.AuthorViewSet.as_view({"get": "list", "post": "create"}), name="author_list"),get请求handler对应的是self.list。

  而当访问走的路由是re_path(r'^authors/(?P<pk>\d+)/$', views.AuthorViewSet.as_view({'get': 'retrieve',....

 此时get请求handler对应的是self.retrieve。

  如此就实现了两个类合成一个类。

5、找到并执行list方法

  查看ModelViewSet源码:

class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):

  在ListModelMixin找到list方法:

class ListModelMixin(object):
"""
List a queryset.
"""
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)

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: