drf-day8——斷點除錯、認證.許可權.頻率的原始碼分析、基於APIView編寫分頁、全域性異常處理

致丶幻發表於2023-02-08


一、斷點除錯使用

所謂斷點除錯就是程式以debug模式執行,可以在任意位置停下,檢視停止位置變數的變化情況。

使用步驟

  • 步驟一:打斷點

image

  • 步驟二:以debug模式執行程式碼

image

  • 步驟三:這時候我們看pycharm下方的視窗,可以根據不同功能的按鈕,執行不同的操作

image

二、認證,許可權,頻率原始碼分析(瞭解)

2.1 許可權類的執行原始碼

許可權的原始碼執行流程

	-寫一個許可權類,區域性使用,配置在檢視類的,就會執行許可權類的has_permission方法,完成許可權校驗

之前我們在學習drf的apiview的原始碼中,瞭解到在執行檢視類方法之前執行了三大認證,同時兩者都是處於dispatch異常捕獲結構中的(需要注意這裡的dispatch是APIView的方法,不是view中的dispatch方法)。

    -APIView類的497行左右, self.initial(request, *args, **kwargs)---》執行3大認證
    
# APIView類的399行左右:
    def initial(self, request, *args, **kwargs):
        # 能夠解析的編碼,版本控制(知道有這作用就好了,不用管,重點是下面部分)
        self.format_kwarg = self.get_format_suffix(**kwargs)
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

     	# 認證元件的執行位置
        self.perform_authentication(request)
        # 許可權元件  [讀它]
        self.check_permissions(request)
        # 頻率元件
        self.check_throttles(request)

接著我們點進許可權元件的執行原始碼檢視

# APIView的326 左右
    def check_permissions(self, request):
        # self.get_permissions()----》[CommonPermission(),]
        # permission 是我們配置在檢視類上許可權類的物件
        for permission in self.get_permissions():
            # 許可權類的物件,執行has_permission,這就是為什麼我們寫的許可權類要重寫has_permission方法	
            # self 是檢視類的物件,就是我們們自己的的許可權類的has_permission的view引數(具體的解釋看下面)
            if not permission.has_permission(request, self):
                # has_permission方法的返回值是True或是False,如果return 的是False,就會走這裡,走這裡的結果是沒有許可權的情況
                # 如果配了多個許可權類,第一個沒過,直接不會再執行下一個許可權類了
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )
'在check_permissions中主要是一個for迴圈,而for迴圈的物件是get_permissions方法得到的結果,因此我們進來看看他是起了啥作用'
                
# APIView的274行左右  get_permissions
    def get_permissions(self):
        # self.permission_classes  是我們們配置在檢視類上的列表,裡面是一個個的許可權類,沒加括號就是沒呼叫,他這裡就相當於把這些許可權類都加括號執行,把最後獲取到的物件放到列表中返回出來
        # permission_classes = [CommonPermission]
        # [CommonPermission(),]   本質返回了許可權類的物件,放到列表中
        return [permission() for permission in self.permission_classes]
    
'分析完之後回到上面'
    
    
'我們編寫的許可權類'
# 寫許可權類,寫一個類,繼承基類BasePermission,重寫has_permission方法,在方法中實現許可權認證,如果有許可權return True ,如果沒有許可權,返回False
from rest_framework.permissions import BasePermission


class CommonPermission(BasePermission):
    def has_permission(self, request, view):
        # 實現許可權的控制  ---》知道當前登入使用者是誰?當前登入使用者是  request.user
        if request.user.user_type == 1:
            return True
        else:
            # 沒有許可權,向物件中放一個屬性 message
            # 如果表模型中,使用了choice,就可以透過  get_欄位名_display()  拿到choice對應的中文
            self.message = '您是【%s】,您沒有許可權' % request.user.get_user_type_display()
            return False
    
'我們在這裡重新定義了has_permission方法,在上面的原始碼中呼叫了這個方法,但是他的引數中,第二個引數是self,從我們定義的引數來看,我們可以知道這個self繫結給了view'
    
'看完之後還是回到上面'

