django-restframework


django-restframework

一、安裝與使用

1.安裝

>: pip3 install djangorestframework

2.使用

  • 在settings.py中注冊:

    INSTALLED_APPS = [   
        ....
        'api.apps.ApiConfig',
        # drf必須注冊
        'rest_framework',
    ]
    
  • 模塊

    # drf的封裝風格
    from rest_framework.views import APIView  # CBV
    from rest_framework.response import Response # 響應
    from rest_framework.request import Request # 請求
    from rest_framework.filters import SearchFilter # 過濾器
    from rest_framework.pagination import PageNumberPagination # 分頁
    from rest_framework.exceptions import APIException # 異常
    from rest_framework.authentication import BaseAuthentication # 驗證
    from rest_framework.permissions import IsAuthenticated # 組件
    from rest_framework.throttling import SimpleRateThrottle # 
    from rest_framework.settings import APISettings  # rest_framework配置文件
    from rest_framework import status # 狀態碼
    
    from django.http.request import QueryDict  # 類型
    
  • 基於CBV完成滿足RSSTful規范接口

二、request請求分析

2.1. request數據請求

# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.http.request import QueryDict
class BookAPIView(APIView):

    def get(self, request, *args, **kwargs):
        print(request._request.GET)  # 原生wigs中值
        print(request.GET) # 二次封裝的值
        print(request.META) # 所有get請求信息
        print(request.META.get("HTTP_AUTH")) # 前台發來的頭都會大寫並加上HTTP
        print(request.query_params) # 所有url數據
        
        print(request.POST) # get請求不能獲取Post請求數據

        return Response('get ok')

    def post(self, request, *args, **kwargs):
        print(request._request.POST)  # 原生wigs中值
        print(request.POST) # 二次封裝的值
        print(request.data) # 所有post請求數據
        print(request._request) # url數據包<WSGIRequest: POST '/books/?age=123'>
        print(request.GET) # POST請求可以Post請求數據
            
         # QueryDict轉為
        print(request.query_params.dict())
        if isinstance(request.data, QueryDict):
            print(request.data.dict())

        return Response('post ok')
    
    
    
# urls.py
from . import views
urlpatterns = [
    url(r'^books/$', views.BookAPIView.as_view()),
]

img

總結:

  1. drf中的request是在wsgi的request基礎上進行再一次封裝
  2. 將wsgi的request作為drf的request的一個屬性, _request
  3. drf中的request對wsgi中的request做完全兼容,新的可以直接獲取wsgi中的數據
  4. drf中的request對數據解析更規范化,所有的拼接參數(url?age=18)都解析到了query_params中,所有數據報數據都解析到data中
  5. query_params和data屬於QueryDict類型,可以通過.dict()轉化為字典類型

2.2 request請求源碼分析

# 1. 2. urls.py
from . import views
urlpatterns = [
    url(r'^books/$', views.BookAPIView.as_view()),
]

#3.調用drf中的as_view
@classmethod
def as_view(cls, **initkwargs):     # cls 為 views.BookAPIView類
    ...
    view = super().as_view(**initkwargs) # as_view原生的as_view,super() 為 view
    view.cls = cls
    view.initkwargs = initkwargs

    return csrf_exempt(view)
# 4.進入dif中dispatch 
def dispatch(self, request, *args, **kwargs):   # self為views.BookAPIView類
    self.args = args  
    self.kwargs = kwargs
    # 對原生request進行了二次封裝
    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
                
# 4.1 返回封裝的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
    )
# 5.Request類創建 self._request = request自己的屬性
 def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
           
            .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
        
#6.如何實現獲取wgis中的屬性
def __getattr__(self, attr):   
    """
        If an attribute does not exist on this instance, then we also attempt
        to proxy it to the underlying HttpRequest object.
        """
    try:
        return getattr(self._request, attr)
    except AttributeError:
        return self.__getattribute__(attr)

