【笔记1】drf框架 APIView,Request,Response使用及源码解析

65 阅读12分钟

drf简介

Django REST Framework是一个建立在Django基础之上的Web应用开发框架 可以快速的开发REST API接口应用 在DjangoRestFramework中提供了序列化器Serialzier的定义 可以帮助我们简化序列化与反序列化的过程

还提供丰富的类视图、扩展类、视图集来简化视图的编写工作DjangoRestFramework还提供了认证、权限、限流、过滤、分页、接口文档等功能的支持 DjangoRestFramework提供了一个API 的Web可视化界面来方便查看测试接口

下载
pip install djangorestframework
使用

需要在settings.py中的INSTALLED_APPS添加rest_framework应用

    INSTALLED_APPS = [
        'rest_framework',
    ]

drf框架体验

创建模型
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()
迁移
# 切换到项目根目录下,执行迁移命令
python manage.py makemigrations
python manage.py migrate

视图类ApiView

APIView是基于Django原生的View编写接口的,是drf提供的一个类(APIView),使用drf编写视图类,都是继承这个类及子类,APIView本身就是继承了Django原生的View

1.视图Views.py

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from app01 import models
​
    class BookView(APIView):
        def get(self, request):
            book_list = models.Book.objects.all()
            l = []
            for book in book_list:
                l.append({'name': book.name, 'price': book.price})
            return Response({'code': 100, 'msg': '查询成功', 'results': l})
    
        def post(self,request):
            data = json.loads(request.body)
            models.Book.objects.create(name=request.data.get('name'), price=request.data.get('price'))
            # Response 响应的具体数据内容会被转换(render渲染)成符合前端需求的类型
            return Response({'code': 100, 'msg': '添加成功','results': request.data})
    class BookDetailView(APIView):
        def get(self, request, pk):
            book_obj = models.Book.objects.filter(pk=pk).first()
            l = []
            l.append({'name': book_obj.name, 'price': book_obj.price})
            return Response({'code': 100, 'msg': '查询一条成功', 'results': l})
    
        def put(self, request, pk):
            book_obj = models.Book.objects.filter(pk=pk).first()
            if book_obj:
                book_obj.name = request.data.get('name')
                book_obj.price = request.data.get('price')
                book_obj.save()
                return Response({'code': 100, 'msg': '修改一条成功','results':request.data})
            else:
                return Response({'code': 101, 'msg': '当前不存在此条数据'})
    
        def delete(self,request,pk):
            book_obj = models.Book.objects.filter(pk=pk).exists()
            if book_obj:
                models.Book.objects.filter(pk=pk).delete()
                return Response({'code': 100, 'msg': '删除一条成功'})
            else:
                return Response({'code': 101, 'msg': '当前不存在此条数据'})

2.路由urls.py

    from django.contrib import admin
    from django.urls import path
    from app01 import views
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('books/', views.BookView.as_view()),
        path('books/<int:pk>', views.BookDetailView.as_view()),
    ]

drf框架的APIView类继承自django的View类,并对as_view()、dispatch()方法进行了重写,在dispatch调用initialize_request,进行认证认证操作,对原先的self.request进行了一层的包装

首先在路由中:path('books/', views.BookView.as_view()),当请求来了
    然后会执行as_view(),因为是继承着APIView所以会进入到APIView中执行它的as_view,
    而APIView的as_view其实执行结果和Django原生View的as_view是一样的,不过去除了csrf认证
    class APIView(View):
        #默认去setting的,可以自己设置值,则会覆盖。
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES #认证
        throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES    #限流
        permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES #  权限
        #。。。。其他
        
        @classmethod
        def as_view(cls, **initkwargs):
            '''
            如果视图类有queryset属性,且其类型为models.query.QuerySet,则设置一个函数force_evaluation
            这个函数用于防止直接评估queryset,因为结果会被缓存并在多个请求之间重复使用
            '''
            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
            '''
            这里调用了父类的as_view方法,父类是Django原生的View
            然后把自己的参数传递了进去,并返回被csrf_exempt装饰的视图,这个csrf会把所有的请求csrf检验认证去掉
            '''
            view = super().as_view(**initkwargs)
            view.cls = cls
            view.initkwargs = initkwargs
    
            '''
            csrf_exempt是一个装饰器,用于豁免csrf验证
            相当于在所有的方法上加上了这个装饰器
            '''
            return csrf_exempt(view)
