视图三部曲

一部曲 · 使用混合(mixins)

上一节的视图部分:

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)

我们使用这一套逻辑,意味着有一张模型表,就要将上面的代码写一遍,代码的复用性很差,所有,我们要对复用的部分进行封装,我们通过三步,一步一步的来实现代码的复用和封装。

第一步:大体思路是使用混合类(多继承的形式),借助封装好的mixins类

from rest_framework import mixins

  我们可以发现,这些复用的代码中,有两个变量是必须要知道的,一个是模型表中的数据,一个是该模型表对应的serializers对象,所以我们可以将这两个变量单独给提出来放到类的静态属性中,这样整个类中都可以调用。

使用方法:

将查看所有数据的方法封装到一个类中:

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方法返回即可。

同样的方式,将添加数据,查看某一条数据,编辑某一条数据,删除某一条数据也分别封装到一个个类下,只需要在对应的请求方法中,返回继承类中对应的方法即可。

添加数据封装到类:

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 {}

查看某一条数据封装的类:

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}),这种形式很眼熟,这就是我们取值的操作。

mixin类编写视图

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

三部曲 · viewsets.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中的非常有用的三个套件

深入解析当下大热的前后端分离组件django-rest_framework系列二的更多相关文章

  1. 深入解析当下大热的前后端分离组件django-rest_framework系列一

    前言 Nodejs的逐渐成熟和日趋稳定,使得越来越多的公司开始尝试使用Nodejs来练一下手,尝一尝鲜.在传统的web应用开发中,大多数的程序员会将浏览器作为前后端的分界线.将浏览器中为用户进行页面展 ...

  2. 深入解析当下大热的前后端分离组件django-rest_framework系列四

    查漏补缺系列 解析器 request类 django的request类和rest-framework的request类的源码解析 局部视图 from rest_framework.parsers im ...

  3. 深入解析当下大热的前后端分离组件django-rest_framework系列三

    三剑客之认证.权限与频率组件 认证组件 局部视图认证 在app01.service.auth.py: class Authentication(BaseAuthentication): def aut ...

  4. 海纳百川无所不容,Win10环境下使用Docker容器式部署前后端分离项目Django+Vue.js

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_179 随着现代化产品研发的不断推进,我们会发现,几乎每个产品线都会包含功能各异的服务,而且服务与服务之间存在也会存在着错综复杂的依 ...

  5. 《Spring Boot 入门及前后端分离项目实践》系列介绍

    课程计划 课程地址点这里 本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 个部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 项目实践开发 ...

  6. SpringBoot+Vue豆宝社区前后端分离项目手把手实战系列教程01---搭建前端工程

    豆宝社区项目实战教程简介 本项目实战教程配有免费视频教程,配套代码完全开源.手把手从零开始搭建一个目前应用最广泛的Springboot+Vue前后端分离多用户社区项目.本项目难度适中,为便于大家学习, ...

  7. 从壹开始前后端分离 [ Vue2.0+.NetCore2.1] 二十六║Client渲染、Server渲染知多少{补充}

    前言 书接上文,昨天简单的说到了 SSR 服务端渲染的相关内容<二十五║初探SSR服务端渲染>,主要说明了相关概念,以及为什么使用等,昨天的一个小栗子因为时间问题,没有好好的给大家铺开来讲 ...

  8. 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二九║ Nuxt实战:异步实现数据双端渲染

    回顾 哈喽大家好!又是元气满满的周~~~二哈哈,不知道大家中秋节过的如何,马上又是国庆节了,博主我将通过三天的时间,给大家把项目二的数据添上(这里强调下,填充数据不是最重要的,最重要的是要配合着让大家 ...

  9. SpringBoot+Vue豆宝社区前后端分离项目手把手实战系列教程02---创建后端工程

    本节代码开源地址 代码地址 项目运行截图 搭建后端工程 0.导入sql 在数据库导入 /* Navicat Premium Data Transfer Source Server : localhos ...

随机推荐

  1. IDEA批量修改变量快捷键

    Window: Ctrl+Shift+Alt+J Mac:  Ctrl+Option+G

  2. dns随笔(部分转载)

    1.allow-notify allow-notify 定义了一个匹配列表并且只应用于从dns区域(slave zone),比如,这个列表是一个ip列表,它 2. 触发同步的过程 http://www ...

  3. HDU1698 线段树(区间更新区间查询)

    Just a Hook Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total S ...

  4. How to solve “Device eth0 does not seem to be present, delaying initialization” error

    Today, I encountered with a strange error after I cloned CentOS 6 guest machine in Oracle VirtualBox ...

  5. Tomcat7项目迁移到Tomcat8中文乱码问题

    我打算开始使用Tomcat8了,先解决中文乱码问题,在解决其它的问题! 个人推荐:修改server.xml方式 对于SpringMVC报的错误我稍后在补充问题 1.问题描述 Tomcat 7下项目切换 ...

  6. vue-cli中引入jquery的方法

    vue-cli中引入jquery的方法 以前写vue项目都没有引入过jquery,今天群里面的一位小伙伴问了我这个问题,我就自己捣鼓了一下,方法如下: 我们先进入webpack.base.conf.j ...

  7. JS鼠标滚轮事件解析

    一.不同浏览器的鼠标滚轮事件 首先,不同的浏览器有不同的滚轮事件.主要是有两种,onmousewheel(IE/Opera/Chrome支持,firefox不支持)和DOMMouseScroll(只有 ...

  8. java项目环境搭建

    开发java项目时,由于涉及到版权问题,最好使用开源.免费的软件.比如eclipse. 此外,一个web的java项目涉及到jdk.tomcat等,插件还可能用到svn插件.maven插件. 建议进入 ...

  9. SpringBoot打war包并部署到tomcat下运行

    一.修改pom.xml. 1.packaging改为war 2.build节点添加<finalName>你的项目名</finalName> 二.修改项目启动类,继承Spring ...

  10. log4net 性能测试

    1.执行事务:20260 次 写日志:        耗时11.59分 不写日志:    耗时11.55分 异步日志:    耗时12.49分 (个人电脑,.net 线程池调用线程写日志可能比主线程直 ...