Nodejs的逐渐成熟和日趋稳定,使得愈来愈多的公司开始尝试使用Nodejs来练一下手,尝一尝鲜。在传统的web应用开发中,大多数的程序员会将浏览器做为先后端的分界线。将浏览器中为用户进行页面展现的部分称之为前端,而将运行在服务器,为前端提供业务逻辑和数据准备的全部代码统称为后端。html
先后端分离是web应用的一种架构模式。在开发阶段,先后端工程师约定好数据交互接口,实现并行开发和测试;在运行阶段先后端分离模式须要对web应用进行分离部署,先后端之间使用HTTP或者其余协议进行交互请求。在先后端分离架构中,后端只须要负责按照约定的数据格式向前端提供可调用的API服务便可。先后端之间经过HTTP请求进行交互,前端获取到数据后,进行页面的组装和渲染,最终返回给浏览器。前端
从目前应用软件开发的发展趋势来看,主要有两方面须要注意:python
愈来愈注重用户体验,随着互联网的发展,开始多终端化。程序员
大型应用架构模式正在向云化、微服务化发展。web
咱们主要经过先后端分离架构,为咱们带来如下四个方面的提高:django
为优质产品打造精益团队
经过将开发团队先后端分离化,让先后端工程师只须要专一于前端或后端的开发工做,是的先后端工程师实现自治,培养其独特的技术特性,而后构建出一个全栈式的精益开发团队。json
提高开发效率
先后端分离之后,能够实现先后端代码的解耦,只要先后端沟通约定好应用所需接口以及接口参数,即可以开始并行开发,无需等待对方的开发工做结束。与此同时,即便需求发生变动,只要接口与数据格式不变,后端开发人员就不须要修改代码,只要前端进行变更便可。如此一来整个应用的开发效率必然会有质的提高。后端
完美应对复杂多变的前端需求
若是开发团队能完成先后端分离的转型,打造优秀的先后端团队,开发独立化,让开发人员作到专一专精,开发能力必然会有所提高,可以完美应对各类复杂多变的前端需求。api
加强代码可维护性
先后端分离后,应用的代码再也不是先后端混合,只有在运行期才会有调用依赖关系。 浏览器
应用代码将会变得整洁清晰,不管是代码阅读仍是代码维护都会比之前轻松。
在Django中,有一个个的应用(app),好比admin、form、contenttype等,rest_framework也是一个应用,只不过这个应用是基于restful协议实现的。经过一些接口实现对数据快速的增删改查。这套应用主要是经过发送不一样的请求方式,来实现对数据的增删改查。
以book为例:
url 请求方式 相关操做
/books/ get 查看书籍
/books/ post 添加书籍
/books/1/ put/patch 修改书籍
/books/1/ delete 删除书籍
备注:put与patch请求的区别:都是编辑修改,put是总体修改,patch是局部的修改。
这样的设计风格是应用于CBV的(class based view),因此在了解rest_framework以前,咱们先了解Django中是如何经过CBV实现路由的分发。
首先,从路由出发,在每个url后面,对应的会执行一个as_view()的类方法,在Django启动的时候 ,会直接执行,返回一个视图函数。
#cbv形式的url urlpatterns = [ url(r'^login/',views.Login.as_view()), ]
首先,从Login这个类中找as_view()方法,没有就从父类中找,继承View,在父类中有一个as_view()的类方法。
class View(object): """ Intentionally simple parent class for all views. Only implements dispatch-by-method and simple sanity checking. """ http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] def __init__(self, **kwargs): """ Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things. """ # Go through keyword arguments, and either save their values to our # instance, or raise an error. for key, value in six.iteritems(kwargs): setattr(self, key, value) @classonlymethod def as_view(cls, **initkwargs): """ Main entry point for a request-response process. """ for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs # 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=()) return view
咱们看看as_view()中都实现了什么?返回一个view,view是as_view()内部的一个函数,因此当用户访问url时,直接执行这个view(),返回一个self.dispatch(request, *args, **kwargs),首先在本类中找,没有这样一个方法,从父类View中找,
class View(object): def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. 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 return handler(request, *args, **kwargs)
咱们来瞅瞅这个dispatch方法中都干了什么?
首先,先判断请求方式在不在http_method_names这个列表中,若是在,就经过反射,获得一个handler(以请求方式小写的形式的函数),而后调用这个handler(),因此,在View函数中,dispatch方法实现了按照请求方式分发的功能。
咱们发送get请求,对应的就执行类中的get()方法,也就是发送什么请求,就对应执行对应的方法。经过这种方式,咱们直接在对应的方法下实现对应的视图函数便可。
这就是django内部的视图类的路由分发的实现方式,说这么多,对于咱们的rest_framework有什么用呢?很直白的告诉你,rest_framework就是基于这种方式二次封装,从而实现经过请求方式来对对应数据的增删改查。
了解rest_framework以前,首先,要下载rest_framework:
pip install djangorestframework
既然是Django的一个应用,就要先将这个应用添加到settings.py中的INSTALLED_APPS中:
INSTALLED_APPS = ( ... 'rest_framework', )
在深刻了解rest_framework以前,咱们先快速实现一个基于rest_framework的示例:
urls.py
from django.conf.urls import url, include from rest_framework import routers from framework import views # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() router.register(r'users', views.UserViewSet) router.register(r'books',views.BookViewSet) # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ url(r'^', include(router.urls)), url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) ]
app下的models.py
from django.db import models # Create your models here. class Book(models.Model): title = models.CharField(max_length=32) def __str__(self): return self.title
app下的views.py
from django.contrib.auth.models import User from rest_framework import serializers, viewsets from framework import models # Serializers define the API representation. class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ('url', 'username', 'email', 'is_staff') # ViewSets define the view behavior. class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer # Book 模型 class BookSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = models.Book fields = ['url','title'] class BookViewSet(viewsets.ModelViewSet): queryset = models.Book.objects.all() serializer_class = BookSerializer
这样,咱们就实现了一个书籍的增删改查,启动Django,访问http://127.0.0.1:8000/,就会出现下面这个页面:
固然,这个页面不是咱们写的,是rest_framework内置的一个页面。这是rest_framework提供给咱们用做测试的页面。
小技巧:访问rest_framework中对应的url时,在路径后面拼上?format=json,咱们能够获得该数据的一个json字符串形式的列表。
使用rest_framework,咱们并无返回一个HTML页面,只是返回了一堆json格式的字符串。没错,这就是restful协议的实现方式,经过不一样的请求返回固定格式的数据,从而实现先后端分离,这就是restful所要实现的。
django的rest_framework组件实现了先后端的分离,后端只需提供相应接口便可,前端发送什么请求,就返回什么数据,从而使得先后端各司其职。
使用rest_framework在后端的开发中,确定会发送响应的请求测试数据,这里,咱们借助一个很NB的插件:Postman
经过rest_framework,能够快速的实现对一个模型表的增删改查,那它内部到底都帮咱们作了哪些事情?这个应用都实现了哪些功能呢?
首先,先导入
from rest_framework.views import APIView
看看这个APIView都实现了什么?
class APIView(View): @classmethod def as_view(cls, **initkwargs): """ Store the original class on the view function. This allows us to discover information about the view when we do URL reverse lookups. Used for breadcrumb generation. """ if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet): def force_evaluation(): raise RuntimeError( 'Do not evaluate the `.queryset` attribute directly, ' 'as the result will be cached and reused between requests. ' 'Use `.all()` or call `.get_queryset()` instead.' ) cls.queryset._fetch_all = force_evaluation view = super(APIView, cls).as_view(**initkwargs) view.cls = cls view.initkwargs = initkwargs # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt. return csrf_exempt(view)
很明显,APIView继承了View,而且从新封装了as_view()方法,返回了view,这么一看,好像跟 父类View中的as_view()方法没什么特大的区别,只是作了一些扩展。rest_framework真正NB的地方是下面这个方法的封装,在父类View中,view返回self.dispatch(),APIView重写了dispatch方法,意义重大。
class APIView(View): def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ 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
备注:在try中的self.initial(request,...)执行了auth组件,权限组件,频率组件。
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler. """ self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted self.perform_authentication(request) #auth self.check_permissions(request) # 权限 self.check_throttles(request) #频率
这个dispatch方法中,try里面的内容跟父类中view的实现如出一辙,也是实现了一个分发,没有多大的意义,可是,在try上面,
request = self.initialize_request(request, *args, **kwargs)
self.request = request
这两行代码,重中之重,实现了对request方法的封装和重写。
为何要重写request呢?
由于在Django中,信息的解析与封装是经过wsgiref这个模块实现的,这个模块有一个缺陷。封装的request.POST方法不支持json格式的字符串,也就是说这个模块中没有提供json格式的解析器。而先后端的分离,就须要一种先后端都支持的数据格式,那就是json,而这刚好是wsgiref所没法实现的,要实现先后端分离,必需要对request方法重写。
rest_framework是如何实现request的重写的。
那咱们看看self.initialize_request(request, *args, **kwargs)里面都实现了那些东西?
from rest_framework.request import Request class APIView(View): def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context )
这个方法return了一个类Request的实例对象,这个Request类传入旧的request,对旧的equest进行了一系列的加工,返回新的request对象
class Request(object): """ Wrapper allowing to enhance a standard `HttpRequest` instance. Kwargs: - request(HttpRequest). The original request instance. - parsers_classes(list/tuple). The parsers to use for parsing the request content. - authentication_classes(list/tuple). The authentications used to try authenticating the request's user. """ def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None): assert isinstance(request, HttpRequest), ( 'The `request` argument must be an instance of ' '`django.http.HttpRequest`, not `{}.{}`.' .format(request.__class__.__module__, request.__class__.__name__) ) self._request = request self.parsers = parsers or () self.authenticators = authenticators or () self.negotiator = negotiator or self._default_negotiator() self.parser_context = parser_context self._data = Empty self._files = Empty self._full_data = Empty self._content_type = Empty self._stream = Empty
初始化的过程当中,咱们能够获得一些对咱们有用的信息,咱们能够经过这个类Request的实例对象,也就是新的request,经过
request._request 就能够获得咱们旧的request。
class Request(object):
@property def query_params(self): """ More semantically correct name for request.GET. """ return self._request.GET @property def data(self): if not _hasattr(self, '_full_data'): self._load_data_and_files() return self._full_data
因此,咱们能够经过这个新的request.query_params 获得旧的request.GET的值(这个方法没啥卵用),最关键的是request.data这个方式,能够获得序列化后的数据,这个方法补全了旧request的不足。
备注:在request.data中,提供了不少数据类型的解析器,包括json的,因此,对于提交的数据,咱们能够直接经过这个方法获取到。而不须要经过request.POST。
那么咱们接下来讲说rest_framework的下一个组件-------序列化组件
开发咱们的Web API的第一件事是为咱们的Web API提供一种将代码片断实例序列化和反序列化为诸如json
之类的表示形式的方式。
咱们在使用json序列化时,有这么几种方式:
第一种,借助Python内置的模块j。手动构建一个字典,经过json.dumps(obj)获得一个json格式的字符串,使用这种方式有必定的局限性,那就是咱们没法控制这张表中的字段,有多少个字段,咱们就须要添加多少个键值对,一旦后期表结构发生变化,就会很麻烦。
第二种方式:解决上述方式中字段的不可控性,就须要借助Django中内置的一个方法model_to_dict,(from django.forms.models import model_to_dict),咱们能够将取出的全部的字段循环,依次将每一个对象传入model_to_dict中,这样就解决了字段的问题。
还有一种方式,也是Django提供的一个序列化器:from django.core.serializers import serialize ,咱们能够直接将queryset数据类型直接传进去,而后指定咱们要转化的格式便可,这两种Django提供给咱们的方法虽然能够解决这个序列化字段的问题,可是有一个缺点,那就是咱们能够直接将一个数据转化为json字符串形式,可是却没法反序列化为queryset类型的数据。
rest_framework提供了一个序列化组件---serializers,完美的帮助咱们解决了上述的问题:from rest_framework import serializers ,用法跟Django中forms组件的用法很是类似,也须要先自定制一个类,而后这个类必须继承serializers.Serializer,而后咱们须要序列化那些字段就在这个类中配置那些字段,还能够自定制字段的展现格式,很是的灵活。
models部分:
from django.db import models # Create your models here. class Book(models.Model): title=models.CharField(max_length=32) price=models.IntegerField() pub_date=models.DateField() publish=models.ForeignKey("Publish") authors=models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name=models.CharField(max_length=32) email=models.EmailField() def __str__(self): return self.name class Author(models.Model): name=models.CharField(max_length=32) age=models.IntegerField() def __str__(self): return self.name
views部分:
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.Serializer): title=serializers.CharField(max_length=32) price=serializers.IntegerField() pub_date=serializers.DateField() publish=serializers.CharField(source="publish.name") #authors=serializers.CharField(source="authors.all") authors=serializers.SerializerMethodField() def get_authors(self,obj): temp=[] for author in obj.authors.all(): temp.append(author.name) return temp class BookViewSet(APIView): def get(self,request,*args,**kwargs): book_list=Book.objects.all() # 序列化方式1: # from django.forms.models import model_to_dict # import json # data=[] # for obj in book_list: # data.append(model_to_dict(obj)) # print(data) # return HttpResponse("ok") # 序列化方式2: # data=serializers.serialize("json",book_list) # return HttpResponse(data) # 序列化方式3: bs=BookSerializers(book_list,many=True) return Response(bs.data)
值得注意的时,咱们使用这种方式序列化时,须要先实例化一个对象,而后在传值时,若是值为一个queryset对象时,须要指定一个参数many=True,若是值为一个obj时,不须要指定many=False,默认为False。
#咱们自定义一个类Bookserialize,继承serializers.Serializer from framework.views import Bookserialize from framework import models book_list = models.Book.objects.all() bs= Bookserialize(book_list,many=True) bs.data [OrderedDict([('title', '神墓')]), OrderedDict([('title', '完美世界')])] # queryset对象 结果为一个列表,里面放着每个有序字典 book_obj = models.Book.objects.first() bs_ = Bookserialize(book_obj) bs_.data {'title': '神墓'} # 若是为一个对象时,结果为一个字典
特别须要注意的是:使用这种方式序列化时,对于特殊字段(一对多ForeignKey、多对多ManyToMany),serializers没有提供对应的字段,须要指定特殊的方式,由于obj.这个字段时,获得的是一个对象,因此咱们对于FK,须要使用一个CharField字段,而后在这个字段中指定一个source属性,指定显示这个对象的那个字段。一样的,对于多对多的字段,咱们也要使用特殊的显示方式:SerializerMethodField(),指定为这种字段类型时,显示的结果为一个自定义的函数的返回值,这个自定义函数的名字必须是get_字段名,固定写法,接收一个obj对象,返回值就是该字段在序列化时的显示结果。
另外,咱们在取值时,直接经过这个对象.data的方式取值,这是rest_framework提供给咱们的序列化接口。
其实。咱们应该明白它内部的实现方式:若是值为一个queryset对象,就建立一个list,循环这个queryset获得每个数据对象,而后在循环配置类下面的每个字段,直接这个对象obj.字段 得出值,添加到一个字典中,在将这个字典添加到这个列表中。因此,对于这些特殊字段,咱们取值时,经过这种方式获得的是一个对象。
经过这种方式就会出现一个问题,咱们每序列化一个表,就要将这个表中的字段所有写一遍,这样显得很麻烦。在Djangoforms组件中,有一个ModelForm,能够帮咱们将咱们模型表中的全部字段转化为forms组件中对应的字段,一样的,在serializers中,一样有一个,能够帮咱们将咱们的模型类转化为serializers中对应的字段。这个组件就是ModelSerializer。
class BookSerializers(serializers.ModelSerializer): class Meta: model=Book fields="__all__" depth=1
备注:
1.定义一个depth=1 会将特殊字段 FK和M2M中每一个对象的全部字段所有取出来。
2.对于FK字段,显示的是主键值,对于多对多字段,默认显示方式为:[pk1,pk2,...],一个列表中,包含全部字段对象的主键值。若是咱们不想显示主键值,能够重写对应字段属性。
class BookModelSerializers(ModelSerializer): class Meta: model=Book fields="__all__" authors=serializers.SerializerMethodField() def get_authors(self,obj): temp=[] for obj in obj.authors.all(): temp.append(obj.name) return temp
3.对于含有choices的字段,咱们能够经过指定字段的source来显示展现的值
好比:course_class = models.Integerfield(choices=((1,'初级'),(2,'中级')))
course_class = serializers.CharField(source='get_course_class_display')
def post(self,request,*args,**kwargs): 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)
备注:跟form组件相似,若是校验不经过,能够经过这个对象.errors将错误信息返回。
class BookSerializers(serializers.ModelSerializer): class Meta: model=Book fields="__all__" # exclude = ['authors',] # depth=1 def create(self, validated_data): authors = validated_data.pop('authors') obj = Book.objects.create(**validated_data) obj.authors.add(*authors) return obj
class BookDetailViewSet(APIView): def get(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj) return Response(bs.data) def put(self,request,pk): book_obj=Book.objects.filter(pk=pk).first() bs=BookSerializers(book_obj,data=request.data) if bs.is_valid(): bs.save() return Response(bs.data) else: return HttpResponse(bs.errors)
class BookSerializers(serializers.ModelSerializer): publish= serializers.HyperlinkedIdentityField(
view_name='publish_detail',
lookup_field="publish_id",
lookup_url_kwarg="pk") class Meta: model=Book fields="__all__" #depth=1
urls部分:
1
2
3
4
5
6
|
urlpatterns
=
[
url(r
'^books/$'
, views.BookViewSet.as_view(),name
=
"book_list"
),
url(r
'^books/(?P<pk>\d+)$'
, views.BookDetailViewSet.as_view(),name
=
"book_detail"
),
url(r
'^publishers/$'
, views.PublishViewSet.as_view(),name
=
"publish_list"
),
url(r
'^publishers/(?P<pk>\d+)$'
, views.PublishDetailViewSet.as_view(),name
=
"publish_detail"
),
]
|
restframework中的APIView组件,弥补了Django中对于json数据格式的支持,经过先后端都支持的json,完美的实现了先后端分离,使得后端只须要提供数据接口便可,并且restframework组件中的serializers组件,提供了更快捷、更灵活的序列化支持。下个系列中,将主要阐述restframework对视图函数的三层封装,感觉一下视图三部曲的神奇。