rest-framework框架——解析器、ur控制、分页、响应器、渲染器
一、解析器(parser)
解析器在reqest.data取值的时候才执行。
对请求的数据进行解析:是针对请求体进行解析的。表示服务器可以解析的数据格式的种类。
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):....
1、django的request类源码解析
(1)django中发送请求对比
#如果是urlencoding格式发送的数据,在POST里面有值 Content-Type: application/url-encoding..... request.body request.POST #如果是发送的json格式数据,在POST里面是没有值的,在body里面有值,可通过decode,然后loads取值 Content-Type: application/json..... request.body request.POST
(2)关于decode和encode
浏览器发送过来是字节需要先解码 ---> decode 如:s=‘中文‘
如果是在utf8的文件中,该字符串就是utf8编码,如果是在gb2312的文件中,则其编码为gb2312。这种情况下,要进行编码转换,都需要先用 decode方法将其转换成unicode编码,再使用encode方法将其转换成其他编码。通常,在没有指定特定的编码方式时,都是使用的系统默认编码创建的代码文件。 如下:
s.decode(‘utf-8‘).encode(‘utf-8‘)
decode():是解码 --->把字节变成字符串 encode()是编码---->把字符串变成字节
(3)查看django中WSGIRequest解析方法
# 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这两种类型。
因此在django中传json数据默认是urlencoded解析到request中:body取到json数据时,取到的数据时字节,需要先decode解码,将字节变成字符串。
request.body.decode("utf8") json.loads(request.body.decode("utf8"))
为了传输json数据每次都要decode\loads,比较麻烦因此才有了解析器解决这个问题。
2、rest-framework的request类源码解析
在rest-framework中 是以利用Request类进行数据解析。
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
3、局部视图parser
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)
4、全局视图parser
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',] }
二、url路由控制
因为我们使用的是视图集而不是视图,我们可以通过简单地将视图集注册到router类来为我们的API自动生成URL conf。
同样,如果我们需要对API URL有更多的控制,我们可以直接使用常规的基于类的视图,并显式地编写URL conf。
最后,我们将默认登录和注销视图包含在可浏览API中。这是可选的,但如果您的API需要身份验证,并且希望使用可浏览的API,那么这是有用的。
1、全自动路由示例
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"), ]
视图的内容不需要任何变动即生效:
2、DRF路由组件使用
路由传参写的特别多,但是框架将这些也已经封装好了。
修改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写,当不需要那么多路由的时候,不要用这种路由注册,否则会对外暴露过多的接口,会存在风险。总之,一切按照业务需要去用。
三、分页组件(Pagination)
REST框架支持自定义分页风格,你可以修改每页显示数据集合的最大长度。
分页链接支持以下两种方式提供给用户:
- 分页链接是作为响应内容提供给用户
- 分页链接被包含在响应头中(Content-Range或者Link)
内建风格使用作为响应内容提供给用户。这种风格更容易被使用可浏览API的用户所接受。
如果使用通用视图或者视图集合。系统会自动帮你进行分页。
如果使用的是APIView,你就需要自己调用分页API,确保返回一个分页后的响应。可以将pagination_class设置为None关闭分页功能。
1、设置分页风格
可以通过设置DEFAULT_PAGINATION_CLASS和PAGE_SIZE,设置全局变量。
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': 100 }
需要同时设置pagination class和page size。 也可以在单个视图中设置pagination_class属性,但一般你需要使用统一的分页风格。
2、修改分页风格
如果你需要修改分页风格 ,需要重写分页类,并设置你需要修改的属性。
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 # 在视图中使用pagination_class属性调用该自定义类
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):.....
(1)设置max_page_size是限制直接在页面访问时最大的数据条数显示
虽然总共有4条数据,页面访问get请求时?page=1&size=100但是依然只能拿到max_page_size限制拿到的3条。
3、根据页码分页——PageNumberPagination
这个分页样式接受请求查询参数中的一个数字页面号。
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
更多配置属性:
- django_paginator_class 使用Django分页类。默认为django.core.paginator.Paginator,适用于大多数情况
- page_size 用来显示每页显示对象的数量,如果设置了就重写PAGE_SIZE设置。
- page_query_param 页面查询参数,一个字符串值,指示用于分页控件的查询参数的名称。
- page_size_query_param 该参数允许客户端根据每个请求设置页面大小。一般默认设置为None.
- max_page_size 只有设置了page_size_query_param参数,该参数才有意义,为客户端请求页面中能够显示的最大数量
- last_page_strings 用于存储使用page_query_param参数请求过的值列表或元组,默认为(‘last’,)
- template 用来在可浏览API中,渲染分页的模板(html)名字,可以重写分页样式,或者设置为None,禁用分页。默认为”rest_framework/pagination/numbers.html”。
4、根据位置和个数分页——LimitOffsetPagination
这种分页样式与查找多个数据库记录时使用的语法类似。客户端包括一个”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类)配置:
- default_limit: 如果客户端没有提供,则默认使用与PAGE_SIZE值一样。
- limit_query_param:表示限制查询参数的名字,默认为’limit’
- offset_query_param:表示偏移参数的名字, 默认为’offset’
- max_limit:允许页面中显示的最大数量,默认为None
- template: 渲染分页结果的模板名,默认为”rest_framework/pagination/numbers.html”.
5、游标分页——CursorPagination
基于游标的分页显示了一个不透明的“cursor”指示器,客户端可以使用它来浏览结果集。这种分页方式只允许用户向前或向后进行查询。并且不允许客户端导航到任意位置。
基于游标的分页要求在结果集中有一个惟一的、不变的条目顺序。这个排序通常是记录上的一个创建时间戳,用来表示分页的顺序。
基于游标的分页比其他方案更复杂。它还要求结果集给出一个固定的顺序,并且不允许客户端任意地对结果集进行索引,但是它确实提供了以下好处:
- 提供一致的分页视图。当使用正确的指针分页时,即使在分页过程中其他客户端插入新项时,客户端也不会在分页时看到同一个项两次。
- 支持使用非常大的数据集。大量数据集使用基于off-set的分页方式可能会变得低效或不可用。基于指针的分页模式有固定的时间属性,并且随着数据集的大小的增加而不会减慢。
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"
6、设置全局配置
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 }
显示效果:
四、响应器(response)
五、渲染器
规定页面显示的效果(无用)。
1、局部添加渲染器
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('...')
显示效果:
2、全局添加渲染器
settings.py:
# 渲染器配置到全局 REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer','rest_framework.renderers.BrowsableAPIRenderer'] }
显示效果同上。