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") ]
/app01/serializer.py:前端
class AuthorModelSerializers(serializers.ModelSerializer): class Meta: model = Author fields = "__all__"
# 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)
注意:node
这两个变量是必须的。queryset告知这个类此次处理的数据。serializer_class告知这个类数据处理用到的序列化组件。python
这个类扩展了REST框架的APIView
类,为标准列表和详细视图添加了常见的行为。数据库
提供的每一个具体的通用视图都是经过把GenericAPIView
与一个或多个mixin类进行组合来构建的。django
# 方式三:基于rest_framework框架实现序列化(pip install djangorestframework) from rest_framework.views import APIView from rest_framework.response import Response from .serializers import BookSerializer # 自定义序列化类 class BookView(APIView): query_set = Book.objects.all() # 将query_set抽离 serializer_class = BookSerializer # 拿到序列化器 def get(self, request): # 第一个图书对象 # book_obj = Book.objects.first() # ret = BookSerializer(book_obj) # book_list = Book.objects.all() book_list = self.query_set # ret = BookSerializer(book_list, many=True) # 使用序列化器序列化 ret = self.serializer_class(book_list, many=True) """ 序列化的数据保存在ret.data中 """ return Response(ret.data) """ 得出来的结果会使用Django REST framework模板,在serializers.py中定制好序列化类后,显示效果以下所示: HTTP 200 OK Allow: GET, HEAD, OPTIONS Content-Type: application/json Vary: Accept [ { "id": 1, "title": "python开发", "category": "Python", "pub_time": "2011-08-27", "publisher": { "id": 1, "title": "人民日报社" }, "author": [ { "id": 1, "name": "xxx" }, { "id": 2, "name": "sssxx" } ] }, ... ] """ def post(self, request): print(request.data) serializer = BookSerializer(data=request.data) # 序列化器校验前端传回来的数据 if serializer.is_valid(): serializer.save() # 验证成功后保存数据库 # 由于ModelSerializer的create方法不支持source的用法。所以必须还自定义一个create方法。 return Response(serializer.validated_data) # validated_data存放验证经过的数据 else: return Response(serializer.errors) # errors存放错误信息 ''' 发送post请求接口设计 POST /books/list { "title": "nodejs的使用教程", "w_category": "1", "pub_time": "2018-10-27", "publisher_id": 1, "author_list": [1,2,3] } ''' class BookEditView(APIView): def get(self, request, id): """查看单条数据""" book_obj = Book.objects.filter(id=id).first() ret = BookSerializer(book_obj) return Response(ret.data) ''' GET /books/retrieve/3 { "id": 3, "title": "Linux开发", "category": "Linux", "pub_time": "2008-08-27", "publisher": { "id": 3, "title": "长江日报社" }, "author": [ { "id": 1, "name": "阿萨德" }, { "id": 3, "name": "阿斯达" } ] } ''' def put(self, request, id): """更新操做""" book_obj = Book.objects.filter(id=id).first() serializer = BookSerializer( book_obj, # 待更新对象 data=request.data, # 要更新的数据 partial=True # 重点:进行部分验证和更新 ) if serializer.is_valid(): serializer.save() # 保存 return Response(serializer.validated_data) # 返回验证经过的数据 # return Response(serializers.data) # 返回全部数据 else: return Response(serializer.errors) # 返回验证错误的数据 def delete(self, request, id): """删除操做""" book_obj = Book.objects.filter(id=id).first() book_obj.delete() return Response("")
升级:改写使用通用类,继承通用方法。json
class GenericAPIView(APIView): # 通用APIView模板类 query_set = None serializer_class = None def get_queryset(self): return self.query_set def get_serializer(self, *args, **kwargs): return self.serializer_class(*args, **kwargs) # 实例化,且接收全部的参数 class BookView(GenericAPIView): # 以方法的形式调用获取 query_set = Book.objects.all() serializer_class = BookSerializer def get(self, request): book_list = self.get_queryset() ret = self.get_serializer(book_list, many=True) return Response(ret.data) def post(self, request): serializer = BookSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) else: return Response(serializer.errors)
class GenericAPIView(APIView): # 通用APIView模板类 query_set = None serializer_class = None def get_queryset(self): return self.query_set def get_serializer(self, *args, **kwargs): return self.serializer_class(*args, **kwargs) # 实例化,且接收全部的参数 class ListModelMixin(object): def list(self, request): queryset = self.get_queryset() ret = self.get_serializer(queryset, many=True) return Response(ret.data) class CreateModelMixin(object): def create(self, request): serializer = self.get_serializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) else: return Response(serializer.errors) class RetrieveModelMixin(object): def retrieve(self, request, id): # 查看单条数据 book_obj = self.get_queryset().filter(id=id).first() ret = self.get_serializer(book_obj) return Response(ret.data) class UpdateModelMixin(object): def update(self, request, id): book_obj = self.get_queryset().filter(id=id).first() serializer = self.get_serializer( book_obj, # 待更新对象 data=request.data, # 要更新的数据 partial=True # 重点:进行部分验证和更新 ) if serializer.is_valid(): serializer.save() # 保存 return Response(serializer.validated_data) # 返回验证经过的数据 # return Response(serializers.data) # 返回全部数据 else: return Response(serializer.errors) # 返回验证错误的数据 class DestroyModelMixin(object): def delete(self, request, id): book_obj = self.get_queryset().filter(id=id).first() book_obj.delete() return Response("") class BookView(GenericAPIView, ListModelMixin, CreateModelMixin): # 一层封装 query_set = Book.objects.all() serializer_class = BookSerializer def get(self, request): return self.list(request) def post(self, request): return self.create(request) class BookEditView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): # 一层封装 query_set = Book.objects.all() serializer_class = BookSerializer def get(self, request, id): """查看单条数据""" return self.retrieve(request, id) def put(self, request, id): """更新操做""" return self.update(request, id) def delete(self, request, id): """删除操做""" return self.destroy(request, id)
进一步封装以下所示:api
# 二层封装 class ListCreateAPIView(GenericAPIView, ListModelMixin, CreateModelMixin): pass class RetrieveUpdateDestroyAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): pass # class BookView(GenericAPIView, ListModelMixin, CreateModelMixin): # 一层封装 class BookView(ListCreateAPIView): # 使用二层封装 query_set = Book.objects.all() serializer_class = BookSerializer def get(self, request): return self.list(request) def post(self, request): return self.create(request) # class BookEditView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): # 一层封装 class BookEditView(RetrieveUpdateDestroyAPIView): # 使用二层封装 query_set = Book.objects.all() serializer_class = BookSerializer def get(self, request, id): """查看单条数据""" return self.retrieve(request, id)
通过两次封装后,剩下两个视图,但依然不够简洁。因为两个get请求的返回值不一样,写了两个视图来解决。app
如今能够经过路由传参的方式来解决上述问题。框架
from django.urls import path, include from .views import BookView, BookEditView, BookModelViewSet urlpatterns = [ # path('list', BookView.as_view()), # 查看全部的图书 # 注意url中参数命名方式,2.0以前的写法:'retrieve/(?P<id>\d+)' # 2.0以后的写法:<>内声明类型,冒号后面跟着关键字参数 # path('retrieve/<int:id>', BookEditView.as_view()) # 单条数据查看 path('list', BookModelViewSet.as_view({"get": "list", "post": "create"})), path('retrieve/<int:id>', BookModelViewSet.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})) ]
as_view方法自己是不支持传参的,所以须要重写该方法。ide
class ViewSetMixin(object): def as_view(self): """ 按照参数指定的去匹配 get-->list :return: """ pass class BookModelViewSet(ViewSetMixin, GenericAPIView, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): pass
可是咱们不用本身去定义该方法,能够直接使用框架已经提供好的ViewSetMixin:
from rest_framework.viewsets import ViewSetMixin
它作的主要事情就是重写as_view方法:
class ViewSetMixin: @classonlymethod def as_view(cls, actions=None, **initkwargs): 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 # Bind methods to actions # This is the bit that's different to a standard view # 这里actions是前面url中提供的字典参数{"get": "list", "post": "create"} for method, action in actions.items(): # 循环获得的method是get,action是list handler = getattr(self, action) # self是本身的视图类 setattr(self, method, handler) # 在本身的视图类中找list方法 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 # 作分发:get-->self.get;post-->self.post return self.dispatch(request, *args, **kwargs)
因为self.get已经等于self.list,分发get对应的是list。
from rest_framework.viewsets import ViewSetMixin class BookModelViewSet(ViewSetMixin, GenericAPIView, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): query_set = Book.objects.all() serializer_class = BookSerializer
因为get去匹配时已经自动对应到了self.list方法。所以再也不自定义视图方法。
from rest_framework.viewsets import ViewSetMixin class ModelViewSet(ViewSetMixin, GenericAPIView, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): pass class BookModelViewSet(ModelViewSet): query_set = Book.objects.all() serializer_class = BookSerializer
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)这个类的运行流程以下所示:
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通常用来获取列表页,不须要重写方法。
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)
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()
这一章简要分析了源码的内容以及各个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 # 告知处理用到的序列化组件
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函数都封装进去了。
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等封装方式彻底相似。
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"), ]
注意:
利用参数来指定什么请求方式由哪个内部方法来执行。尤为注意两种不一样get请求用不一样的方法来处理。
from rest_framework import viewsets class AuthorViewSet(viewsets.ModelViewSet): queryset = Author.objects.all() # 配置queryset:告知这个类此次处理的数据 serializer_class = AuthorModelSerializers # 告知处理用到的序列化组件
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类。
class GenericViewSet(ViewSetMixin, generics.GenericAPIView): pass
能够看到GenericViewSet继承了前面学的generics.GenericAPIView类,这个类扩展了REST框架的APIView
类,可是它并无改写dispatch方法,所以url中能够添加参数与它无关。
GenericViewSet还继承了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函数
注意:
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为空则报错的意思。
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;
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
从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。
如此就实现了两个类合成一个类。
查看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)
全部的视图类在以下这四个文件内:
from rest_framework import views # APIView from rest_framework import generics # 公共通用视图类:GenericAPIView,及各类组合视图类CreateAPIView、ListAPIView、RetrieveAPIView等 from rest_framework import mixins # 混合继承类:CreateModelMixin、ListModelMixin、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin from rest_framework import viewsets # 重写as_view: ViewSetMixin;其余类都是帮助去继承ViewSetMixin
视图类继承顺序,如Django REST Framework视图图谱所示:
首先 django是继承 view的,DRF是从APIView开始继承起,APIView封装了request,其中包含了data、query_params等属性、方法。
而后 GenericAPIView封装了 get_queryset() 和 get_serializer();ViewSetMixin重写了 as_view()方法。
随后 GenericViewSet帮忙继承GenericAPIView和ViewSetMixin。
最后最高层的封装是 ModelViewSet。