總結:

  • APIView中的dispatch方法,dispatch方法內部執行了initial方法進行認證許可權頻率這三大認證,initial方法的倒數第二行執行了許可權校驗---》self.check_permissions(request)

  • 裡面取出配置在檢視類上的許可權類,例項化得到物件,一個個執行物件的has_permission方法,如果返回False,就直接結束,不再繼續往下執行,許可權就認證透過

  • 如果檢視類上不配置許可權類:permission_classes = [CommonPermission],會使用配置檔案的api_settings.DEFAULT_PERMISSION_CLASSES
    使用順序是優先使用檢視類中的區域性配置,再優先使用專案配置檔案中的配置,其次使用drf內建配置檔案中的配置

2.2 認證原始碼分析

前面部分跟許可權類的分析流程一樣,不做詳細解釋了,到執行initial方法處為止。

# 之前讀過:drf的apiview,在執行檢視類的方法之前,執行了3大認證----》dispatch方法中的
    -497行左右, self.initial(request, *args, **kwargs)---》執行3大認證
    
# APIView類的399行左右:
    def initial(self, request, *args, **kwargs):
        # 能夠解析的編碼,版本控制(知道有這作用就好了,不用管,重點是下面部分)
        self.format_kwarg = self.get_format_suffix(**kwargs)
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

     	# 認證元件的執行位置【讀它】
        self.perform_authentication(request)
        # 許可權元件 
        self.check_permissions(request)
        # 頻率元件
        self.check_throttles(request)
        
'我們進入認證元件的原始碼進行分析'
 # APIView的316行左右
    def perform_authentication(self, request):
        request.user #我們們覺得它是個屬性,其實它是個方法,包裝成了資料屬性
        
        
 # Request類的user方法(用了APIView或是GenericAPIView後,在dispatch中重新定義了request,用Request產生的物件包裝了一些,可以看第三天的部落格)   Request的原始碼的219行左右
    @property
    def user(self):
        if not hasattr(self, '_user'):
            '咋們先看這裡沒有找到_user屬性時執行的程式碼,可以看到執行了_authenticate方法,而當前的物件是Request類產生的,因此我們要去Request類中查詢這個方法'
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
    
    
 # self 是Request的物件,找Request類的self._authenticate()   373 行
    def _authenticate(self):
        # self.authenticators 跟我們在許可權類的原始碼分析中見到的形式不一樣,但是我們可以猜測兩者的作用應該是相似的,就是我們配置在檢視類上認證類的一個個物件,放到列表中
        # 點進原始碼我們發現他是在Request類初始化的時候,傳入的(看下面)
        for authenticator in self.authenticators:
            try:
                # 而異常捕獲內的程式碼也跟之前一樣,是用於獲取認證類中的重寫的方法的結果返回了兩個值。
                # 他有兩種返回結果,第一種結果時返回兩個值,第一個值是當前登入使用者,第二個的token,只走這一個認證類,後面的不再走了
                # 第二種結果是可以返回None,會繼續執行下一個認證類
                # 同時我們也可以看到,如果報錯了就會直接停止執行,有返回值的時候會把值獲取,然後返回出去
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                # 解壓賦值(當我們在獲取到認證類校驗之後的結果後,會到下方的程式碼這裡進行解壓賦值):
                #self.user=當前登入使用者,self是當次請求的新的Request的物件
                #self.auth=token
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()
        
        
        
 # self.authenticators  去Request類的init中找     152行左右
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        .....
        self.authenticators = authenticators or ()
		.....
        
        
 # 什麼時候呼叫Reqeust的__init__?---》APIVIew的dispatch上面的492行的:request = self.initialize_request(request, *args, **kwargs)-----》385行----》
    def initialize_request(self, request, *args, **kwargs):
        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
'我們可以看到就是在給老的request進行包裝的時候傳入的這個引數'
'接著我們點進get_authenticators方法中檢視原始碼'
        def get_authenticators(self):
        return [auth() for auth in self.authentication_classes]
