解析器在reqest.data取值的時候才執行。html
對請求的數據進行解析:是針對請求體進行解析的。表示服務器能夠解析的數據格式的種類。python
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser, FileUploadParser """ 默認得是 JSONParser FormParser MultiPartParser """ class BookView(APIView): # authentication_classes = [TokenAuth, ] parser_classes = [FormParser, JSONParser] def get(self, request):....
#若是是urlencoding格式發送的數據,在POST裏面有值 Content-Type: application/url-encoding..... request.body request.POST #若是是發送的json格式數據,在POST裏面是沒有值的,在body裏面有值,可經過decode,而後loads取值 Content-Type: application/json..... request.body request.POST
瀏覽器發送過來是字節須要先解碼 ---> decode 如:s=‘中文‘數據庫
若是是在utf8的文件中,該字符串就是utf8編碼,若是是在gb2312的文件中,則其編碼爲gb2312。這種狀況下,要進行編碼轉換,都須要先用 decode方法將其轉換成unicode編碼,再使用encode方法將其轉換成其餘編碼。一般,在沒有指定特定的編碼方式時,都是使用的系統默認編碼建立的代碼文件。 以下: django
s.decode(‘utf-8‘).encode(‘utf-8‘) json
decode():是解碼 --->把字節變成字符串 encode()是編碼---->把字符串變成字節後端
# 1:導入django的類 from django.core.handlers.wsgi import WSGIRequest # 2: class WSGIRequest(http.HttpRequest): def _get_post(self): if not hasattr(self, ‘_post‘): self._load_post_and_files() return self._post # 3:self._load_post_and_files()從這裏找到django解析的方法 def _load_post_and_files(self): """Populate self._post and self._files if the content-type is a form type""" if self.method != ‘POST‘: self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict() return if self._read_started and not hasattr(self, ‘_body‘): self._mark_post_parse_error() return if self.content_type == ‘multipart/form-data‘: if hasattr(self, ‘_body‘): # Use already read data data = BytesIO(self._body) else: data = self try: self._post, self._files = self.parse_file_upload(self.META, data) except MultiPartParserError: # An error occurred while parsing POST data. Since when # formatting the error the request handler might access # self.POST, set self._post and self._file to prevent # attempts to parse POST data again. # Mark that an error occurred. This allows self.__repr__ to # be explicit about it instead of simply representing an # empty POST self._mark_post_parse_error() raise elif self.content_type == ‘application/x-www-form-urlencoded‘: self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() else: self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
從if self.content_type == ‘multipart/form-data‘:和 self.content_type == ‘application/x-www-form-urlencoded‘: 能夠知道django只解析urlencoded‘和form-data這兩種類型。api
所以在django中傳json數據默認是urlencoded解析到request中:body取到json數據時,取到的數據時字節,須要先decode解碼,將字節變成字符串。瀏覽器
request.body.decode("utf8") json.loads(request.body.decode("utf8"))
爲了傳輸json數據每次都要decode\loads,比較麻煩所以纔有瞭解析器解決這個問題。服務器
在rest-framework中 是以利用Request類進行數據解析。app
1:找到apiview class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES # 解析器 2:找api_settings沒有定義找默認 renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES 3:. api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS) 4:DEFAULTS DEFAULTS = { # Base API policies # 自帶的解析器 ‘DEFAULT_PARSER_CLASSES‘: ( ‘rest_framework.parsers.JSONParser‘, # 僅處理請求頭content-type爲application/json的請求體 ‘rest_framework.parsers.FormParser‘, # 僅處理請求頭content-type爲application/x-www-form-urlencoded 的請求體 ‘rest_framework.parsers.MultiPartParser‘ # 僅處理請求頭content-type爲multipart/form-data的請求體 ),
注意除了上面三種以外還有一個專門處理文件上傳:
from rest_framework.parsers import FileUploadParser
from rest_framework.parsers import JSONParser,FormParser class PublishViewSet(generics.ListCreateAPIView): parser_classes = [FormParser,JSONParser] queryset = Publish.objects.all() serializer_class = PublshSerializers def post(self, request, *args, **kwargs): print("request.data",request.data) return self.create(request, *args, **kwargs)
REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",], "DEFAULT_THROTTLE_RATES":{ "visit_rate":"5/m", }, "DEFAULT_PARSER_CLASSES":['rest_framework.parsers.FormParser',] }
由於咱們使用的是視圖集而不是視圖,咱們能夠經過簡單地將視圖集註冊到router類來爲咱們的API自動生成URL conf。
一樣,若是咱們須要對API URL有更多的控制,咱們能夠直接使用常規的基於類的視圖,並顯式地編寫URL conf。
最後,咱們將默認登陸和註銷視圖包含在可瀏覽API中。這是可選的,但若是您的API須要身份驗證,而且但願使用可瀏覽的API,那麼這是有用的。
from django.contrib import admin from django.urls import path, re_path, include from rest_framework import routers from app01 import views routers = routers.DefaultRouter() # 實例化 routers.register("authors", views.AuthorViewSet) # 註冊某一個視圖 urlpatterns = [ path('admin/', admin.site.urls), ... # as_view參數指定什麼請求走什麼方法 # re_path(r'^authors/$', views.AuthorViewSet.as_view({"get": "list", "post": "create"}), name="author_list"), # re_path(r'^authors/(?P<pk>\d+)/$', views.AuthorViewSet.as_view({ # 'get': 'retrieve', # 'put': 'update', # 'patch': 'partial_update', # 'delete': 'destroy' # }), name="author_detail"), # 改寫以下: re_path(r"", include(routers.urls)), re_path(r'^login/$', views.LoginView.as_view(), name="login"), ]
視圖的內容不須要任何變更即生效:
路由傳參寫的特別多,可是框架將這些也已經封裝好了。
修改DRFDemo/urls.py文件以下所示:
from django.urls import path, include from .views import BookView, BookEditView, BookModelViewSet from rest_framework.routers import DefaultRouter router = DefaultRouter() # 路由實例化 # 第一個參數是路由匹配規則,這裏的路由是分發下來的,所以能夠不作設置;第二個參數是視圖 router.register(r"", BookModelViewSet) urlpatterns = [ # path('list', BookView.as_view()), # 查看全部的圖書 # 注意url中參數命名方式,2.0以前的寫法:'retrieve/(?P<id>\d+)' # 2.0以後的寫法:<>內聲明類型,冒號後面跟着關鍵字參數 # path('retrieve/<int:id>', BookEditView.as_view()) # 單條數據查看 # path('list', BookModelViewSet.as_view({"get": "list", "post": "create"})), # path('retrieve/<int:id>', BookModelViewSet.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})) ] urlpatterns += router.urls # router.urls是自動生成帶參數的路由
可是須要自定製的時候仍是須要咱們本身用APIView寫,當不須要那麼多路由的時候,不要用這種路由註冊,不然會對外暴露過多的接口,會存在風險。總之,一切按照業務須要去用。
REST框架支持自定義分頁風格,你能夠修改每頁顯示數據集合的最大長度。
分頁連接支持如下兩種方式提供給用戶:
內建風格使用做爲響應內容提供給用戶。這種風格更容易被使用可瀏覽API的用戶所接受。
若是使用通用視圖或者視圖集合。系統會自動幫你進行分頁。
若是使用的是APIView,你就須要本身調用分頁API,確保返回一個分頁後的響應。能夠將pagination_class設置爲None關閉分頁功能。
能夠經過設置DEFAULT_PAGINATION_CLASS和PAGE_SIZE,設置全局變量。
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': 100 }
須要同時設置pagination class和page size。 也能夠在單個視圖中設置pagination_class屬性,但通常你須要使用統一的分頁風格。
若是你須要修改分頁風格 ,須要重寫分頁類,並設置你須要修改的屬性。
from rest_framework.views import APIView
from rest_framework.response import Response
from app01.serializer import *
from .utils import *
#分頁
from rest_framework.pagination import PageNumberPagination
class MyPageNumberPagination(PageNumberPagination):
# 默認每頁顯示的數據條數
page_size = 2
# 獲取URL參數中設置的每頁顯示數據條數
page_size_query_param = 'size'
# 獲取URL參數中傳入的頁碼key
page_query_param = 'page'
# 最大支持的每頁顯示的數據條數(對這個進行限制127.0.0.1:8000/books/?page=1&size=100)
max_page_size = 3
class BookView(APIView):
# authentication_classes = [TokenAuth, ]
# parser_classes = [FormParser, JSONParser]
pagination_class = MyPageNumberPagination #
def get(self, request):
book_list = Book.objects.all() # queryset
# 實例化分頁對象,獲取數據庫中的分頁數據
pnp = MyPageNumberPagination()
books_page = pnp.paginate_queryset(book_list, request, self)
# 序列化對象
bs = BookModelSerializers(books_page, many=True, context={"request": request}) # 序列化結果
# return Response(bs.data)
return bs.get_paginated_response(bs.data) # 生成分頁和數據
def post(self, request):.....
在視圖中使用pagination_class屬性調用該自定義類
雖然總共有4條數據,頁面訪問get請求時?page=1&size=100可是依然只能拿到max_page_size限制拿到的3條。
這個分頁樣式接受請求查詢參數中的一個數字頁面號。
GET https://api.example.org/accounts/?page=4
響應對象:
HTTP 200 OK { "count": 1023 "next": "https://api.example.org/accounts/?page=5", "previous": "https://api.example.org/accounts/?page=3", "results": [ … ] }
繼承了APIView的視圖,也能夠設置pagination_class屬性選擇PageNumberPagination
class MyPageNumberPagination(PageNumberPagination): # 默認每頁顯示的數據條數 page_size = 1 # 獲取URL參數中設置的每頁顯示數據條數 page_size_query_param = 'size' # 獲取URL參數中傳入的頁碼key page_query_param = 'page' # 最大支持的每頁顯示的數據條數(對這個進行限制127.0.0.1:8000/books/?page=1&size=100) max_page_size = 3
更多配置屬性:
這種分頁樣式與查找多個數據庫記錄時使用的語法相似。客戶端包括一個」limit」和一個 「offset」查詢參數。該限制表示返回的條目的最大數量,而且與page_size大小相同。偏移量表示查詢的起始位置,與完整的未分頁項的集合有關。
GET https://api.example.org/accounts/?limit=100&offset=400 HTTP 200 OK { "count": 1023 "next": "https://api.example.org/accounts/?limit=100&offset=500", "previous": "https://api.example.org/accounts/?limit=100&offset=300", "results": [ … ] }
這種也能夠設置PAGE_SIZE,而後客戶端就能夠設置limit參數了。
繼承了GenericAPIView的子類,能夠經過設置pagination_class屬性爲LimitOffsetPagination使用
class MyLimitOffsetPagination(LimitOffsetPagination): # 默認每頁顯示的數據條數 default_limit = 1 # URL中傳入的顯示數據條數的參數 limit_query_param = 'limit' # URL中傳入的數據位置的參數 offset_query_param = 'offset' # 最大每頁顯得條數 max_limit = None
(重寫LimitOffsetPagination類)配置:
基於遊標的分頁顯示了一個不透明的「cursor」指示器,客戶端可使用它來瀏覽結果集。這種分頁方式只容許用戶向前或向後進行查詢。而且不容許客戶端導航到任意位置。
基於遊標的分頁要求在結果集中有一個唯一的、不變的條目順序。這個排序一般是記錄上的一個建立時間戳,用來表示分頁的順序。
基於遊標的分頁比其餘方案更復雜。它還要求結果集給出一個固定的順序,而且不容許客戶端任意地對結果集進行索引,可是它確實提供瞭如下好處:
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination class MyCursorPagination(CursorPagination): # URL傳入的遊標參數 cursor_query_param = 'cursor' # 默認每頁顯示的數據條數 page_size = 2 # URL傳入的每頁顯示條數的參數 page_size_query_param = 'page_size' # 每頁顯示數據最大條數 max_page_size = 1000 # 根據ID從大到小排列 ordering = "id"
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination', 'PAGE_SIZE': 100 }
顯示效果:
規定頁面顯示的效果(無用)。
urls.py:
from django.conf.urls import url, include from api.views import course urlpatterns = [ # path('admin/', admin.site.urls), url(r'^api/course/$', course.CourseView.as_view()), ]
api/views/course.py:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer class CourseView(APIView): # 渲染器 # renderer_classes = [JSONRenderer] # 表示只返回json格式 renderer_classes = [JSONRenderer, BrowsableAPIRenderer] # 數據嵌套在html中展現 def get(self, request, *args, **kwargs): return Response('...')
顯示效果:
settings.py:
# 渲染器配置到全局 REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer','rest_framework.renderers.BrowsableAPIRenderer'] }
顯示效果同上。
API版本控制容許咱們在不一樣的客戶端之間更改行爲(同一個接口的不一樣版本會返回不一樣的數據)。
DRF提供了許多不一樣的版本控制方案。可能會有一些客戶端由於某些緣由再也不維護了,可是咱們後端的接口還要不斷的更新迭代,這個時候經過版本控制返回不一樣的內容就是一種不錯的解決方案。
DRF提供了五種版本控制方案以下所示:
from rest_framework import versioning # view中引入版本控制 # 查看 rest_framework/versioning.py文件: class BaseVersioning:... class AcceptHeaderVersioning(BaseVersioning): # 將版本信息放到請求頭中 """ GET /something/ HTTP/1.1 Host: example.com Accept: application/json; version=1.0 """ class URLPathVersioning(BaseVersioning): # 將版本信息放入URL中 """ To the client this is the same style as `NamespaceVersioning`. The difference is in the backend - this implementation uses Django's URL keyword arguments to determine the version. An example URL conf for two views that accept two different versions. urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'), url(r'^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail') ] GET /1.0/something/ HTTP/1.1 Host: example.com Accept: application/json """ class NamespaceVersioning(BaseVersioning): # 經過namespace來區分版本 """ To the client this is the same style as `URLPathVersioning`. The difference is in the backend - this implementation uses Django's URL namespaces to determine the version. An example URL conf that is namespaced into two separate versions # users/urls.py urlpatterns = [ url(r'^/users/$', users_list, name='users-list'), url(r'^/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail') ] # urls.py urlpatterns = [ url(r'^v1/', include('users.urls', namespace='v1')), url(r'^v2/', include('users.urls', namespace='v2')) ] GET /1.0/something/ HTTP/1.1 Host: example.com Accept: application/json """ class HostNameVersioning(BaseVersioning): # 經過主機名來區分版本 """ GET /something/ HTTP/1.1 Host: v1.example.com Accept: application/json """ class QueryParameterVersioning(BaseVersioning): # 經過url查詢參數區分版本 """ GET /something/?version=0.1 HTTP/1.1 Host: example.com Accept: application/json """
class APIView(View): def dispatch(self, request, *args, **kwargs): 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)
class APIView(View): def initial(self, request, *args, **kwargs): # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme
能夠看到在這裏是有version的,也就是版本。
class APIView(View): def determine_version(self, request, *args, **kwargs): """ If versioning is being used, then determine any API version for the incoming request. Returns a two-tuple of (version, versioning_scheme) """ if self.versioning_class is None: return (None, None) scheme = self.versioning_class() return (scheme.determine_version(request, *args, **kwargs), scheme)
1)繼續查看versioning_class
class APIView(View): versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
由此可知就是配置了一個類。所以在determine_version中self.versioning_class()是作了一個實例化的動做。
2)return (scheme.determine_version(request, *args, **kwargs), scheme)返回了一個元組
若是在/views/course.py的視圖類中定義versioning_class:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import QueryParameterVersioning class CourseView(APIView): versioning_class = QueryParameterVersioning def get(self, request, *args, **kwargs): self.dispatch return Response('...')
則能夠實例化獲得scheme實例,並在函數返回語句中返回scheme。
3)進一步查看QueryParameterVersioning中的determine_version
class QueryParameterVersioning(BaseVersioning): """ GET /something/?version=0.1 HTTP/1.1 Host: example.com Accept: application/json """ invalid_version_message = _('Invalid version in query parameter.') def determine_version(self, request, *args, **kwargs): version = request.query_params.get(self.version_param, self.default_version) if not self.is_allowed_version(version): raise exceptions.NotFound(self.invalid_version_message) return version
這裏返回的是version,預計就是版本號。
4)進一步查看request.query_params定義
class Request(object): @property def query_params(self): """ More semantically correct name for request.GET. """ return self._request.GET
所以version = request.query_params.get(self.version_param, self.default_version)實際上是去URL中獲取GET傳來的version對應的參數。
5)查看version_param
class BaseVersioning(object): default_version = api_settings.DEFAULT_VERSION allowed_versions = api_settings.ALLOWED_VERSIONS version_param = api_settings.VERSION_PARAM
由此可知這是一個全局配置,默認值就等於version,由此可知前面返回的version就是版本號。
def initial(self, request, *args, **kwargs): # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme
version是版本,scheme是對象並分別賦值給request.version和request.scheme。
class CourseView(APIView): versioning_class = QueryParameterVersioning def get(self, request, *args, **kwargs): print(request.version) return Response('...')
1)直接訪問
此時python後臺輸出:none
2)用url參數傳遞版本
此時python後臺輸出:v1
class QueryParameterVersioning(BaseVersioning): """ GET /something/?version=0.1 HTTP/1.1 Host: example.com Accept: application/json """ invalid_version_message = _('Invalid version in query parameter.') def determine_version(self, request, *args, **kwargs): version = request.query_params.get(self.version_param, self.default_version) if not self.is_allowed_version(version): raise exceptions.NotFound(self.invalid_version_message) return version
能夠看到version拿到後,用self.is_allowed_version方法作了一個判斷。
class BaseVersioning(object): default_version = api_settings.DEFAULT_VERSION allowed_versions = api_settings.ALLOWED_VERSIONS version_param = api_settings.VERSION_PARAM def is_allowed_version(self, version): if not self.allowed_versions: return True return ((version is not None and version == self.default_version) or (version in self.allowed_versions))
能夠看到ALLOWED_VERSIONS也是存放在配置文件中。
# 渲染器配置到全局 REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer'], 'ALLOWED_VERSIONS': ['v1', 'v2'] # 容許的版本 }
settings.py作以下配置
# 渲染器配置到全局 REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer'], 'ALLOWED_VERSIONS': ['v1', 'v2'], # 容許的版本 'VERSION_PARAM': 'version', # 把默認的version修改成其餘參數:http://127.0.0.1:8000/api/course/?versionsss=v1 'DEFAULT_VERSION': 'v1', # 默認版本 }
python後臺輸出:v1。
前面都是在單個類中配置了版本控制,以下所示:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import QueryParameterVersioning class CourseView(APIView): versioning_class = QueryParameterVersioning def get(self, request, *args, **kwargs): self.dispatch return Response('...')
源碼查看到全局版本控制配置信息:
class APIView(View): versioning_class = api_settings.DEFAULT_VERSIONING_CLASS # Allow dependency injection of other settings to make testing easier. settings = api_settings
所以也能夠在settings.py中配置全局版本控制:
# 渲染器配置到全局 REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer'], 'ALLOWED_VERSIONS': ['v1', 'v2'], # 容許的版本 'VERSION_PARAM': 'version', # 把默認的version修改成其餘參數:http://127.0.0.1:8000/api/course/?versionsss=v1 'DEFAULT_VERSION': 'v1', # 默認版本 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning', # 全局版本控制 }
顯示效果以下:
前面寫的是基於url的get傳參方式,如:/users?version=v1,可是這種方式顯示版本不是最推薦的。通常須要把版本號寫在前面。改寫須要調整urls.py配置。
from django.conf.urls import url, include urlpatterns = [ # path('admin/', admin.site.urls), url(r'^api/', include('api.urls')), ]
from django.conf.urls import url, include from api.views import course urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/course/$', course.CourseView.as_view()), ]
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning class CourseView(APIView): versioning_class = URLPathVersioning def get(self, request, *args, **kwargs): print(request.version) return Response('...')
之後都推薦用這種方式寫版本,全局配置修改同上。
詳見:http://www.cnblogs.com/wupeiqi/articles/7805382.html
基於 accept 請求頭方式,如:Accept: application/json; version=1.0
基於主機名方法,如:v1.example.com
基於django路由系統的namespace,如:example.com/v1/users/