img

  1. 導入drf中的APIView並創建類繼承它,在配置url: url(r'^books/$', views.BookAPIView.as_view()),
  2. 當項目啟動時會執行drf中的as_view()方法,返回view方法(局部禁用了csrf認證), url:url(r'^books/$', views.BookAPIView.view
  3. 瀏覽器發送請求來時則會執行url中view函數,在執行view中返回的是 self.dispatch(request, *args, **kwargs), dispatch是drf中的dispatch, 不是wsgi中原生的dispatch
  4. 進入drf中的dispatch首先會對原生的request進行二次封裝:request = self.initialize_request(request, *args, **kwargs),在initialize_request方法中返回了一個request實例對象
  5. 進入request.py中的Request方法中就會發現在初始化方法中將wsgi方法的request變為了自己的一個屬性: 'self._request = request'
  6. request通過反射的方法獲取wsgi中request屬性,當request.獲取屬性先到 _request中查找,沒有則查自己本身的
  7. 核心
    • 走drf的Request初始化方法__init__:self._request = request
    • drf的Request的getter方法__getattr__:先從self._request反射取屬性,沒取到再從drf的request中取
    • request除了可以訪問原wsgi協議的request所有內容,還可以訪問 query_params、data

三、渲染模板

根據用戶請求的RUL向服務器要響應的數據類型,比如:json數據,xml數據,將這些數據向用戶返回

3.1渲染模板的使用

# 在renderers.py模板模塊中導入你要渲染的模板
# JSONRenderer: 返回json數據
# BrowsableAPIRenderer: 返回瀏覽器html頁面

from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer

# 1.局部配置
# 渲染模塊的局部配置
# 局部禁用就是配置空list:[]  # pytthon3.7 有問題
renderer_classes = [JSONRenderer, BrowsableAPIRenderer]

# 2.全局配置
# drf的配置,在drf中settings.py查看如何配置
REST_FRAMEWORK = {
    # 渲染模塊的全局配置:開發一般只配置json
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer',
    ],
   
}

img

3.2渲染模板的源碼解析

# 1 對請求響應進行二次封裝
def dispatch(self, request, *args, **kwargs):
    ....
    ....
    # 二次處理響應
    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

# 2. 接續配置的渲染類
def finalize_response(self, request, response, *args, **kwargs):

    if isinstance(response, Response):
        if not getattr(request, 'accepted_renderer', None):
            # 點進去,內部解析了配置的渲染的類
            neg = self.perform_content_negotiation(request, force=True)
            # 拿到渲染類對象
            request.accepted_renderer, request.accepted_media_type = neg
            
            response.accepted_renderer = request.accepted_renderer
            response.accepted_media_type = request.accepted_media_type
            response.renderer_context = self.get_renderer_context()

# 3. 獲取渲染對象的類    
def perform_content_negotiation(self, request, force=False):
    """
        Determine which renderer and media type to use render the response.
        """
    # 點進去,獲取配置的渲染類
    renderers = self.get_renderers()
    conneg = self.get_content_negotiator()
    try:
        return conneg.select_renderer(request, renderers, self.format_kwarg)
    except Exception:
        if force:
            return (renderers[0], renderers[0].media_type)
        raise

# 3 renderer_classes 自己全局
 def get_renderers(self):
        """
        Instantiates and returns the list of renderers that this view can use.
        """
        # 從自己的視圖類找renderer_classer類屬性(局部配置)APIView的類屬性(從自己配置文件中找)> 自己的項目配置文件中找(全局配置), drf默認配置問價中找(默認配置)
        return [renderer() for renderer in self.renderer_classes]
    
class BaseRenderer:
    """
    All renderers should extend this class, setting the `media_type`
    and `format` attributes, and override the `.render()` method.
    """
    media_type = None
    format = None
    charset = 'utf-8'
    render_style = 'text'

    def render(self, data, accepted_media_type=None, renderer_context=None):
        raise NotImplementedError('Renderer class requires .render() to be implemented')