'這裡我們可以看到,跟許可權類的原始碼中相似的部分出現了,就是把檢視類中使用的認證類進行加括號然後存放到列表中返回'
'回到上面'
    
 # 總結:
	1 配置在檢視類上的認證類,會在執行檢視類方法之前執行,在許可權認證之前執行
    2 自己寫的認證類,可以返回兩個值或None
    3 後續可以從request.user 取出當前登入使用者(前提是你要在認證類中返回)

2.3 頻率原始碼分析

前面部分跟許可權類的分析流程一樣,不做詳細解釋了,到執行initial方法處為止。

# 之前讀過:drf的apiview,在執行檢視類的方法之前,執行了3大認證----》dispatch方法中的
    -497行左右, self.initial(request, *args, **kwargs)---》執行3大認證
    
# APIView類的399行左右:
    def initial(self, request, *args, **kwargs):
        # 能夠解析的編碼,版本控制(知道有這作用就好了,不用管,重點是下面部分)
        self.format_kwarg = self.get_format_suffix(**kwargs)
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

     	# 認證元件的執行位置
        self.perform_authentication(request)
        # 許可權元件 
        self.check_permissions(request)
        # 頻率元件【讀它】
        self.check_throttles(request)
        
        
# APIView 的352行
    def check_throttles(self, request):
        throttle_durations = []
        #self.get_throttles() 這個方法我們也不能一下看出他是什麼作用的,但是在這個位置,必然跟前面兩個的原始碼中的程式碼的作用是類似的,因此我們也要點進原始碼進行檢視(見下方)
        # 配置在檢視類上的頻率類的物件,放到列表中
        
        # 每次取出一個頻率類的物件,執行allow_request方法,這裡allow_request方法是存在的,因為我們繼承的是SimpleRateThrottle,他內部已經寫了一格allow_request方法。如果我們繼承BaseThrottle可以在原始碼中發現allow_request方法是需要我們自行重寫的,不然會報錯
        # 我們回顧頻率類中我們自行重寫的方法並沒有對頻率進行校驗,因此我們也可以在原始碼中看出是allow_request方法進行了校驗。
        # allow_request方法的返回值如果是False,訪問頻率超過了限制,不能再走了
        # allow_request方法的返回值如果是True,沒有超出訪問頻率限制,可以直接往後
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                throttle_durations.append(throttle.wait())
'這裡的wait()方法和後面的程式碼的作用可以看接下去的自定義頻率類進行對比理解,不展開具體分析了'
        if throttle_durations:
            # Filter out `None` values which may happen in case of config / rate
            # changes, see #1438
            durations = [
                duration for duration in throttle_durations
                if duration is not None
            ]

            duration = max(durations, default=None)
            self.throttled(request, duration)
            

'在get_throttles這個方法中我們可以看到他確實是跟之前一樣的作用,把頻率類加上括號然後套在列表中返回出來'
            

    def get_throttles(self):
        return [throttle() for throttle in self.throttle_classes]
'返回上面'
            
    
    
'allow_request方法(講解不詳細,後文有詳細的分析)'
        def allow_request(self, request, view):
        if self.rate is None:
            return True
		'這裡的兩個屬性透過英文意思我猜測就是檢測是否配置了頻率要求'
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True

        self.history = self.cache.get(self.key, [])
        self.now = self.timer()
		'透過自定義頻率類的學習,我們可以得知這裡的字典是儲存頻率的檢測的時間和計算頻率的引數'
        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()
    '這裡就是根據時間範疇對字典中的資料值進行檢測,並返回檢測結果(布林值)'
# 總結:
	-我們寫的頻率類:繼承BaseThrottle,重寫allow_request,在內部判斷,如果超頻了,就返回False,如果沒超頻率,就返回True

2.4 自定義頻率類(瞭解)

這裡咋們瞭解邏輯,會用程式碼即可

