上一节的视图部分:html
from rest_framework.views import APIView from rest_framework.response import Response from .models import * from django.shortcuts import HttpResponse from django.core import serializers from rest_framework import serializers class BookSerializers(serializers.ModelSerializer): class Meta: model=Book fields="__all__" #depth=1 class PublshSerializers(serializers.ModelSerializer): class Meta: model=Publish fields="__all__" depth=1 class BookViewSet(APIView): def get(self,request,*args,**kwargs): book_list=Book.objects.all() bs=BookSerializers(book_list,many=True,context={'request': request}) return Response(bs.data) def post(self,request,*args,**kwargs): print(request.data) bs=BookSerializers(data=request.data,many=False) if bs.is_valid(): print(bs.validated_data) bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class BookDetailViewSet(APIView): def get(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj,context={'request': request}) return Response(bs.data) def put(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj,data=request.data,context={'request': request}) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class PublishViewSet(APIView): def get(self,request,*args,**kwargs): publish_list=Publish.objects.all() bs=PublshSerializers(publish_list,many=True,context={'request': request}) return Response(bs.data) def post(self,request,*args,**kwargs): bs=PublshSerializers(data=request.data,many=False) if bs.is_valid(): # print(bs.validated_data) bs.save() return Response(bs.data) else: return HttpResponse(bs.errors) class PublishDetailViewSet(APIView): def get(self,request,pk): publish_obj=Publish.objects.filter(pk=pk).first() bs=PublshSerializers(publish_obj,context={'request': request}) return Response(bs.data) def put(self,request,pk): publish_obj=Publish.objects.filter(pk=pk).first() bs=PublshSerializers(publish_obj,data=request.data,context={'request': request}) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
咱们使用这一套逻辑,意味着有一张模型表,就要将上面的代码写一遍,代码的复用性不好,全部,咱们要对复用的部分进行封装,咱们经过三步,一步一步的来实现代码的复用和封装。django
第一步:大致思路是使用混合类(多继承的形式),借助封装好的mixins类api
from rest_framework import mixins
咱们能够发现,这些复用的代码中,有两个变量是必需要知道的,一个是模型表中的数据,一个是该模型表对应的serializers对象,因此咱们能够将这两个变量单独给提出来放到类的静态属性中,这样整个类中均可以调用。app
使用方法:框架
将查看全部数据的方法封装到一个类中:ide
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)
因此,咱们处理查看全部数据的视图时,直接将这个类继承,同时,在get方法中,将list方法返回便可。函数
一样的方式,将添加数据,查看某一条数据,编辑某一条数据,删除某一条数据也分别封装到一个个类下,只须要在对应的请求方法中,返回继承类中对应的方法便可。post
添加数据封装到类:fetch
class CreateModelMixin(object): """ Create a model instance. """ def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) 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 {}
查看某一条数据封装的类:ui
class RetrieveModelMixin(object): """ Retrieve a model instance. """ def retrieve(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(instance) return Response(serializer.data)
编辑数据的类:
class UpdateModelMixin(object): """ Update a model instance. """ def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None): # If 'prefetch_related' has been applied to a queryset, we need to # forcibly invalidate the prefetch cache on the instance. instance._prefetched_objects_cache = {} return Response(serializer.data) def perform_update(self, serializer): serializer.save() def partial_update(self, request, *args, **kwargs): kwargs['partial'] = True return self.update(request, *args, **kwargs)
删除数据的类:
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()
经过上面的方式,咱们会有一个疑问,咱们传入的pk值,是怎么处理的。
咱们在处理类的视图函数时,必需要继承一个APIView的类,一样的,也将这个类进行了封装:放到了rest_framework.generics下面的GenericAPIView中;
from rest_framework import views class GenericAPIView(views.APIView): pass
这个类继承了APIView,并进行了相应的扩展。咱们传的pk值,就是在这里被处理的。
处理单条数据时,咱们确定要先将对应的数据取出来,再作对应的处理,一样的,封装到类中确定也作了相似的处理,咱们观察处理单条数据的类,会发现这样一行代码:instance = self.get_object() 很明显,确定是取数据去了,怎么取?不知道,看看,先找到这个方法。咱们从本类和继承的类中依次去找,最后在GenericAPIView中找到了这个方法:
def get_object(self): """ Returns the object the view is displaying. You may want to override this if you need to provide non-standard queryset lookups. Eg if objects are referenced using multiple keyword arguments in the url conf. """ queryset = self.filter_queryset(self.get_queryset()) # Perform the lookup filtering. lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field assert lookup_url_kwarg in self.kwargs, ( 'Expected view %s to be called with a URL keyword argument ' 'named "%s". Fix your URL conf, or set the `.lookup_field` ' 'attribute on the view correctly.' % (self.__class__.__name__, lookup_url_kwarg) ) filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} obj = get_object_or_404(queryset, **filter_kwargs) # May raise a permission denied self.check_object_permissions(self.request, obj) return obj
这个方法最终将obj返回了,那就要好奇了,怎么一会儿将obj取出来的,继续查看 obj=get_object_or_404(queryset,**fi..)
def get_object_or_404(queryset, *filter_args, **filter_kwargs): """ Same as Django's standard shortcut, but make sure to also raise 404 if the filter_kwargs don't match the required types. """ try: return _get_object_or_404(queryset, *filter_args, **filter_kwargs) except (TypeError, ValueError, ValidationError): raise Http404
进行了一个异常处理,仍是没有咱们要的内容,继续找 _get_object_or_404(queryset,*...,**...)
def get_object_or_404(klass, *args, **kwargs): """ Uses get() to return an object, or raises a Http404 exception if the object does not exist. klass may be a Model, Manager, or QuerySet object. All other passed arguments and keyword arguments are used in the get() query. Note: Like with get(), an MultipleObjectsReturned will be raised if more than one object is found. """ queryset = _get_queryset(klass) try: return queryset.get(*args, **kwargs) except AttributeError: klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__ raise ValueError( "First argument to get_object_or_404() must be a Model, Manager, " "or QuerySet, not '%s'." % klass__name ) except queryset.model.DoesNotExist: raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
这么一堆中,就 queryset.get(*args,**kwargs) 是咱们要的内容,咱们经过url传入pk=val,被**kwargs捕获,传入这个代码中,等同于models.模型类.objects.all().get(**{"pk":val}),这种形式很眼熟,这就是咱们取值的操做。
from rest_framework import mixins from rest_framework import generics class BookViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializers 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 BookDetailViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView): queryset = Book.objects.all() serializer_class = BookSerializers 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)
特别须要注意的是,使用这种方式,设计url时,须要传参的url,必须使用分组命名匹配的形式,并且,命名必须是pk,不然报错
urlpatterns = [
# 错误写法
url(r'^books/$',views.Book.as_view()), url(r'^books/(\d+)/$',views.BookDeail.as_view()),
# 正确写法 url(r'^publish/$',views.Publish.as_view()), url(r'^publish/(?P<pk>\d+)/$',views.PublishDeail.as_view()), #必须是pk ]
采用这种方式匹配url,跟封装的源码有关;
class GenericAPIView(views.APIView): """ Base class for all other generic views. """ # You'll need to either set these attributes, # or override `get_queryset()`/`get_serializer_class()`. # If you are overriding a view method, it is important that you call # `get_queryset()` instead of accessing the `queryset` property directly, # as `queryset` will get evaluated only once, and those results are cached # for all subsequent requests. queryset = None serializer_class = None # If you want to use object lookups other than pk, set 'lookup_field'. # For more complex lookup requirements override `get_object()`. lookup_field = 'pk'
上面的代码虽然必定程度上减小了代码的复用性,可是,咱们对于每个模型表任然要进行大量的复用代码。
备注:在继承类中配置了lookup_field属性,If you want to use object lookups other than pk, set 'lookup_field'
第二步:将这些封装的类进一步封装。
经过使用mixin类,咱们使用更少的代码重写了这些视图,但咱们还能够再进一步。REST框架提供了一组已经混合好(mixed-in)的通用视图,咱们能够使用它来简化咱们的views.py
模块。
将咱们不须要接收传参的视图函数总体封装到一个类(查看全部数据,添加数据)
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)
一样的,须要接收一个pk值的视图封装:
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)
借助这个封装,进一步的简化代码量
from rest_framework import mixins from rest_framework import generics class BookViewSet(generics.ListCreateAPIView): queryset = Book.objects.all() serializer_class = BookSerializers class BookDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Book.objects.all() serializer_class = BookSerializers class PublishViewSet(generics.ListCreateAPIView): queryset = Publish.objects.all() serializer_class = PublshSerializers class PublishDetailViewSet(generics.RetrieveUpdateDestroyAPIView): queryset = Publish.objects.all() serializer_class = PublshSerializers
经过上面的代码,咱们已经减小了大量的代码,可是,好像咱们还在复用一些代码,queryset和serializer_class 这两个参数。因此咱们继续封装。
第三步的思路:将接收pk的url和不接受pk的url合为一个视图,会有一个问题,那就是,get请求,咱们无论查看全部数据仍是查看某一条数据,都会走同一个视图,怎么能够解决这个问题?问题的根源是出在分发上,咱们能够重写。
咱们将视图合二为一,就须要将继承的全部的类进一步封装。
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
导入:
from rest_framework.viewsets import ModelViewSet
urls.py:
url(r'^books/$', views.BookViewSet.as_view({"get":"list","post":"create"}),name="book_list"), url(r'^books/(?P<pk>\d+)$', views.BookViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }),name="book_detail"),
views.py:
class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializers
使用这种方式,url就必须在as_view()方法里,传一个字典参数。而以前是不须要传的,显然,重写了as_view()方法,那咱们就去找这个重写as_view的类。ModelViewSet中前五个是咱们第一步封装的,惟有最后一个GenericViewSet,是重写的
class GenericViewSet(ViewSetMixin, generics.GenericAPIView): """ The GenericViewSet class does not provide any actions by default, but does include the base set of generic view behavior, such as the `get_object` and `get_queryset` methods. """ pass
这个类中经过混合类的方式进行继承,后面的是咱们以前用过的,惟有前面的ViewSetMixin是扩展的,很显然,是这个类重写了as_view()
class ViewSetMixin(object): """ This is the magic. Overrides `.as_view()` so that it takes an `actions` keyword that performs the binding of HTTP methods to actions on the Resource. For example, to create a concrete view binding the 'GET' and 'POST' methods to the 'list' and 'create' actions... 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. """ # 此处省略部分源码... 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 for method, action in actions.items(): handler = getattr(self, action) 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) # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) # We need to set these on the view function, so that breadcrumb # generation can pick out these bits of information from a # resolved URL. view.cls = cls view.initkwargs = initkwargs view.suffix = initkwargs.get('suffix', None) view.actions = actions return csrf_exempt(view)
同样的,这个as_view()最终返回一个view函数,跟以前是同样的,那么接收的这个参数有什么用呢?确定在view函数中,调用了这个参数,并实现了某些东西。(actions接收了传的字典参数)
for method, action in actions.items(): handler = getattr(self, action) setattr(self, method, handler)
这三行代码是进行了相应的处理,首先,循环这个参数,经过反射获得value对应的函数名(list、create、retrieve、update、destory),最后经过setattr实现了咱们调用哪一个key(method)就会执行对应的value,从而完美的解决了分发的问题。
经过这三次分发,实现了代码的复用。
经过三层封装,使得咱们能够很是快速的经过几行代码实现一个模型类的增删改查,大大的提升了开发效率。有利就有弊,封装的越完善,就意味着不够灵活。下个系列中,带来restframework中的很是有用的三个套件。