​
            '''
            总结:当路由匹配成功后,会执行APIView父类的as_view,并且会先执行csrf_exempt(View)(request)
            然后按照正常执行Django原生的as_view方法,然后会执行它的view(request),最终会返回一个
            return self.dispatch(request, *args, **kwargs),它会从视图类中寻找,发现没有,
            因为视图类继承的是APIView,所以会优先从APIview中寻找是否有dispatch这个方法,结果找到了
            ''''dispatch方法用于处理传入的请求'
        def dispatch(self, request, *args, **kwargs):
            '保存参数'
            self.args = args
            self.kwargs = kwargs
            
            request = self.initialize_request(request, *args, **kwargs)
            '''
            这里把原来的request传入到self.initialize_request对象中
                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
                    )
            然后会执行def initialize_request方法,最后它返回了一个Request(reqeust)
            它又把这个原来的request传入进去了,然后实例化对象,调用Request的初始化方法
            下面就是Request的__init__方法,我们也对其进行解读
            def __init__(self, request, parsers=None, authenticators=None,
                negotiator=None, parser_context=None):
                这里它把传入过来的原request放到这个self._request中,这里的self已经不是视图类了,
                因为这个Request类没有继承任何一个类,它就是它自己,所以这个self是Request
                self._request = request
                # 指定解析器、身份验证器、内容协商器的参数,默认为 None 或空元组
                self.parsers = parsers or ()
                self.authenticators = authenticators or ()
                self.negotiator = negotiator or self._default_negotiator()
                self.parser_context = parser_context
                # 用于保存请求数据的属性,初始值为 Empty(可能是一个自定义的占位符类)
                self._data = Empty
                self._files = Empty
                self._full_data = Empty
                self._content_type = Empty
                self._stream = Empty
            '''
            '''
            从上面一路追源码可以看到,此时这里的self.request已经不是最开始的request了,变成了DRF提供的Request类的对象了
            而是initialize_request经过一系列操作得到的新的request了,然后此处的self则还是视图类,
            所以后续视图类的方法中,可以直接使用self.request取出它,视图类的request=新的request了
            '''
            self.request = request
            
            try:
                '执行initial方法,这里执行了三大认证,认证/频率/权限'
                self.initial(request, *args, **kwargs)
                '通过反射方法,去视图类中执行跟请求方式同名的方法'
                # 这个就跟原来的那个View类的dispatch 判断当前是否拥有八大方法如果有则执行
                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
                '因为这里得到同名的方法后传入的也是新的Request类对象了,那么视图类方法的传入的request也是新的request'
                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

drf 框架的Request类对data进行了处理,所以使用drf框架视图类,可以直接通过request.data访问到数据,而不像django需要根据情况访问request.body,POST,FILES

class Empty:
    """
    Placeholder for unset attributes.
    Cannot use `None`, as that may be a valid value.
    """
    pass
class Request:
    """
    允许增强标准 'HttpRequest' 实例的包装器。
​
args:
        - request(HttpRequest) 的 API 请求。原始请求实例。
        - 解析器(列表/元组)。用于解析请求内容。
        - 身份验证器(列表/元组)。用于尝试的验证者对请求的用户进行身份验证。
    """
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        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
        
    # 没有设置data.setter,所以Request.data是一个只读属性
    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            self._load_data_and_files()
        return self._full_data
    
    
    
    def _load_data_and_files(self):
        """
        将请求内容解析为 self.data。
        """
        if not _hasattr(self, '_data'):
            self._data, self._files = self._parse()
            if self._files:
                self._full_data = self._data.copy()
                self._full_data.update(self._files)
            else:
                self._full_data = self._data
​
            # 如果是表单媒体类型,请将数据和文件复制到底层的HTTP请求中,以便适当地处理可关闭的对象。
            if is_form_media_type(self.content_type):
                self._request._post = self.POST
                self._request._files = self.FILES
                
​
    
    @property
    def stream(self):
        """
        Returns an object that may be used to stream the request content.
        """
        if not _hasattr(self, '_stream'):
            self._load_stream()
        return self._stream
    def _load_stream(self):
        """
        Return the content body of the request, as a stream.
        """
        meta = self._request.META
        try:
            content_length = int(
                meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0))
            )
        except (ValueError, TypeError):
            content_length = 0
