解析器在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/