總結:

  1. 根據流程,再次進入drf的dispatch方法,該方法對響應數據進行了二次封裝,然后,進入self.finalize_response方法
  2. 在self.finalize_respons方法中perform_content_negotiation中獲取渲染的對象結果,進入perform_content_negotiation方法
  3. perform_content_negotiation方法中實現了獲取配置文件的渲染結果集,通過self.get_renderers()方法
  4. 在self.get_renderers()方法中self.renderer_classes讀取配置的渲染對象,先從創建的視圖類中(BookAPIView(APIView)(局部配置))找,然后在從自己的項目文件中找(項目中 settings.py,全局配置), 最后從drf默認配置文件中(默認配置)
  5. renderer()是從配置文件中獲取到的對應類然后到 renderer.py 文件中獲取類對象,創建的類對象(JSONRenderer())返回數據渲染的結果
  6. 核心:可以全局和局部配置視圖類支持的結果渲染:默認可以json和頁面渲染,學習該模塊的目的是開發可以全局只配置json方式渲染

四、解析模塊的使用

服務器根據設置的請求頭content-type接收客戶端對應的數據信息

# JSONParser: 只接收json數據
# FormParser: 只接收urlencoded
# MultiPartParser:只接收form-data
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser

# 1.局部配置
# 解析模塊的局部配置
#局部配置禁用就是配置空list[]
parser_classes = [JSONParser, MultiPartParser, FormParser]

# 2.全局配置
# drf的配置,在drf中settings.py查看如何配置
REST_FRAMEWORK = {   
    # 解析模塊的全局配置
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ]
}

img

# 1.對數據進行二次解析
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進行了二次封裝,查看解析
    request = self.initialize_request(request, *args, **kwargs)
    ...
    
# 2 獲取解析數據
def initialize_request(self, request, *args, **kwargs):
    """
        Returns the initial request object.
        """
    # 准備要解析的字典
    parser_context = self.get_parser_context(request)

    return Request(
        # 初始wigs中request
        request, 
        # 解析模塊在封裝原生request是,將數據一並解析
        parsers=self.get_parsers(), 
        authenticators=self.get_authenticators(),
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context # 解析的內容
    )
    
# 2.1 返回要解析內容字典
def get_parser_context(self, http_request):
    """
        Returns a dict that is passed through to Parser.parse(),
        as the `parser_context` keyword argument.
        """
    # Note: Additionally `request` and `encoding` will also be added
    #       to the context by the Request object.
    return {
        'view': self,
        'args': getattr(self, 'args', ()),
        'kwargs': getattr(self, 'kwargs', {})
    }

# 3 獲取要解析的對象
def get_parsers(self):
    """
     Instantiates and returns the list of parsers that this view can use.
     """
    return [parser() for parser in self.parser_classes]

class BaseParser:  
    media_type = None
    def parse(self, stream, media_type=None, parser_context=None):      
        raise NotImplementedError(".parse() must be overridden.")

總結:

  1. 根據流程,再次進入drf的dispatch方法,該方法對響應數據進行了二次封裝同時也對數據進行了二次解析,然后,進入 request = self.initialize_request(request, *args, **kwargs)方法
  2. 在 self.initialize_request方法中通過self.get_parser_context(request)方法,獲取要解析的數據,get_parser_context(self, http_request):方法返回解析的數據字典返回
  3. 在Request類中調用get_parsers(self)方法獲取解析的對象,解析的隨想先從創建的視圖類中(BookAPIView(APIView)(局部配置))找,然后在從自己的項目文件中找(項目中 settings.py,全局配置), 最后從drf默認配置文件中(默認配置),實現數據的解析
  4. 只有在后台設置了解析的數據,才會給前台正確的響應否則報錯
  5. 核心:請求的數據包格式會有三種(json、urlencoded、form-data),drf默認支持三種數據的解析,可以全局或局部配置視圖類具體支持的解析方式

五、異常模塊的使用

重寫異常模塊的目的是記錄異常信息(日志記錄)

5.1異常模塊的使用

# 1. 配置異常模塊,通過settings.py獲取
REST_FRAMEWORK = {  
    # 異常模塊配置
    # Exception handling
    # 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', # 默認
    'EXCEPTION_HANDLER': 'api.utils.exception_handler', # 自己重寫的路徑
    'NON_FIELD_ERRORS_KEY': 'non_field_errors',

}