​
        if content_length == 0:
            self._stream = None
        elif not self._request._read_started:
            self._stream = self._request    # 这里的request是django原先的request
        else:
            self._stream = io.BytesIO(self.body) # 这里的body没能找到定义,后面再看看
            
    # 解析的过程主要是下面这个方法        
    def _parse(self):
        """
        Parse the request content, returning a two-tuple of (data, files)
​
        May raise an `UnsupportedMediaType`, or `ParseError` exception.
        """
        media_type = self.content_type
        try:
            stream = self.stream
        except RawPostDataException:
            if not hasattr(self._request, '_post'):
                raise
            # If request.POST has been accessed in middleware, and a method='POST'
            # request was made with 'multipart/form-data', then the request stream
            # will already have been exhausted.
            if self._supports_form_parsing():
                return (self._request.POST, self._request.FILES)
            stream = None
​
        if stream is None or media_type is None:
            if media_type and is_form_media_type(media_type):
                empty_data = QueryDict('', encoding=self._request._encoding)
            else:
                empty_data = {}
            empty_files = MultiValueDict()
            return (empty_data, empty_files)
​
        parser = self.negotiator.select_parser(self, self.parsers)
​
        if not parser:
            raise exceptions.UnsupportedMediaType(media_type)
​
        try:
            parsed = parser.parse(stream, media_type, self.parser_context)
        except Exception:
            # If we get an exception during parsing, fill in empty data and
            # re-raise.  Ensures we don't simply repeat the error when
            # attempting to render the browsable renderer response, or when
            # logging the request or similar.
            self._data = QueryDict('', encoding=self._request._encoding)
            self._files = MultiValueDict()
            self._full_data = self._data
            raise
