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組件,權限組件,頻率組件。
這個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對視圖函數的三層封裝,感覺一下視圖三部曲的神奇。
做者:高賽出處:https://i.cnblogs.com/EditPosts.aspx?opt=1本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。