class SuperThrottle(BaseThrottle):
    VISIT_RECORD = {}
	'從下面的邏輯中我們發現需要有一個訪問字典儲存ip和被訪問的時間,因此需要在這裡定義'
    def __init__(self):
        self.history = None
		'這裡的history屬性同樣是因為下方的程式碼中需要用到才定義的'
    def allow_request(self, request, view):
        # 自己寫邏輯,判斷是否超頻
        # (1)取出訪問者ip
        # (2)判斷當前ip不在訪問字典裡,新增進去,並且直接返回True,表示第一次訪問,在字典裡,繼續往下走 {ip地址:[時間1,時間2,時間3,時間4]}
        # (3)迴圈判斷當前ip的列表,有值,並且當前時間減去列表的最後一個時間大於60s,把這種資料pop掉,這樣列表中只有60s以內的訪問時間,
        # (4)判斷,當列表小於3,說明一分鐘以內訪問不足三次,把當前時間插入到列表第一個位置,返回True,順利透過
        # (5)當大於等於3,說明一分鐘內訪問超過三次,返回False驗證失敗
        # (1)取出訪問者ip
        ip = request.META.get('REMOTE_ADDR')
        import time
        ctime = time.time()
        # (2)判斷當前ip不在訪問字典裡,新增進去,並且直接返回True,表示第一次訪問
        if ip not in self.VISIT_RECORD:
            self.VISIT_RECORD[ip] = [ctime, ]
            return True
        # self.history  = [時間1]
        self.history = self.VISIT_RECORD.get(ip,[])
        # (3)迴圈判斷當前ip的列表,有值,並且當前時間減去列表的最後一個時間大於60s,把這種資料pop掉,這樣列表中只有60s以內的訪問時間,
        while self.history and ctime - self.history[-1] > 60:
            self.history.pop()
        # (4)判斷,當列表小於3,說明一分鐘以內訪問不足三次,把當前時間插入到列表第一個位置,返回True,順利透過
        # (5)當大於等於3,說明一分鐘內訪問超過三次,返回False驗證失敗
        if len(self.history) < 3:
            self.history.insert(0, ctime)
            return True
        else:
           
            return False
	'當我們不寫下方的wait方法時,我們會發現跟SimpleRateThrottle中編寫的allow_request方法有點不同,沒有那串英文展示還有多久時間可以繼續訪問,這裡的wait方法就是用於展示那句話的'
    def wait(self):
        import time
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

2.5 SimpleRateThrottle原始碼分析

# 寫一個頻率類,重寫allow_request方法,在裡面實現頻率控制

# SimpleRateThrottle---》allow_request
    def allow_request(self, request, view):
		# 這裡就是透過配置檔案和scop取出 頻率限制是多少,比如一分鐘訪問5此
        if self.rate is None:
            return True

        # 返回了ip,就以ip做限制
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True
		# 下面的邏輯,跟我們們寫的一樣
        self.history = self.cache.get(self.key, [])
        self.now = self.timer()
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()
    
    
    
# SimpleRateThrottle的init方法
    def __init__(self):
        if not getattr(self, 'rate', None):
            # self.rate= '5、h'
            self.rate = self.get_rate()
        
        # 這行程式碼我們不能直接看懂他的意思,因此需要分析原始碼(接著看下方的程式碼)
        # 5    36000
        self.num_requests, self.duration = self.parse_rate(self.rate)
        
        
'在SimpleRateThrottle的init方法中我們發現rate屬性是用get_rate方法獲取的'
# SimpleRateThrottle的get_rate() 方法
    def get_rate(self):
	'這一塊程式碼就是判斷我們有沒有設定頻率限制(scope屬性),如果沒有有會報錯'
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            #  self.scope 是 lqz 字串
            # 而scope的相關配置資訊是需要我們在配置檔案中自行設定的
            # 原始碼就是把他繫結到配置檔案中的存有配置資訊的字典上:THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
            # return '5/h'
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)
'看完之後回到上面的雙下init先'  
            
# SimpleRateThrottle的parse_rate 方法
	def parse_rate(self, rate):
        # 這裡舉例rate的值是'5/h'
        
        # 這裡的if是用於處理有scope配置的時候,但配置內容為空的時候的處理方式
        if rate is None:
            return (None, None)
        # num =5
        # period= 'hour'
        # 這裡很明顯就是進行字串切割
        num, period = rate.split('/')
        # num_requests=5
        # 這裡就是獲取頻率中的訪問次數限制
        num_requests = int(num)
        # 這裡就是對頻率的時間限制,因為他是用首個字元來進行時分秒的區分的,所以我們之前學習頻率元件的時候說時分秒的字元開頭對就可以,後面隨便寫
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        # (5,36000),這裡就是返回頻率限制的具體內容了
        return (num_requests, duration)