​
        # Parser classes may return the raw data, or a
        # DataAndFiles object.  Unpack the result as required.
        try:
            return (parsed.data, parsed.files)
        except AttributeError:
            empty_files = MultiValueDict()
            return (parsed, empty_files)
        
    # 另外Request中还可以访问到认证后的ueser,如果有的话
    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                #设置self.user, self.auth,设置self.user实际上调用@user.setter修饰的方法,最后还是设置的self._user
                self._authenticate()
        return self._user
    @user.setter
    def user(self, value):
        """
        Sets the user on the current request. This is necessary to maintain
        compatibility with django.contrib.auth where the user property is
        set in the login and logout functions.
​
        Note that we also set the user on Django's underlying `HttpRequest`
        instance, ensuring that it is available to any middleware in the stack.
        """
        self._user = value
        self._request.user = value
    @property
    def auth(self):
        """
        Returns any non-user authentication information associated with the
        request, such as an authentication token.
        """
        if not hasattr(self, '_auth'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._auth

继承APIView+Request和Django原生View区别

从上述代码中可以看到继承APIView+Response和Django原生View区别到底在哪?
	1.首先记住APIView类是drf提供的一个类,而drf是一个app,在django中三方模块都是一个个应用,
		它注册app,rest_framework,就可以在浏览器中直接访问,drf自带的页面查看数据等。
		而原生则无法查看,会直接报错,但是可以通过接口测试工具访问,但是仅只有纯json数据
	2.在进行post请求的时候,原生View需要注释掉csrf中间件,而APIView无需注释
	3.原生View传递数据时,如果是中文,需要声明ensure_ascii为False,而APIView无需
	4.原生view使用form-data/urlencoded用获取json格式的不一样,需要从request.body中按照所需切割,比较麻烦,
	  而APIView内部对数据进行了封装,直接使用request.data可自行判断是否是那种编码格式然后进行自行转换好,无需我们
	  进行操作

Response

   当一个请求完成后,我们一般都会返回drf中的Rsponse对象,那么该对象其中最重要的作用就是序列化。

   将需要返回给页面的数据进行JSON处理,除此之外还会对返回的页面等进行包装

   下面是Rsponse的初始化函数:

def __init__(self, data=None, status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None):
​

   参数详解:

参数描述
data返回的数据,内部会进行序列化,需传入一个字典
status返回的http状态码,默认是200
template_name渲染并返回的模板
headers返回的响应头,可以组织一个字典往响应头中放入token信息等
content_type响应的编码格式,入application/json以及text/html等

   有了这些参数,我们可以这样做:

from rest_framework.response import Response
from rest_framework.views import APIView
​
class Test(APIView):
    def get(self,request):
        ret_Msg = {"status":"100","message":""}  # 返回的信息
        return Response(
            data=ret_Msg,
            status=200, # 代表成功
            headers={"token":"xxxx"},
        )
​

   这样的话在请求头中就能拿到返回的token

image.png

返回状态码

   drf中内置了很多状态码的常量,我们在返回状态码时可以使用它们。

# from rest_framework import statusHTTP_100_CONTINUE = 100
HTTP_101_SWITCHING_PROTOCOLS = 101
HTTP_200_OK = 200
HTTP_201_CREATED = 201
HTTP_202_ACCEPTED = 202
HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203
HTTP_204_NO_CONTENT = 204
HTTP_205_RESET_CONTENT = 205
HTTP_206_PARTIAL_CONTENT = 206
HTTP_207_MULTI_STATUS = 207
HTTP_208_ALREADY_REPORTED = 208
HTTP_226_IM_USED = 226
HTTP_300_MULTIPLE_CHOICES = 300
HTTP_301_MOVED_PERMANENTLY = 301
HTTP_302_FOUND = 302
HTTP_303_SEE_OTHER = 303
HTTP_304_NOT_MODIFIED = 304
HTTP_305_USE_PROXY = 305
HTTP_306_RESERVED = 306
HTTP_307_TEMPORARY_REDIRECT = 307
HTTP_308_PERMANENT_REDIRECT = 308
HTTP_400_BAD_REQUEST = 400
HTTP_401_UNAUTHORIZED = 401
HTTP_402_PAYMENT_REQUIRED = 402
HTTP_403_FORBIDDEN = 403
HTTP_404_NOT_FOUND = 404
HTTP_405_METHOD_NOT_ALLOWED = 405
HTTP_406_NOT_ACCEPTABLE = 406
HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
HTTP_408_REQUEST_TIMEOUT = 408
HTTP_409_CONFLICT = 409
HTTP_410_GONE = 410
HTTP_411_LENGTH_REQUIRED = 411
HTTP_412_PRECONDITION_FAILED = 412
HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413
HTTP_414_REQUEST_URI_TOO_LONG = 414
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416
HTTP_417_EXPECTATION_FAILED = 417
HTTP_418_IM_A_TEAPOT = 418
HTTP_422_UNPROCESSABLE_ENTITY = 422
HTTP_423_LOCKED = 423
HTTP_424_FAILED_DEPENDENCY = 424
HTTP_426_UPGRADE_REQUIRED = 426
HTTP_428_PRECONDITION_REQUIRED = 428
HTTP_429_TOO_MANY_REQUESTS = 429
HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431
HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451
HTTP_500_INTERNAL_SERVER_ERROR = 500
HTTP_501_NOT_IMPLEMENTED = 501
HTTP_502_BAD_GATEWAY = 502
HTTP_503_SERVICE_UNAVAILABLE = 503
HTTP_504_GATEWAY_TIMEOUT = 504
HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505
HTTP_506_VARIANT_ALSO_NEGOTIATES = 506
HTTP_507_INSUFFICIENT_STORAGE = 507
HTTP_508_LOOP_DETECTED = 508
HTTP_509_BANDWIDTH_LIMIT_EXCEEDED = 509
HTTP_510_NOT_EXTENDED = 510
HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
​
​

配置Rsponse

   如果你在浏览器中访问该页面,可以发现它返回的其实是一个页面:

image.png    但是在postman中,返回的则是json格式的字符串。

{
    "status": "100",
    "message": ""
}

   原因是因为它会根据request对象中的请求头accept来自动转换对应的数据格式,如果前端请求中未进行Accept声明,则会采用默认方式处理响应数据,我们可以通过配置来修改默认响应格式。

   可以在rest_framework.settings查找所有的drf默认配置项

DEFAULTS = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',   # 返回json
        'rest_framework.renderers.BrowsableAPIRenderer',  # 返回页面
    ],
    
    }

   如果我们想让浏览器访问页面时也返回JSON格式,则可以进行下面两种配置方法。

局部配置

   只指定某一个视图返回规定的数据格式:

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import settings
from rest_framework.renderers import JSONRenderer
​
class Test(APIView):
    renderer_classes=[JSONRenderer,]  # 该视图只返回JSON数据
    def get(self,request):
        ret_Msg = {"status":"100","message":""}  # 返回的信息
        return Response(
            data=ret_Msg,
            status=200, # 代表成功
            headers={"token":"xxxx"},
        )
​

全局配置

   由于它返回时查找顺序是先找局部,再找Django.settings.py,最后再找rest_framework.settings.py,所以我们可以再Django.settings.py下进行覆写:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',   # 所有视图均返回JSON格式数据
    ],
    }
​

参考文章 blog.csdn.net/achen_m/art… www.cnblogs.com/Yunya-Cnblo…