前言
Nodejs的逐漸成熟和日趨穩定,使得越來越多的公司開始嘗試使用Nodejs來練一下手,嘗一嘗鮮。在傳統的web應用開發中,大多數的程序員會將瀏覽器作為前后端的分界線。將瀏覽器中為用戶進行頁面展示的部分稱之為前端,而將運行在服務器,為前端提供業務邏輯和數據准備的所有代碼統稱為后端。
前后端分離是web應用的一種架構模式。在開發階段,前后端工程師約定好數據交互接口,實現並行開發和測試;在運行階段前后端分離模式需要對web應用進行分離部署,前后端之間使用HTTP或者其他協議進行交互請求。在前后端分離架構中,后端只需要負責按照約定的數據格式向前端提供可調用的API服務即可。前后端之間通過HTTP請求進行交互,前端獲取到數據后,進行頁面的組裝和渲染,最終返回給瀏覽器。
從目前應用軟件開發的發展趨勢來看,主要有兩方面需要注意:
-
越來越注重用戶體驗,隨着互聯網的發展,開始多終端化。
-
大型應用架構模式正在向雲化、微服務化發展。
我們主要通過前后端分離架構,為我們帶來以下四個方面的提升:
-
為優質產品打造精益團隊
通過將開發團隊前后端分離化,讓前后端工程師只需要專注於前端或后端的開發工作,是的前后端工程師實現自治,培養其獨特的技術特性,然后構建出一個全棧式的精益開發團隊。 -
提升開發效率
前后端分離以后,可以實現前后端代碼的解耦,只要前后端溝通約定好應用所需接口以及接口參數,便可以開始並行開發,無需等待對方的開發工作結束。與此同時,即使需求發生變更,只要接口與數據格式不變,后端開發人員就不需要修改代碼,只要前端進行變動即可。如此一來整個應用的開發效率必然會有質的提升。 -
完美應對復雜多變的前端需求
如果開發團隊能完成前后端分離的轉型,打造優秀的前后端團隊,開發獨立化,讓開發人員做到專注專精,開發能力必然會有所提升,能夠完美應對各種復雜多變的前端需求。 -
增強代碼可維護性
前后端分離后,應用的代碼不再是前后端混合,只有在運行期才會有調用依賴關系。
應用代碼將會變得整潔清晰,不論是代碼閱讀還是代碼維護都會比以前輕松。
rest_framework的簡單了解
在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實現路由的分發。
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之前,首先,要下載rest_framework:
pip install djangorestframework
既然是Django的一個應用,就要先將這個應用添加到settings.py中的INSTALLED_APPS中:
INSTALLED_APPS = ( ... 'rest_framework', )
使用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,可以快速的實現對一個模型表的增刪改查,那它內部到底都幫我們做了哪些事情?這個應用都實現了哪些功能呢?
組件一、APIView
首先,先導入
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的下一個組件-------序列化組件
組件二、serializers
開發我們的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。
組件三、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')
提交post請求
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將錯誤信息返回。
重寫save中的create方法
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
單條數據的get和put請求
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)
超鏈接API:Hyperlinked
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對視圖函數的三層封裝,感受一下視圖三部曲的神奇。