三、基於APIView編寫分頁

分頁功能,只有查詢所有才有

ps:編寫的時候可以參照ListAPIView中繼承的那個list方法進行參考

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

基於APIView編寫的list

class BookView(ViewSetMixin, APIView):
    '這裡的方法名稱必須是list,因為路由在自動自動建立的時候對映也是自動配置的'
    def list(self, request):
        books = Book.objects.all()
        # 使用步驟
        # 1 例項化得到一個分頁類的物件
        paginator = CommonLimitOffsetPagination()
        # 2 呼叫分頁類物件的paginate_queryset方法來完成分頁(因為原始碼寫了三個引數我們也寫三個引數,最後一個引數是view,又因為我們就是在檢視配中編寫,所以寫self就好了),返回的page是 要序列化的資料,分頁好的
        page = paginator.paginate_queryset(books, request, self)
        if page is not None:
            serializer = BookSerializer(instance=page, many=True)
            # 我們可以發現原始碼中顯示呼叫了get_serializer方法,這就是呼叫序列化類,因此這裡我們也可以直接呼叫序列化類
            
            # 3 返回資料,呼叫paginator的get_paginated_response方法
            # return paginator.get_paginated_response(serializer.data)
            # 透過檢視他的原始碼我們發現改用Response返回資料的原理(看下方原始碼和分析)
            return Response({
                'total': paginator.count,
                'next': paginator.get_next_link(),
                'previous': paginator.get_previous_link(),
                'results': serializer.data
            })
            '這裡的字典中的鍵,可以自定義命名,一旦我們更改了這些名字,在postman中接收到的資料的鍵的名稱也會更改'

第二步中需要研究的原始碼

page = self.paginate_queryset(queryset)

'這裡的這個paginate_queryset方法是需要去GenericAPIView中查詢的,程式碼如下:'

    @property
    def paginator(self):
        """
        The paginator instance associated with the view, or `None`.
        """
        if not hasattr(self, '_paginator'):
            if self.pagination_class is None:
                self._paginator = None
            else:
                self._paginator = self.pagination_class()
        return self._paginator
    
'我們不難發現這個被偽裝的paginator其實就是產生一個分頁類的物件'
    
    def paginate_queryset(self, queryset):
        if self.paginator is None:
            return None
        return self.paginator.paginate_queryset(queryset, self.request, view=self)
    
'在最後我們看到了一個熟悉的單詞paginate_queryset,這裡就相當於是使用分頁類產生的物件傳入資料進行校驗'

第三步中需要研究的原始碼

    def get_paginated_response(self, data):
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data)
    '這裡我們可以看到這裡跟第二步其實差不多,是獲取序列化類產生的物件,然後返回相應的結果,我們透過之前的學習得知返回的結果肯定是Response封裝的,因此肯定就是get_paginated_response方法中進行了封裝'
    
    
        def get_paginated_response(self, data):
        return Response(OrderedDict([
            ('count', self.page.paginator.count),
            ('next', self.get_next_link()),
            ('previous', self.get_previous_link()),
            ('results', data)
        ]))
        '然後我們在檢視類中寫了這個方法之後,點進來會看到上方的這個原始碼,他就是封裝了返回的資料的函式,因此我們可以直接給他進行替換,然後對格式進行自定義'

四、全域性異常處理

# APIView的dispatch方法中執行了三大認證,然後執行了檢視類的方法,如果出了異常,會被異常捕獲,捕獲後統一處理
    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
    
    
# drf 內建了一個函式,只要上面過程出了異常,就會執行這個函式,這個函式只處理的drf的異常
	-主動拋的非drf異常
    -程式出錯了 
    都不會被處理
    我們的目標,無論主動拋還是程式執行出錯,都同意返回規定格式--》能記錄日誌
    公司裡一般返回   {code:999,'msg':'系統錯誤,請聯絡系統管理員'}
    
    
    
# 寫一個函式,內部處理異常,在配置檔案中配置一下即可


'第一步:首先在drf的配置檔案中有一個配置資訊如下(這個後面再配置,先挑出來講而已)'
# Exception handling
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    '這個配置就是處理drf內部異常的配置檔案,如果我們想要進行自定義,就需要在dango的配置檔案中自行註冊進行替換'
    
'第二步:我們檢視dispatch中的異常捕獲,發現handle_exception處理了這些異常捕獲的資訊,而他的引數就是錯誤資訊'

 response = self.handle_exception(exc)
    
'第三步:我們進入他的原始碼發現他返回的資訊是response,並且這個response是由exception_handler方法獲得的'


    def handle_exception(self, exc):
        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()
        response = exception_handler(exc, context)

        if response is None:
            self.raise_uncaught_exception(exc)

        response.exception = True
        return response

'第四步:我們可以看到他則是來自上面的一行程式碼'
    
    exception_handler = self.get_exception_handler()
    
'第五步:進入這個get_exception_handler方法的原始碼,我們可以發現他就是拿了配置檔案中的配置返回出去'
    
        def get_exception_handler(self):
        return self.settings.EXCEPTION_HANDLER
    
'而配置資訊中對應的是一個函式,因此exception_handler的值就相當於是獲取這個函式的結果'
    
    def exception_handler(exc, context):
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()

    if isinstance(exc, exceptions.APIException):
        headers = {}
        if getattr(exc, 'auth_header', None):
            headers['WWW-Authenticate'] = exc.auth_header
        if getattr(exc, 'wait', None):
            headers['Retry-After'] = '%d' % exc.wait

        if isinstance(exc.detail, (list, dict)):
            data = exc.detail
        else:
            data = {'detail': exc.detail}

        set_rollback()
        return Response(data, status=exc.status_code, headers=headers)

    return None
    
    
'研究了原始碼之後我們就開始自己編寫,因為原本的異常處理函式有兩個引數,因此我們也需要上兩個引數,而它的原始碼太多了,我們可以跟物件導向的派生方法一樣,在我們自行定義的函式內呼叫原本的函式,在此基礎上進行自定義'
    
def common_exception_handler(exc, context):
    # exc 錯誤物件
    # context:上下文,有view:當前出錯的檢視類的物件,args和kwargs檢視類方法分組出來的引數,request:當次請求的request物件
    # 只要走到這裡,就要記錄日誌 ,只有錯了,才會執行這個函式
    # 記錄日誌儘量詳細
    print('時間,登入使用者id,使用者ip,請求方式,請求地址,執行的檢視類,錯誤原因')
    res = exception_handler(exc, context)
    if res:  # 透過觀察原來的函式我們發現有值的時候,說明返回了Response 物件,沒有值說明返回None
        # 如果是Response 物件說明是drf的異常,已經被處理了,如果是None表明沒有處理,就是非drf的異常
        res = Response(data={'code': 888, 'msg': res.data.get('detail', '請聯絡系統管理員')}) # 這裡就是自定義返回的資訊的格式,處理的就是原本的drf中的報錯
    else:
        # res = Response(data={'code': 999, 'msg': str(exc)})
        # 記錄日誌
        res = Response(data={'code': 999, 'msg': '系統錯誤,請聯絡系統管理員'})
        # 這裡就是處理非drf報錯的程式碼,如果想看具體報錯可以把msg的值換成引數中的exc接收的錯誤資訊


    return res


# 在配置檔案中配置
REST_FRAMEWORK = {
	'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
}

五、作業

# 1 整理認證,頻率,許可權原始碼 執行流程
# 2 自定義頻率類,完成一分鐘訪問5次控制
# 3 整理SimpleRateThrottle原始碼執行流程
# 4 基於APIView編寫分頁
# 5 編寫全域性異常處理,後端報錯,返回固定格式
	-詳細列印錯誤原因
    print('時間,登入使用者id(如果沒登入就是匿名使用者),使用者ip,請求方式,請求地址,執行的檢視類,錯誤原因')


相關文章