# 2.編寫異常模塊,創建一個新的文件.py重寫exception_handler方法
from rest_framework.response import Response
def exception_handler(exc, context):
    # 在exception_handler函數完成異常信息的返回以及異常信息的logging日志
    print(exc)
    print(context)
    print(type(exc))
    # exc.detail 攜帶錯誤的信息
    return Response(f'{context["view"].__class__.__name__}{exc}')

# 2.2升級版
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler

def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)
    if response is None:  # drf沒有處理的異常

        response = Response({"detail": f'{context["view"].__class__.__name__}{exc}'})
    # 項目階段,將日志記錄保存到文件中
    return response

# 2.3終極版
from rest_framework.response import Response
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework import status


def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)
    if response is None:  # drf沒有處理的異常

        response = Response(data={
            'status': 7,
            "detail": f'{context["view"].__class__.__name__}{exc}',
        },
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    response = Response(status=response.status_code, data={
        'status': 7,
        "detail": f'{response.data.get("detail")}',
    })
    # 項目階段,將日志記錄保存到文件中
    return response

5.2異常源碼分析

 # 1. 異常出錯
    def dispatch(self, request, *args, **kwargs):      
        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

# 2 異常處理
def handle_exception(self, exc):
    """
        Handle any exception that occurs, by returning an appropriate response,
        or re-raising the error.
        """
    if isinstance(exc, (exceptions.NotAuthenticated,
                        exceptions.AuthenticationFailed)):
        # WWW-Authenticate header for 401 responses, else coerce to 403
        auth_header = self.get_authenticate_header(self.request)

        if auth_header:
            exc.auth_header = auth_header
        else:
            exc.status_code = status.HTTP_403_FORBIDDEN
        # 都會走,獲取異常處理函數
        exception_handler = self.get_exception_handler()
        # 獲取請求數據內容字典
        context = self.get_exception_handler_context()
        # 異常函數:接收exc,context,返回response對象
        response = exception_handler(exc, context)

        if response is None:
            # 異常函數如果為空交給原生django處理
            self.raise_uncaught_exception(exc)
            # 異常返回
            response.exception = True
            return response
    # 2.1獲取異常處理函數具體函數,可以自己配置
 def get_exception_handler(self):
        """
        Returns the exception handler that this view uses.
        """
        return self.settings.EXCEPTION_HANDLER

總結:

  1. 在APIView的dispatch方法中,有一個try...except...異常,將代碼運行的異常都交給異常處理模塊進行處理: response = self.handle_exception(exc)

  2. 在handle_exception()中從配置中映射處理異常的函數(自定義異常模塊就是自定義配置函數指向自己的函數),通過self.get_exception_handler()去獲取配置文件中異常函數對象,然后將數據給自己寫的函數進行處理,如果沒有配置則是框架中默認的

  3. 異常函數exception_handler(exe,content)處理異常,就會走自己重寫的異常函數,先交給系統處理(客戶端的異常),系統沒處理(服務器異常),再自己處理

  4. 核心:異常信息都需要被logging記錄,所以需要自定義;drf只處理客戶端異常,服務器異常需要手動處理,統一處理結果

5.3.狀態碼響應模塊

class APIResponse(Response):
    # 格式化data
    def __init__(self, status=0, msg='ok', results=None, http_status=None, headers=None, exception=False, **kwargs):
        data = {  # json的response基礎有數據狀態碼和數據狀態信息
            'status': status,
            'msg': msg
        }
        if results is not None:  # 后台有數據,響應數據
            data['results'] = results
        data.update(**kwargs)  # 后台的一切自定義響應數據直接放到響應數據data中
        super().__init__(data=data, status=http_status, headers=headers, exception=exception)

Response類生成對象需要的參數,以及Response類的對象可以使用的屬性

  1. 參數:Response(data=響應的數據, status=響應的網絡狀態碼, headers=想通過響應頭再攜帶部分信息給前端)

  2. 屬性:response.data response.status_code response.status_text

  3. 源碼:Response類的__init__方法

  4. 核心:知道response對象產生可以傳那些信息,response對象又是如何訪問這些信息的

六、總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM