<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      一文讀懂django-restframeword(DRF)

      轉(zhuǎn)載請(qǐng)注明 來(lái)源:http://www.eword.name/
      Author:eword
      Email:eword@eword.name

      django-restframeword(DRF)解讀

      一、面相對(duì)象編程中的類(lèi)繼承和反射基礎(chǔ)

      要讀懂DRF原理,學(xué)通類(lèi)繼承和反射原理很重要。

      1.1、類(lèi)繼承

      • 在Python中,類(lèi)的繼承是面向?qū)ο缶幊痰囊粋€(gè)核心概念。通過(guò)繼承,一個(gè)類(lèi)(稱(chēng)為子類(lèi)或派生類(lèi))可以獲取另一個(gè)類(lèi)(稱(chēng)為父類(lèi)或基類(lèi))的屬性和方法。這使得代碼的重用和擴(kuò)展變得容易。
      • Python支持多重繼承,這意味著一個(gè)類(lèi)可以繼承自多個(gè)父類(lèi)。繼承關(guān)系通過(guò)類(lèi)定義時(shí)使用的class關(guān)鍵字和括號(hào)內(nèi)的基類(lèi)名來(lái)建立。
      # 定義一個(gè)基類(lèi)(父類(lèi))  
      class Animal:  
          def __init__(self, name):  
              self.name = name  
            
          def speak(self):  
              print(f"{self.name} makes a sound")  
        
      # 定義一個(gè)子類(lèi),繼承自Animal  
      class Dog(Animal):  
          def __init__(self, name, breed):  
              super().__init__(name)  # 調(diào)用父類(lèi)的__init__方法  
              self.breed = breed  
            
          def bark(self):  
              print(f"{self.name} barks!")  
        
      # 創(chuàng)建一個(gè)Dog類(lèi)的實(shí)例  
      my_dog = Dog("Buddy", "Labrador")  
        
      # 調(diào)用繼承自Animal的方法  
      my_dog.speak()  # 輸出: Buddy makes a sound  
        
      # 調(diào)用Dog類(lèi)特有的方法  
      my_dog.bark()  # 輸出: Buddy barks!
      

      多重繼承

      # 定義第一個(gè)父類(lèi)  
      class FlyingAnimal:  
          def fly(self):  
              print("I can fly!")  
        
      # 定義第二個(gè)父類(lèi)  
      class Mammal:  
          def give_birth(self):  
              print("I give birth to live young.")  
        
      # 定義子類(lèi),繼承自FlyingAnimal和Mammal  
      class Bat(FlyingAnimal, Mammal):  
          pass  
        
      # 創(chuàng)建一個(gè)Bat類(lèi)的實(shí)例  
      my_bat = Bat()  
        
      # 調(diào)用繼承自FlyingAnimal的方法  
      my_bat.fly()  # 輸出: I can fly!  
        
      # 調(diào)用繼承自Mammal的方法  
      my_bat.give_birth()  # 輸出: I give birth to live young.
      

      1.2、反射

      在Python中,反射(Reflection)是一種能力,它允許程序在運(yùn)行時(shí)檢查對(duì)象、模塊、函數(shù)、類(lèi)等的信息,并且可以動(dòng)態(tài)地調(diào)用其方法和屬性。Python的內(nèi)置函數(shù)和模塊提供了實(shí)現(xiàn)反射功能所需的工具。反射在Python中通常涉及到以下幾個(gè)內(nèi)置函數(shù):

      • dir():返回一個(gè)對(duì)象的屬性列表,包括方法、變量等。
      • getattr():獲取對(duì)象屬性的值。
      • setattr():設(shè)置對(duì)象屬性的值。
      • hasattr():檢查對(duì)象是否具有某個(gè)屬性。
      • callable():檢查對(duì)象是否可以被調(diào)用(例如函數(shù)或方法)。

      1.2.1、使用舉例

      使用 dir() 查看對(duì)象的屬性

      class MyClass:  
          def my_method(self):  
              print("This is a method.")  
            
          my_variable = "This is a variable."  
        
      obj = MyClass()  
      print(dir(obj))  # 列出對(duì)象的所有屬性
      

      使用 getattr() 獲取屬性的值

      value = getattr(obj, 'my_variable')  # 獲取my_variable屬性的值  
      print(value)  # 輸出: This is a variable.
      

      使用 setattr() 設(shè)置屬性的值

      setattr(obj, 'my_variable', 'New value')  # 設(shè)置my_variable屬性的新值  
      print(getattr(obj, 'my_variable'))  # 輸出: New value
      

      使用 hasattr() 檢查屬性是否存在

      exists = hasattr(obj, 'my_method')  # 檢查my_method方法是否存在  
      print(exists)  # 輸出: True
      

      使用 callable() 檢查對(duì)象是否可調(diào)用

      is_callable = callable(obj.my_method)  # 檢查my_method方法是否可以被調(diào)用  
      print(is_callable)  # 輸出: True
      

      1.2.2、通過(guò)反射創(chuàng)建對(duì)象

      在Python中,你可以使用反射來(lái)動(dòng)態(tài)地創(chuàng)建對(duì)象。

      1. 使用globals()或locals()函數(shù)來(lái)獲取當(dāng)前全局或局部命名空間。
      2. 使用getattr()來(lái)獲取類(lèi)或函數(shù)的引用。
      3. 調(diào)用該類(lèi)或函數(shù)來(lái)創(chuàng)建對(duì)象。

      ps: 更常見(jiàn)的方法是直接使用globals()或locals()結(jié)合類(lèi)名或函數(shù)名來(lái)動(dòng)態(tài)地獲取類(lèi)或函數(shù),并調(diào)用它們。

      class MyClass:  
          def __init__(self, name):  
              self.name = name  
            
          def say_hello(self):  
              print(f"Hello, my name is {self.name}!")  
        
      # 動(dòng)態(tài)創(chuàng)建對(duì)象  
      class_name = "MyClass"  # 類(lèi)名作為字符串  
      obj = globals()[class_name](name="Dynamic Object")  # 使用globals()獲取類(lèi)引用并創(chuàng)建對(duì)象  
        
      # 調(diào)用對(duì)象的方法  
      obj.say_hello()  # 輸出: Hello, my name is Dynamic Object!
      

      在這個(gè)例子中,

      1. 首先定義了一個(gè)名為MyClass的類(lèi)。
      2. 然后,將類(lèi)名作為字符串存儲(chǔ)在變量class_name中。
      3. 接著,使用globals()函數(shù)來(lái)獲取當(dāng)前全局命名空間,并使用類(lèi)名字符串作為鍵來(lái)獲取類(lèi)的引用。
      4. 最后,我們調(diào)用這個(gè)類(lèi)來(lái)創(chuàng)建一個(gè)新的對(duì)象obj,并傳遞所需的參數(shù)。

      如果是在一個(gè)函數(shù)或方法內(nèi)部,并且類(lèi)是在那個(gè)局部作用域中定義的,可以使用locals()代替globals()。但是,通常情況下,類(lèi)定義在模塊級(jí)別,所以globals()更為常用。

      如果你的類(lèi)定義在另一個(gè)模塊中,需要先導(dǎo)入那個(gè)模塊,然后從模塊的命名空間中使用反射來(lái)獲取類(lèi)的引用。

      import my_module  # 假設(shè)my_module中包含MyClass的定義  
        
      class_name = "MyClass"  
      obj = my_module.__dict__[class_name](name="Dynamic Object from Module")  
      obj.say_hello()
      

      1.2.3、通過(guò)反射實(shí)現(xiàn)事件注冊(cè)機(jī)制

      1.2.3.1、父類(lèi)調(diào)用子類(lèi)函數(shù)

      實(shí)現(xiàn)事件注冊(cè)機(jī)制前,要先了解如何通過(guò)父類(lèi)調(diào)用子類(lèi)的函數(shù)或?qū)傩浴?/p>

      1. 在父類(lèi)中定義一個(gè)方法,該方法使用反射來(lái)調(diào)用子類(lèi)的方法。
      2. 在子類(lèi)中實(shí)現(xiàn)這個(gè)方法。
      3. 當(dāng)父類(lèi)需要調(diào)用這個(gè)方法時(shí),它使用反射來(lái)找到并調(diào)用子類(lèi)中的實(shí)現(xiàn)。

      這通常是通過(guò)使用getattr()函數(shù)來(lái)實(shí)現(xiàn)的,該函數(shù)可以從一個(gè)對(duì)象中獲取一個(gè)屬性的值,如果該屬性是一個(gè)方法,則可以調(diào)用它。

      class Parent:  
          def callback_subclass_method(self):  
              # 使用反射調(diào)用子類(lèi)的方法  
              method_name = "subclass_specific_method"  
              if hasattr(self, method_name):  
                  method = getattr(self, method_name)  
                  method()  
              else:  
                  print("No subclass-specific method found.")  
        
      class Child(Parent):  
          def subclass_specific_method(self):  
              print("This is a method specific to the subclass.")  
        
      # 創(chuàng)建子類(lèi)實(shí)例并調(diào)用父類(lèi)方法  
      child_instance = Child()  
      child_instance.callback_subclass_method()
      

      在這個(gè)例子中,Parent類(lèi)有一個(gè)callback_subclass_method方法,它試圖調(diào)用一個(gè)名為subclass_specific_method的方法。這個(gè)方法在Child類(lèi)中被定義。當(dāng)Child類(lèi)的一個(gè)實(shí)例調(diào)用callback_subclass_method時(shí),它實(shí)際上會(huì)調(diào)用Child類(lèi)中定義的subclass_specific_method。

      注意:這個(gè)例子假設(shè)子類(lèi)確實(shí)有一個(gè)名為subclass_specific_method的方法。在實(shí)際應(yīng)用中,可能需要更復(fù)雜的邏輯來(lái)處理不同子類(lèi)可能有不同方法的情況。

      此外,如果設(shè)計(jì)允許子類(lèi)有不同的方法名稱(chēng)或簽名,可能需要使用更高級(jí)的反射技術(shù),或者重新考慮設(shè)計(jì),以便父類(lèi)可以使用更通用的接口來(lái)與子類(lèi)交互。這通常可以通過(guò)接口、抽象基類(lèi)或協(xié)議來(lái)實(shí)現(xiàn)。

      最后,過(guò)度使用反射可能會(huì)使代碼難以理解和維護(hù),因?yàn)樗试S在運(yùn)行時(shí)動(dòng)態(tài)地改變行為。因此,在使用反射時(shí)應(yīng)該謹(jǐn)慎,并確保代碼的可讀性和可維護(hù)性。

      1.2.3.1、事件注冊(cè)機(jī)制

      在Python中,通過(guò)反射實(shí)現(xiàn)事件注冊(cè)機(jī)制可以涉及到創(chuàng)建一個(gè)事件分發(fā)器,它允許不同的對(duì)象(通常是類(lèi)的實(shí)例)注冊(cè)和觸發(fā)事件。每個(gè)對(duì)象可以定義自己的事件處理函數(shù),并在事件發(fā)生時(shí)通過(guò)反射機(jī)制被調(diào)用。

      class EventDispatcher:  
          def __init__(self):  
              self.event_handlers = {}  
        
          def register_event(self, event_name, handler):  
              """注冊(cè)事件處理函數(shù)"""  
              if event_name not in self.event_handlers:  
                  self.event_handlers[event_name] = []  
              self.event_handlers[event_name].append(handler)  
        
          def trigger_event(self, event_name, *args, **kwargs):  
              """觸發(fā)事件并調(diào)用所有注冊(cè)的處理函數(shù)"""  
              if event_name in self.event_handlers:  
                  for handler in self.event_handlers[event_name]:  
                      handler(*args, **kwargs)  
        
      class Listener:  
          def on_event(self, *args, **kwargs):  
              """事件處理函數(shù)"""  
              print(f"Event received: {args}, {kwargs}")  
        
      # 創(chuàng)建事件分發(fā)器和監(jiān)聽(tīng)器  
      dispatcher = EventDispatcher()  
      listener = Listener()  
        
      # 注冊(cè)事件處理函數(shù)  
      dispatcher.register_event("my_event", listener.on_event)  
        
      # 觸發(fā)事件  
      dispatcher.trigger_event("my_event", "Hello", x=10)
      

      在這個(gè)例子中:

      • EventDispatcher類(lèi)維護(hù)了一個(gè)字典event_handlers,它映射事件名稱(chēng)到處理函數(shù)列表。
      • register_event方法允許其他對(duì)象將其事件處理函數(shù)注冊(cè)到特定的事件上。
      • trigger_event方法則負(fù)責(zé)調(diào)用所有注冊(cè)到特定事件的處理函數(shù)。

      Listener類(lèi)有一個(gè)on_event方法,它是事件處理函數(shù)。當(dāng)事件被觸發(fā)時(shí),這個(gè)方法將被調(diào)用。

      然后,創(chuàng)建了一個(gè)EventDispatcher實(shí)例和一個(gè)Listener實(shí)例,將Listener的on_event方法注冊(cè)到名為"my_event"的事件上,并觸發(fā)這個(gè)事件。當(dāng)事件被觸發(fā)時(shí),所有注冊(cè)到該事件的處理函數(shù)(在這個(gè)例子中是on_event方法)都會(huì)被調(diào)用。

      這個(gè)例子并沒(méi)有直接使用getattr或setattr等反射函數(shù),因?yàn)榉瓷渫ǔS糜趧?dòng)態(tài)地獲取或設(shè)置對(duì)象的屬性或方法。

      在這個(gè)例子中,事件處理函數(shù)的注冊(cè)和觸發(fā)是顯式進(jìn)行的,而不是通過(guò)字符串名稱(chēng)動(dòng)態(tài)查找的。
      可以擴(kuò)展這個(gè)機(jī)制,允許通過(guò)字符串名稱(chēng)來(lái)注冊(cè)和觸發(fā)事件,這樣就涉及到了反射的使用。
      如果需要通過(guò)字符串名稱(chēng)來(lái)動(dòng)態(tài)地注冊(cè)和觸發(fā)事件,可以修改register_event和trigger_event方法,讓它們接受事件名稱(chēng)作為字符串,并使用getattr來(lái)動(dòng)態(tài)地獲取和處理事件處理函數(shù)。但是請(qǐng)注意,這會(huì)增加代碼的復(fù)雜性,并可能引入更多的錯(cuò)誤和安全問(wèn)題,因此應(yīng)該謹(jǐn)慎使用。

      二、CBV和FBV的區(qū)別

      FBV(function base views) 基于函數(shù)的視圖,就是在視圖里使用函數(shù)處理請(qǐng)求。
      CBV(class base views) 基于類(lèi)的視圖,就是在視圖里使用類(lèi)處理請(qǐng)求。

      2.1、FBV結(jié)構(gòu)示例

      # urls.py 文件
      urlpatterns = [
          path("login/", views.login),
      ]
      
      # views.py文件
      from django.shortcuts import render,HttpResponse
      
      def login(request):
          if request.method == "GET":
              return HttpResponse("GET 方法")
          if request.method == "POST":
              user = request.POST.get("user")
              pwd = request.POST.get("pwd")
              if user == "eword" and pwd == "123456":
                  return HttpResponse("POST 方法")
              else:
                  return HttpResponse("POST 方法1")
      

      2.2、CBV 結(jié)構(gòu)示例

      # urls.py 文件
      urlpatterns = [
          path("login/", views.Login.as_view()),
      ]
      
      # views.py文件
      from django.shortcuts import render,HttpResponse
      from django.views import View
      
      class Login(View):
          def get(self,request):
              return HttpResponse("GET 方法")
      
          def post(self,request):
              user = request.POST.get("user")
              pwd = request.POST.get("pwd")
              if user == "eword" and pwd == "123456":
                  return HttpResponse("POST 方法")
              else:
                  return HttpResponse("POST 方法 1")
      

      2.3、FBV下API 示例

      2.3.1、模式一:一個(gè)方法操作所有

      通過(guò)body來(lái)攜帶id等pk參數(shù)。

      # serializers.d4_serializers.py
      from rest_framework import serializers, status
      from d4.models.d4_models import D4Dic
      
      
      class D4DicSerializer(serializers.Serializer):
          # 編號(hào)
          id = serializers.CharField(read_only=False)
          # 父級(jí)ID 1級(jí)統(tǒng)一0000
          fid = serializers.CharField(required=False)
          # 數(shù)值
          value = serializers.CharField(max_length=50)
          # 顯示值
          name = serializers.CharField(max_length=50)
          # 類(lèi)別,用source定義別名
          dic_type = serializers.CharField(max_length=50, source='type')
          # 狀態(tài)   (1=啟用,0=棄用)
          status = serializers.CharField(max_length=6)
          # 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫(xiě)死代碼對(duì)應(yīng)))
          is_locked = serializers.CharField(max_length=2)
          # 簡(jiǎn)碼  一般為  name 內(nèi)容的大寫(xiě)首字母
          code = serializers.CharField(max_length=255)
          # 描述
          description = serializers.CharField(max_length=255)
          # 排序(升序)范圍 0-50000
          sort = serializers.IntegerField()
          # 是否可以刪除 1是 0否
          is_delete = serializers.CharField(max_length=2)
          # 創(chuàng)建人id
          created_by = serializers.CharField(max_length=32)
          # 創(chuàng)建日期
          created_date = serializers.DateTimeField()
          # 更新人id
          update_by = serializers.CharField(max_length=32)
          # 更新日期
          update_date = serializers.DateTimeField()
      
          def create(self, validated_data):
              # 根據(jù)提供的驗(yàn)證過(guò)的數(shù)據(jù)創(chuàng)建并返回一個(gè)新的`Dic`實(shí)例。
              return D4Dic.objects.create(**validated_data)
      
          def update(self, instance, validated_data):
               
              # # 根據(jù)提供的驗(yàn)證過(guò)的數(shù)據(jù)更新和返回一個(gè)已經(jīng)存在的`Snippet`實(shí)例。
              # instance.fid=validated_data.get('fid',instance.fid)
              # instance.value=validated_data.get('value',instance.value)
              # instance.name=validated_data.get('name',instance.name)
              # instance.type=validated_data.get('type',instance.type)
              # instance.status=validated_data.get('status',instance.status)
              # instance.locked=validated_data.get('locked',instance.locked)
              # instance.easy_code=validated_data.get('easy_code',instance.easy_code)
              # instance.description=validated_data.get('description',instance.description)
              # instance.sort=validated_data.get('sort',instance.sort)
              # instance.dict_type=validated_data.get('dict_type',instance.dict_type)
              # instance.is_del=validated_data.get('is_del',instance.is_del)
              # return instance
      
      
              # # 根據(jù)查詢(xún)出來(lái)的示例更新驗(yàn)證過(guò)的數(shù)據(jù)
              # # 通過(guò)以下方式請(qǐng)求
              # #     請(qǐng)求類(lèi)中:dic = D4Dic.objects.get(id=pk)
              # #     請(qǐng)求類(lèi)中:serializer = D4DicSerializer(dic, data=data_json)
              # D4Dic.objects.filter(pk=instance.id).update(**validated_data)
              # updated_d4dic = D4Dic.objects.get(pk=instance.id)
              # return updated_d4dic
          
              # 根據(jù)請(qǐng)求的數(shù)據(jù)查詢(xún)實(shí)例更新驗(yàn)證過(guò)的數(shù)據(jù),這樣可以減少一次查詢(xún)
              # 通過(guò)以下方式請(qǐng)求
              #     請(qǐng)求類(lèi)中:data = request.body.decode('utf-8')  # 將請(qǐng)求的body轉(zhuǎn)為字符串
              #     請(qǐng)求類(lèi)中:data_json = json.loads(data)  # 解析為json
              #     請(qǐng)求類(lèi)中:serializer = D4DicSerializer(instance=data_json, data=data_json)
              if 'id' in instance and instance.get("id"):
                  id = instance.get("id")
              else:
                  raise Exception("id不存在")
              
              
              dic = D4Dic.objects.filter(pk=id)
              if not dic.exists():
                  raise Exception(status.HTTP_404_NOT_FOUND)
      
              dic.update(**validated_data)
              return validated_data
      
      
      # urls.py
      urlpatterns = [
          path('dic', dic_view.Dic),
      
      # dic_view.py  一個(gè)方法解決一個(gè)資源的所有請(qǐng)求。
      from django.shortcuts import HttpResponse, render
      from rest_framework import status
      from d4.models.d4_dic_model import D4Dic
      from d4.serializers.d4_serializers import D4DicSerializer
      import json
      
      
      def Dic(request):
          '''
          FBV下的系統(tǒng)字典管理API  
          '''
          if request.method == "GET":
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              request.encoding = 'utf-8'
              if 'pk' in request.GET and request.GET['pk']:
                  # 單條記錄查詢(xún)
                  pk = request.GET['pk']
                  try:
                      dic = D4Dic.objects.get(id=pk)
                  except D4Dic.DoesNotExist:
                      return HttpResponse(status=status.HTTP_404_NOT_FOUND)
      
                  serializer = D4DicSerializer(dic)
                  # 返回序列化數(shù)據(jù)
                  # return Response(serializer.data)
                  # 返回給 html 模板視圖,自定義字典
                  # return render(request,"dic.html",{"dic":"內(nèi)容"})
                  # 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
                  return render(request, "dic.html", serializer.data)
              else:
                  # 多條記錄查詢(xún)
                  dics = D4Dic.objects.all()
                  serializer = D4DicSerializer(dics, many=True)
                  # serializer.data 轉(zhuǎn)換成 json
                  # 可以使用 from rest_framework.renderers import JSONRenderer
                  # data = JSONRenderer().render(serializer.data)
                  # 可以使用 json.dumps(serializer.data)
                  data = json.dumps(serializer.data)
                  return HttpResponse(data)
          elif request.method == "POST":
              print("POST 被執(zhí)行了")
              data = request.body.decode('utf-8')  # 將請(qǐng)求的body轉(zhuǎn)為字符串
              data_json = json.loads(data)  # 解析為json
              # 添加數(shù)據(jù)請(qǐng)求
              serializer = D4DicSerializer(data=data_json)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)已經(jīng)被保存")
                  print(data_json)
                  return HttpResponse(content=json.dumps(data_json), status=status.HTTP_201_CREATED)
              return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
          elif request.method == "PUT":
              print("PUT 被執(zhí)行")
              data = request.body.decode('utf-8')  # 將請(qǐng)求的body轉(zhuǎn)為字符串
              data_json = json.loads(data)  # 解析為json
              # 修改數(shù)據(jù)請(qǐng)
              serializer = D4DicSerializer(instance=data_json, data=data_json)
              if serializer.is_valid():
                  serializer.save()
                  return HttpResponse(json.dumps(serializer.data))
              return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
          elif request.method == "DELETE":
              print("DELETE被執(zhí)行")
              # 刪除數(shù)據(jù)請(qǐng)求
              if 'pk' in request.GET and request.GET['pk']:
                  # 單條記錄查詢(xún)
                  pk = request.GET['pk']
                  try:
                      dic = D4Dic.objects.get(id=pk)
                  except D4Dic.DoesNotExist:
                      print("查不到數(shù)據(jù)")
                      return HttpResponse(status=status.HTTP_404_NOT_FOUND)
                  dic.delete()
                  return HttpResponse(status=status.HTTP_204_NO_CONTENT)
              else:
                  return HttpResponse("pk不存在")
          # 默認(rèn)返回
          return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
      

      方別使用 GET、POST、PUT、DELETE的形式訪問(wèn)一下 http://xxx.xxx.xxx.xxx:8000/dichttp://xxx.xxx.xxx.xxx:8000/dic/xxx試一下效果吧。

      2.3.2、模式二:兩個(gè)方法操作分開(kāi)List和Detail

      通過(guò)Url來(lái)攜帶id等pk參數(shù)。

      # serializers.d4_serializers.py
      from rest_framework import serializers, status
      from d4.models.d4_models import D4Dic
      
      
      class D4DicSerializer(serializers.Serializer):
      
          # 編號(hào)
          id = serializers.CharField(read_only=False)
          # 父級(jí)ID 1級(jí)統(tǒng)一0000
          fid = serializers.CharField(required=False)
          # 數(shù)值
          value = serializers.CharField(max_length=50)
          # 顯示值
          name = serializers.CharField(max_length=50)
          # 類(lèi)別,用source定義別名
          dic_type = serializers.CharField(max_length=50, source='type')
          # 狀態(tài)   (1=啟用,0=棄用)
          status = serializers.CharField(max_length=6)
          # 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫(xiě)死代碼對(duì)應(yīng)))
          is_locked = serializers.CharField(max_length=2)
          # 簡(jiǎn)碼  一般為  name 內(nèi)容的大寫(xiě)首字母
          code = serializers.CharField(max_length=255)
          # 描述
          description = serializers.CharField(max_length=255)
          # 排序(升序)范圍 0-50000
          sort = serializers.IntegerField()
          # 是否可以刪除 1是 0否
          is_delete = serializers.CharField(max_length=2)
          # 創(chuàng)建人id
          created_by = serializers.CharField(max_length=32)
          # 創(chuàng)建日期
          created_date = serializers.DateTimeField()
          # 更新人id
          update_by = serializers.CharField(max_length=32)
          # 更新日期
          update_date = serializers.DateTimeField()
      
          def create(self, validated_data):
              # 根據(jù)提供的驗(yàn)證過(guò)的數(shù)據(jù)創(chuàng)建并返回一個(gè)新的`Dic`實(shí)例。
              return D4Dic.objects.create(**validated_data)
      
          def update(self, instance, validated_data):
              # 這里和模式一不一樣,這里直接將 pk 賦值給 instance
              dic = D4Dic.objects.filter(pk=instance)
              if not dic.exists():
                  raise Exception(status.HTTP_404_NOT_FOUND)
      
              dic.update(**validated_data)
              return validated_data
      
      # urls.py
      from django.urls import path, include, re_path
      from d4.views import dic_view
      urlpatterns = [
             # 這里要注意path 和re_path的區(qū)別。
             re_path(r'^dic/$', dic_view.DicList),
             re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', dic_view.DicDetail),
      
      # dic_view.py  兩個(gè)方法操作分開(kāi)List和Detail。
      from django.shortcuts import HttpResponse, render
      from rest_framework import status
      from d4.models.d4_models import D4Dic
      from d4.serializers.d4_serializers import D4DicSerializer
      import json
      
      def DicList(request):
          '''
          FBV下的系統(tǒng)字典管理API
          '''
          if request.method == "GET":
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              # 多條記錄查詢(xún)
              dics = D4Dic.objects.all()
              serializer = D4DicSerializer(dics, many=True)
              # serializer.data 轉(zhuǎn)換成 json
              # 可以使用 from rest_framework.renderers import JSONRenderer
              # data = JSONRenderer().render(serializer.data)
              # 可以使用 json.dumps(serializer.data)
              data = json.dumps(serializer.data)
              return HttpResponse(data)
          elif request.method == "POST":
              print("POST 被執(zhí)行了")
              data = request.body.decode('utf-8')  # 將請(qǐng)求的body轉(zhuǎn)為字符串
              data_json = json.loads(data)  # 解析為json
              # 添加數(shù)據(jù)請(qǐng)求
              serializer = D4DicSerializer(data=data_json)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)已經(jīng)被保存")
                  print(data_json)
                  return HttpResponse(content=json.dumps(data_json), status=status.HTTP_201_CREATED)
              return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
          # 默認(rèn)返回
          return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
      
      
      def DicDetail(request, pk):
          '''
          FBV下的系統(tǒng)字典管理API
          '''
          if request.method == "GET":
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  return HttpResponse(status=status.HTTP_404_NOT_FOUND)
      
              serializer = D4DicSerializer(dic)
              # 返回序列化數(shù)據(jù)
              return HttpResponse(json.dumps(serializer.data))
              # 返回給 html 模板視圖,自定義字典
              # return render(request,"dic.html",{"dic":"內(nèi)容"})
              # 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
              # return render(request, "dic.html", serializer.data)
          elif request.method == "PUT":
              print("PUT 被執(zhí)行")
              data = request.body.decode('utf-8')  # 將請(qǐng)求的body轉(zhuǎn)為字符串
              data_json = json.loads(data)  # 解析為json
              # 修改數(shù)據(jù)請(qǐng)
              serializer = D4DicSerializer(instance=pk, data=data_json)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)被更新了。")
                  return HttpResponse(json.dumps(serializer.data))
              return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
          elif request.method == "DELETE":
              print("DELETE被執(zhí)行")
              # 刪除數(shù)據(jù)請(qǐng)求
              # 單條記錄查詢(xún)
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  print("查不到數(shù)據(jù)")
                  return HttpResponse(status=status.HTTP_404_NOT_FOUND)
              dic.delete()
              return HttpResponse(status=status.HTTP_204_NO_CONTENT)
          # 默認(rèn)返回
          return HttpResponse(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
      

      2.3.3、使用@api_view裝飾器

      @api_view裝飾器函數(shù)簽名:@api_view(http_method_names=['GET'], exclude_from_schema=False)

      • 用來(lái)包裝你的視圖函數(shù),以確保視圖函數(shù)會(huì)收到Request(而不是Django一般的HttpRequest)對(duì)象,并且返回Response(而不是DjangoHttpResponse)對(duì)象,同時(shí)允許你設(shè)置這個(gè)請(qǐng)求的處理方式。
      • @api_view裝飾器還提供了諸如在適當(dāng)時(shí)候返回405 Method Not Allowed響應(yīng),并處理在使用格式錯(cuò)誤的輸入來(lái)訪問(wèn)request.data時(shí)發(fā)生的任何ParseError異常。
      • 簡(jiǎn)單來(lái)說(shuō)使用@api_view 裝飾器的的效果類(lèi)似在 CBV 下繼承APIView 的效果
      from rest_framework.decorators import api_view
      from rest_framework import status
      from rest_framework.response import Response
      from d4.models.d4_models import D4Dic
      from d4.serializers.d4_serializers import D4DicSerializer
      import json
      
      
      @api_view(['GET', "POST"])
      def DicList(request):
          '''
          FBV下的系統(tǒng)字典管理API
          '''
          if request.method == "GET":
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              # 多條記錄查詢(xún)
              dics = D4Dic.objects.all()
              serializer = D4DicSerializer(dics, many=True)
              # serializer.data 轉(zhuǎn)換成 json
              # 可以使用 from rest_framework.renderers import JSONRenderer
              # data = JSONRenderer().render(serializer.data)
              # 可以使用 json.dumps(serializer.data)
              data = json.dumps(serializer.data)
              return Response(data)
          elif request.method == "POST":
              print("POST 被執(zhí)行了")
              # data = request.body.decode('utf-8')  # 將請(qǐng)求的body轉(zhuǎn)為字符串
              # data_json = json.loads(data)  # 解析為json
              # 添加數(shù)據(jù)請(qǐng)求
              serializer = D4DicSerializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)已經(jīng)被保存")
                  print(request.data)
                  return Response(data=serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
          # 默認(rèn)返回
          return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
      
      @api_view(['GET', "PUT", "DELETE"])
      def DicDetail(request, pk):
          '''
          FBV下的系統(tǒng)字典管理API
          '''
          if request.method == "GET":
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  return Response(status=status.HTTP_404_NOT_FOUND)
      
              serializer = D4DicSerializer(dic)
              # 返回序列化數(shù)據(jù)
              return Response(json.dumps(serializer.data))
              # 返回給 html 模板視圖,自定義字典
              # return render(request,"dic.html",{"dic":"內(nèi)容"})
              # 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
              # return render(request, "dic.html", serializer.data)
          elif request.method == "PUT":
              print("PUT 被執(zhí)行")
              data = request.body.decode('utf-8')  # 將請(qǐng)求的body轉(zhuǎn)為字符串
              data_json = json.loads(data)  # 解析為json
              # 修改數(shù)據(jù)請(qǐng)
              serializer = D4DicSerializer(instance=pk, data=data_json)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)被更新了。")
                  return Response(json.dumps(serializer.data))
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
          elif request.method == "DELETE":
              print("DELETE被執(zhí)行")
              # 刪除數(shù)據(jù)請(qǐng)求
              # 單條記錄查詢(xún)
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  print("查不到數(shù)據(jù)")
                  return Response(status=status.HTTP_404_NOT_FOUND)
              dic.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
          # 默認(rèn)返回
          return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
      
      

      三、View源碼解析

      3.1、as_view()方法

      用來(lái)識(shí)別請(qǐng)求類(lèi)型,并調(diào)用dispatch函數(shù)分發(fā)到對(duì)應(yīng)的處理函數(shù)

      # as_view()方法
          @classonlymethod
          def as_view(cls, **initkwargs):
              """Main entry point for a request-response process."""
              for key in initkwargs:
                  if key in cls.http_method_names:
                      raise TypeError(
                          "The method name %s is not accepted as a keyword argument "
                          "to %s()." % (key, cls.__name__)
                      )
                  if not hasattr(cls, key):
                      raise TypeError(
                          "%s() received an invalid keyword %r. as_view "
                          "only accepts arguments that are already "
                          "attributes of the class." % (cls.__name__, key)
                      )
      
              def view(request, *args, **kwargs):
                  self = cls(**initkwargs)
                  self.setup(request, *args, **kwargs)
                  if not hasattr(self, "request"):
                      raise AttributeError(
                          "%s instance has no 'request' attribute. Did you override "
                          "setup() and forget to call super()?" % cls.__name__
                      )
                  ## 調(diào)用dispatch分發(fā)任務(wù)
                  return self.dispatch(request, *args, **kwargs)
      
              view.view_class = cls
              view.view_initkwargs = initkwargs
      
              # __name__ and __qualname__ are intentionally left unchanged as
              # view_class should be used to robustly determine the name of the view
              # instead.
              view.__doc__ = cls.__doc__
              view.__module__ = cls.__module__
              view.__annotations__ = cls.dispatch.__annotations__
              # Copy possible attributes set by decorators, e.g. @csrf_exempt, from
              # the dispatch method.
              view.__dict__.update(cls.dispatch.__dict__)
      
              # Mark the callback if the view class is async.
              if cls.view_is_async:
                  view._is_coroutine = asyncio.coroutines._is_coroutine
      
              return view
      

      3.2、dispatch()方法

      用來(lái)根據(jù)請(qǐng)求類(lèi)型分發(fā)到對(duì)應(yīng)的處理函數(shù),請(qǐng)求類(lèi)型包括:

      • get
      • post
      • put
      • patch
      • delete
      • head
      • options
      • trace
      # dispatch()方法
          def dispatch(self, request, *args, **kwargs):
              # Try to dispatch to the right method; if a method doesn't exist,
              # defer to the error handler. Also defer to the error handler if the
              # request method isn't on the approved list.
              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
              return handler(request, *args, **kwargs)
      
      

      3.3、請(qǐng)求順序

      • 舉例eg.
        - get請(qǐng)求
        - view()==>return dispatch()==>return get()
        -post請(qǐng)求
        - view()==>return dispatch()==>return post()

      3.4、CBV 下繼承View的 API 示例

      serializers.d4_serializers.py 文件與2.3.2一致
      繼承于View類(lèi)的 DicList類(lèi)和DicDetail類(lèi)無(wú)法在類(lèi)的方法上使用@api_view 裝飾器,所以這些方法的 request 和 response 都是 HttpRequest和 HttpResponse。

      # urls.py文件
      
      from django.urls import path, include, re_path
      from d4.views import dic_view
      
      
      urlpatterns = [
          re_path(r'^dic/$', dic_view.DicList.as_view()),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', dic_view.DicDetail.as_view()),
      ]
      
      
      # dic_view.py
      from rest_framework.views import View
      from rest_framework import status
      from d4.models.d4_models import D4Dic
      from d4.serializers.d4_serializers import D4DicSerializer
      from django.http import HttpResponse
      import json
      
      # DicList類(lèi)
      class DicList(View):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
      
          
          def get(self, request):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              # 多條記錄查詢(xún)
              dics = D4Dic.objects.all()
              serializer = D4DicSerializer(dics, many=True)
              # serializer.data 轉(zhuǎn)換成 json
              # 可以使用 from rest_framework.renderers import JSONRenderer
              # data = JSONRenderer().render(serializer.data)
              # 可以使用 json.dumps(serializer.data)
              data = json.dumps(serializer.data)
              return HttpResponse(data)
      
          
          def post(self, request):
              print("POST 被執(zhí)行了")
              data = request.body.decode('utf-8')  # 將請(qǐng)求的body轉(zhuǎn)為字符串
              data_json = json.loads(data)  # 解析為json
              # 添加數(shù)據(jù)請(qǐng)求
              serializer = D4DicSerializer(data=data_json)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)已經(jīng)被保存")
                  print(data_json)
                  return HttpResponse(json.dumps(serializer.data), status=status.HTTP_201_CREATED)
              return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
      # DicDetail類(lèi)
      class DicDetail(View):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
      
          def get(self, request, pk):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  return HttpResponse(status=status.HTTP_404_NOT_FOUND)
      
              serializer = D4DicSerializer(dic)
              # 返回序列化數(shù)據(jù)
              return HttpResponse(json.dumps(serializer.data))
              # 返回給 html 模板視圖,自定義字典
              # return render(request,"dic.html",{"dic":"內(nèi)容"})
              # 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
              # return render(request, "dic.html", serializer.data)
      
          def put(self, request, pk):
              print("PUT 被執(zhí)行")
              data = request.body.decode('utf-8')  # 將請(qǐng)求的body轉(zhuǎn)為字符串
              data_json = json.loads(data)  # 解析為json
              # 修改數(shù)據(jù)請(qǐng)
              serializer = D4DicSerializer(instance=pk, data=data_json)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)被更新了。")
                  return HttpResponse(json.dumps(serializer.data))
              return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          def delete(self, request, pk):
              print("DELETE被執(zhí)行")
              # 刪除數(shù)據(jù)請(qǐng)求
              # 單條記錄查詢(xún)
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  print("查不到數(shù)據(jù)")
                  return HttpResponse(status=status.HTTP_404_NOT_FOUND)
              dic.delete()
              return HttpResponse(status=status.HTTP_204_NO_CONTENT)
      

      四、APIView源碼解析

      APIView繼承于View,擴(kuò)展了以下功能

      • 重寫(xiě)了as_view()方法和dispatch()方法用來(lái)構(gòu)建新的request對(duì)象,統(tǒng)一請(qǐng)求數(shù)據(jù)格式。
        • eg.
          • a=1&b=2 格式轉(zhuǎn)換成統(tǒng)一的 json 格式{ a:1, b:2 }
      • 重寫(xiě)了as_view()方法并在dispatch()方法進(jìn)行路由分發(fā)前,會(huì)對(duì)請(qǐng)求的客戶(hù)端進(jìn)行身份認(rèn)證、權(quán)限檢查、流浪控制。

      4.1、as_view()方法

      # as_view()方法
      
          @classmethod
          def as_view(cls, **initkwargs):
              """
              Store the original class on the view function.
      
              This allows us to discover information about the view when we do URL
              reverse lookups.  Used for breadcrumb generation.
              """
              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
      
              view = super().as_view(**initkwargs)        ## <---- 這里
              view.cls = cls
              view.initkwargs = initkwargs
      
              # Note: session based authentication is explicitly CSRF validated,
              # all other authentication is CSRF exempt.
              return csrf_exempt(view)          ## <---- 這里
      
      

      4.2、dispatch()方法

      # dispatch()方法
      
      
          # Note: Views are made CSRF exempt from within `as_view` as to prevent
          # accidental removal of this exemption in cases where `dispatch` needs to
          # be overridden.
          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 = self.initialize_request(request, *args, **kwargs)     ## <---- 這里構(gòu)建新的request對(duì)象,統(tǒng)一請(qǐng)求數(shù)據(jù)格式
              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:     ## <--分發(fā)邏輯幾乎和View一樣。
                      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.3、請(qǐng)求順序

      和View類(lèi)幾乎一樣

      • 舉例eg.
        - get請(qǐng)求
        - view()==>return dispatch()==>return get()
        -post請(qǐng)求
        - view()==>return dispatch()==>return post()

      CBV

      4.4、CBV 下繼承APIView的API 示例

      D4DicSerializer 序列化類(lèi)與 2.3.2的 serializers.d4_serializers.py 文件一致。

      # urls.py文件
          re_path(r'^dic/$', dic_view.DicList.as_view()),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', dic_view.DicDetail.as_view()),
      
      # dic_view.py
      
      from rest_framework.response import Response
      from rest_framework import status
      from d4.models.d4_models import D4Dic
      from d4.serializers.d4_serializers import D4DicSerializer
      from rest_framework.views import APIView
      import json
      
      
      class DicList(APIView):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          def get(self, request):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              # 多條記錄查詢(xún)
              dics = D4Dic.objects.all()
              serializer = D4DicSerializer(dics, many=True)
              # serializer.data 轉(zhuǎn)換成 json
              # 可以使用 from rest_framework.renderers import JSONRenderer
              # data = JSONRenderer().render(serializer.data)
              # 可以使用 json.dumps(serializer.data)
              data = json.dumps(serializer.data)
              return Response(json.loads(data))
      
          def post(self, request):
              print("POST 被執(zhí)行了")
              # 添加數(shù)據(jù)請(qǐng)求
              serializer = D4DicSerializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)已經(jīng)被保存")
                  print(request.data)
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
      
      class DicDetail(APIView):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
      
          def get(self, request, pk):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  return Response(status=status.HTTP_404_NOT_FOUND)
      
              serializer = D4DicSerializer(dic)
              # 返回序列化數(shù)據(jù)
              return Response(serializer.data)
              # 返回給 html 模板視圖,自定義字典
              # return render(request,"dic.html",{"dic":"內(nèi)容"})
              # 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
              # return render(request, "dic.html", serializer.data)
      
          def put(self, request, pk):
              print("PUT 被執(zhí)行")
              # 修改數(shù)據(jù)請(qǐng)
              serializer = D4DicSerializer(instance=pk, data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)被更新了。")
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          def delete(self, request, pk):
              print("DELETE被執(zhí)行")
              # 刪除數(shù)據(jù)請(qǐng)求
              # 單條記錄查詢(xún)
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  print("查不到數(shù)據(jù)")
                  return Response(status=status.HTTP_404_NOT_FOUND)
              dic.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      
      

      五、序列化與反序列化

      Serializer 序列化器,把像查詢(xún)集和模型實(shí)例這樣的復(fù)雜數(shù)據(jù)轉(zhuǎn)換為可以輕松渲染成JSONXML或其他內(nèi)容類(lèi)型的原生Python類(lèi)型。序列化器還提供反序列化,在驗(yàn)證傳入的數(shù)據(jù)之后允許解析數(shù)據(jù)轉(zhuǎn)換回復(fù)雜類(lèi)型。
      序列化器構(gòu)造函數(shù):Serializer(instance=None, data=empty, **kwarg)

      • 說(shuō)明:
          1. 用于序列化時(shí),將模型類(lèi)對(duì)象傳入instance參數(shù)
          1. 用于反序列化時(shí),將要被反序列化的數(shù)據(jù)傳入data參數(shù)
          1. 除了instancedata參數(shù)外,在構(gòu)造Serializer對(duì)象時(shí),還可通過(guò)context參數(shù)額外添加數(shù)據(jù),如serializer = AccountSerializer(account, context={'request': request})
            通過(guò)context參數(shù)附加的數(shù)據(jù),可以通過(guò)Serializer對(duì)象的context屬性獲取。
      • 注意
          1. 序列化器聲明了以后,在視圖中調(diào)用 后才會(huì)執(zhí)行。
          1. 不管是序列化還是反序列化,都需要通過(guò)構(gòu)造函數(shù)把數(shù)據(jù)傳遞到序列化器。
          1. 序列化器的字段聲明類(lèi)似 Model
          1. django drf 框架在使用序列化器進(jìn)行序列化時(shí)會(huì)把模型數(shù)據(jù)轉(zhuǎn)換成字典。
          1. django drf 框架的視圖在返回 Response時(shí)會(huì)把字典轉(zhuǎn)換成json。或者把客戶(hù)端發(fā)送過(guò)來(lái)的數(shù)據(jù)(request.data)轉(zhuǎn)換 成 字典.

      5.1、原始序列化器

      
      from rest_framework import serializers
      from models.d4_dic_model import D4Dic
      
      
      class D4DicSerializer(serializers.Serializer):
          # 編號(hào)
          id = serializers.CharField(read_only=True)
          # 父級(jí)ID 1級(jí)統(tǒng)一0000
          fid = serializers.CharField(required=False)
          # 數(shù)值
          value = serializers.CharField(max_length=50)
          # 顯示值
          name = serializers.CharField(max_length=50)
          # 類(lèi)別,用source定義別名
          type = serializers.CharField(max_length=50, source='dic_type')
          # 狀態(tài)   (1=啟用,0=棄用)
          status = serializers.CharField(max_length=6)
          # 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫(xiě)死代碼對(duì)應(yīng)))
          is_locked = serializers.CharField(max_length=2)
          # 簡(jiǎn)碼  一般為  name 內(nèi)容的大寫(xiě)首字母
          code = serializers.CharField(max_length=255)
          # 描述
          description = serializers.CharField(max_length=255)
          # 排序(升序)范圍 0-50000
          sort = serializers.IntegerField()
          # 是否可以刪除 1是 0否
          is_delete = serializers.CharField(max_length=2)
          # 創(chuàng)建人id
          created_by = serializers.CharField(max_length=32, null=False)
          # 創(chuàng)建日期
          created_date = serializers.DateTimeField(null=False)
          # 更新人id
          update_by = serializers.CharField(max_length=32, blank=True, null=True)
          # 更新日期
          update_date = serializers.DateTimeField(blank=True, null=True)
      

      5.2、save()方法下的序列化器

      Serializers 的父類(lèi)BaseSerializer.save()方法如下:

          def save(self, **kwargs):
              assert hasattr(self, '_errors'), (
                  'You must call `.is_valid()` before calling `.save()`.'
              )
      
              assert not self.errors, (
                  'You cannot call `.save()` on a serializer with invalid data.'
              )
      
              # Guard against incorrect use of `serializer.save(commit=False)`
              assert 'commit' not in kwargs, (
                  "'commit' is not a valid keyword argument to the 'save()' method. "
                  "If you need to access data before committing to the database then "
                  "inspect 'serializer.validated_data' instead. "
                  "You can also pass additional keyword arguments to 'save()' if you "
                  "need to set extra attributes on the saved model instance. "
                  "For example: 'serializer.save(owner=request.user)'.'"
              )
      
              assert not hasattr(self, '_data'), (
                  "You cannot call `.save()` after accessing `serializer.data`."
                  "If you need to access data before committing to the database then "
                  "inspect 'serializer.validated_data' instead. "
              )
      
              validated_data = {**self.validated_data, **kwargs}
      
              if self.instance is not None:
                  # 注意這里
                  self.instance = self.update(self.instance, validated_data)
                  assert self.instance is not None, (
                      '`update()` did not return an object instance.'
                  )
              else:
                  # 注意這里
                  self.instance = self.create(validated_data)
                  assert self.instance is not None, (
                      '`create()` did not return an object instance.'
                  )
      
              return self.instance
      

      BaseSerializer.save()方法判斷了序列化器是否傳了instance 參數(shù)。

      • 如果傳了就調(diào)用 def update(self, instance, validated_data)方法。
      • 如果沒(méi)有就調(diào)用def create(self, validated_data)方法
        BaseSerializer 中定義的updatecreate 方法如下:
          def update(self, instance, validated_data):
              raise NotImplementedError('`update()` must be implemented.')
      
          def create(self, validated_data):
              raise NotImplementedError('`create()` must be implemented.')
      

      BaseSerializer 中定義的updatecreate 方法,是需要用戶(hù)在子類(lèi)中重載,實(shí)現(xiàn)業(yè)務(wù)邏輯的。這樣用戶(hù)在調(diào)用序列化器的.save()方法時(shí),會(huì)根據(jù)是否傳了instance參數(shù),將通過(guò)校驗(yàn)的validated_data 數(shù)據(jù)傳遞給重載的updatecreate 方法。

      • 注意:
        • 在調(diào)用.save()方法前要先調(diào)用.is_valid()方法,對(duì)傳入的數(shù)據(jù)進(jìn)行校驗(yàn)。

      5.2.1、 在子類(lèi)中重載updatecreate方法

      from rest_framework import serializers, status
      from d4.models.d4_models import D4Dic
      
      
      class D4DicSerializer(serializers.Serializer):
      
          # 編號(hào)
          id = serializers.CharField(read_only=False)
          # 父級(jí)ID 1級(jí)統(tǒng)一0000
          fid = serializers.CharField(required=False)
          # 數(shù)值
          value = serializers.CharField(max_length=50)
          # 顯示值
          name = serializers.CharField(max_length=50)
          # 類(lèi)別,用source定義別名
          dic_type = serializers.CharField(max_length=50, source='type')
          # 狀態(tài)   (1=啟用,0=棄用)
          status = serializers.CharField(max_length=6)
          # 是否鎖定編輯(0=否,1=是(內(nèi)置字典,與寫(xiě)死代碼對(duì)應(yīng)))
          is_locked = serializers.CharField(max_length=2)
          # 簡(jiǎn)碼  一般為  name 內(nèi)容的大寫(xiě)首字母
          code = serializers.CharField(max_length=255)
          # 描述
          description = serializers.CharField(max_length=255)
          # 排序(升序)范圍 0-50000
          sort = serializers.IntegerField()
          # 是否可以刪除 1是 0否
          is_delete = serializers.CharField(max_length=2)
          # 創(chuàng)建人id
          created_by = serializers.CharField(max_length=32)
          # 創(chuàng)建日期
          created_date = serializers.DateTimeField()
          # 更新人id
          update_by = serializers.CharField(max_length=32)
          # 更新日期
          update_date = serializers.DateTimeField()
      
          def create(self, validated_data):
              # 根據(jù)提供的驗(yàn)證過(guò)的數(shù)據(jù)創(chuàng)建并返回一個(gè)新的`Dic`實(shí)例。
              return D4Dic.objects.create(**validated_data)
      
          def update(self, instance, validated_data):
              # 這里和模式一不一樣,這里直接將 pk 賦值給 instance
              dic = D4Dic.objects.filter(pk=instance)
              if not dic.exists():
                  raise Exception(status.HTTP_404_NOT_FOUND)
      
              dic.update(**validated_data)
              return validated_data
      
      

      5.3、ModelSerializer

      通過(guò)繼承ModelSerializer創(chuàng)建序列化器,可以簡(jiǎn)化字段、createupdate的定義。
      ModelSerializer將繼承Serializer時(shí)需要重復(fù)做的字段定義、create方法和update方法進(jìn)行了封裝,簡(jiǎn)化了序列化器的構(gòu)建。

      # serializers.d4_serializers.py
      from rest_framework import serializers
      from d4.models.d4_models import D4Dic
      
      
      class D4DicSerializer(serializers.ModelSerializer):
          # # 通過(guò) source為 type 字段設(shè)置別名dic_type.
          # # 注意:dic_type和 type 是同一個(gè)字段,且會(huì)同時(shí)被序列化,除非把其中一個(gè)exclude
          # dic_type = serializers.CharField(max_length=50, source='type')
      
          class Meta:
              model = D4Dic
      
              # 序列化全部字段
              fields = "__all__"
      
              # # 序列化數(shù)組內(nèi)的字段
              # fields = ['id', 'fid', 'name', 'value']
      
              # # 排除數(shù)組內(nèi)的字段,注意:fields和exclude不能同時(shí)存在。
              # exclude = ['type']
              
              # 只讀字段
              # read_only_fields =  ['id', 'fid', 'name', 'value']
              
              # 修改某些字段選項(xiàng)
              extra_kwargs  = {
                      'value': {'min_value": 0, 'required':True), 
                      'fid': ('min_value': 0, ' required':True)
              }
      

      六、GenericAPIView源碼解析

      • GenericAPIView繼承了APIView,作用是把視圖中的通用代碼抽取出來(lái),讓視圖方法中的代碼更加通用,方便把通用代碼進(jìn)行簡(jiǎn)寫(xiě)。

      • 相較于APIViewGenericAPIView主要增加了操作序列化器和數(shù)據(jù)庫(kù)查詢(xún)的方法,作用是為Mixin擴(kuò)展類(lèi)的執(zhí)行提供方法支持(通常在使用時(shí),可搭配一個(gè)或多個(gè)Mixin擴(kuò)展類(lèi))。

      • GenericAPIView中需要關(guān)注的屬性和方法如下:

        • get_serializer_class(self)
          • 當(dāng)一個(gè)視圖類(lèi)中用到多個(gè)序列化器時(shí),可以通過(guò)get_serializer_cass方法返回的序列化器類(lèi)名判斷是哪個(gè)序列化器。默認(rèn)返回的serializer_class,可以被重寫(xiě)。
        • get_serializer(self, *args, **kwargs)
          • 在視圖中提供獲取序列化器對(duì)象的方法(返回序列化器對(duì)象),主要用來(lái)提供給Mixin擴(kuò)展類(lèi)使用。
          • 注意:該方法會(huì)在返回的的序列化器對(duì)象的context屬性中補(bǔ)充三個(gè)數(shù)據(jù):requestformatview
            • request: 當(dāng)前視圖的請(qǐng)求對(duì)象
            • view: 當(dāng)前請(qǐng)求的類(lèi)視圖對(duì)象
            • format: 當(dāng)前請(qǐng)求期望返回的數(shù)據(jù)格式
        • get_queryset(self)
          • 返回視圖使用的查詢(xún)集,主要用來(lái)提供給Mixin擴(kuò)展類(lèi)使用,是列表視圖與詳情視圖獲取數(shù)據(jù)的基礎(chǔ),默認(rèn)返回 的queryset屬性,可以被重寫(xiě)。

          def get_queryset(self)
              myclass=xxxx_model_class
              return myclass.object.all()
        

        • get_object(self)
          • 執(zhí)行數(shù)據(jù)查詢(xún)過(guò)濾,返回符合查詢(xún)條件的數(shù)據(jù)詳情對(duì)象,主要用來(lái)提供給Mixin擴(kuò)展類(lèi)使用。在試圖中可以調(diào)用該方法獲取詳情信息的模型類(lèi)對(duì)象。
          • 若數(shù)據(jù)不存在,會(huì)返回404
          • 該方法會(huì)默認(rèn)使用APIView提供的check_object _permissions方法檢當(dāng)前對(duì)象是否有權(quán)限被訪問(wèn)。
          • 要配合路由設(shè)置中的有名分組進(jìn)行,get_object 會(huì)根據(jù)有名分組的參數(shù)進(jìn)行查詢(xún)。

      6.1、CBV 下繼承GenericAPIView的API 示例

      # dic_view.py
      from rest_framework.response import Response
      from rest_framework import status
      from d4.models.d4_models import D4Dic
      from d4.serializers.d4_serializers import D4DicSerializer
      from rest_framework.generics import GenericAPIView
      
      
      class DicList(GenericAPIView):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def get(self, request):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              # 多條記錄查詢(xún)
              # list = self.queryset()
              # serializer = self.get_serializer_class()(self.queryset(), many=True)
              serializer = self.get_serializer(self.get_queryset(), many=True)
              return Response(serializer.data)
      
          def post(self, request):
              print("POST 被執(zhí)行了")
              # 添加數(shù)據(jù)請(qǐng)求
              # serializer = D4DicSerializer(data=request.data)
              serializer = self.get_serializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)已經(jīng)被保存")
                  print(request.data)
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
      
      class DicDetail(GenericAPIView):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def get(self, request, pk):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              # self.get_object() 基于有名路由分組進(jìn)行過(guò)濾
              serializer = self.get_serializer(instance=self.get_object(), many=False)
              # 返回序列化數(shù)據(jù)
              return Response(serializer.data)
              # 返回給 html 模板視圖,自定義字典
              # return render(request,"dic.html",{"dic":"內(nèi)容"})
              # 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
              # return render(request, "dic.html", serializer.data)
      
          def put(self, request, pk):
              print("PUT 被執(zhí)行")
              # 修改數(shù)據(jù)請(qǐng)
              serializer = self.get_serializer(instance=self.get_object(), data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)被更新了。")
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          def delete(self, request, pk):
              print("DELETE被執(zhí)行")
              # 刪除數(shù)據(jù)請(qǐng)求
              # 單條記錄查詢(xún)
              self.get_object().delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      
      

      七、Mixin工具類(lèi)源碼解析

      • Mixin作用:
        • 提供了幾種后端視圖組合,用來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)資源進(jìn)行增刪改查處理流程。即封裝了數(shù)據(jù)資源的增刪改查操作,其他視圖可以通過(guò)繼承相應(yīng)的Mixin擴(kuò)展類(lèi)來(lái)復(fù)用代碼,減少自己編寫(xiě)的代碼量。
        • 這些Mixin擴(kuò)展類(lèi)需要搭配GenericAPIView通用視圖基類(lèi)使用,因?yàn)樗鼈兊膶?shí)現(xiàn)需要調(diào)用GenericAPIView提供的序列化器與數(shù)據(jù)庫(kù)查詢(xún)的方法。

      7.1、Mixin 保函的視圖組合

      7.1.1、CreateModelMixin

      • 作用:
        • 創(chuàng)建(添加)操作視圖擴(kuò)展類(lèi),提供 create(self, request, *args, **kwargs)方法快速實(shí)現(xiàn)創(chuàng)建(添加)視圖,返回201狀態(tài)碼。
      class CreateModelMixin:
          """
          Create a model instance.
          """
          def create(self, request, *args, **kwargs):
              serializer = self.get_serializer(data=request.data)
              serializer.is_valid(raise_exception=True)
              self.perform_create(serializer)
              headers = self.get_success_headers(serializer.data)
              return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
      
          def perform_create(self, serializer):
              serializer.save()
      
          def get_success_headers(self, data):
              try:
                  return {'Location': str(data[api_settings.URL_FIELD_NAME])}
              except (TypeError, KeyError):
                  return {}
      

      7.1.2、ListModelMixin

      • 作用:
        • 列表視圖擴(kuò)展類(lèi),提供 list(request, *args, **kwargs)方法快速實(shí)現(xiàn)列表視圖,返回200狀態(tài)碼。
        • Mixinlist方法會(huì)對(duì)數(shù)據(jù)進(jìn)行過(guò)濾和分頁(yè)。
      class ListModelMixin:
          """
          List a queryset.
          """
          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)
      

      7.1.3、RetrieveModelMixin

      • 作用:
        • 數(shù)據(jù)詳情頁(yè)視圖擴(kuò)展類(lèi),提供 retrieve(self, request, *args, **kwargs)方法快速實(shí)現(xiàn)列表視圖,返回200狀態(tài)碼。
      class RetrieveModelMixin:
          """
          Retrieve a model instance.
          """
          def retrieve(self, request, *args, **kwargs):
              instance = self.get_object()
              serializer = self.get_serializer(instance)
              return Response(serializer.data)
      

      7.1.4、UpdateModelMixin

      • 作用:
        • 更新操作視圖擴(kuò)展類(lèi),提供update(self, request, *args, **kwargs)方法快速實(shí)現(xiàn)列表視圖,返回200狀態(tài)碼。
      class UpdateModelMixin:
          """
          Update a model instance.
          """
          def update(self, request, *args, **kwargs):
              partial = kwargs.pop('partial', False)
              instance = self.get_object()
              serializer = self.get_serializer(instance, data=request.data, partial=partial)
              serializer.is_valid(raise_exception=True)
              self.perform_update(serializer)
      
              if getattr(instance, '_prefetched_objects_cache', None):
                  # If 'prefetch_related' has been applied to a queryset, we need to
                  # forcibly invalidate the prefetch cache on the instance.
                  instance._prefetched_objects_cache = {}
      
              return Response(serializer.data)
      
          def perform_update(self, serializer):
              serializer.save()
      
          def partial_update(self, request, *args, **kwargs):
              kwargs['partial'] = True
              return self.update(request, *args, **kwargs)
      
      

      7.1.5、DestroyModelMixin

      • 作用:
        • 刪除操作視圖擴(kuò)展類(lèi),提供destroy(self, request, *args, **kwargs)方法快速實(shí)現(xiàn)列表視圖,返回204狀態(tài)碼。
      class DestroyModelMixin:
          """
          Destroy a model instance.
          """
          def destroy(self, request, *args, **kwargs):
              instance = self.get_object()
              self.perform_destroy(instance)
              return Response(status=status.HTTP_204_NO_CONTENT)
      
          def perform_destroy(self, instance):
              instance.delete()
      

      7.2、繼承Mixin時(shí)的API 示例

      # dic_view.py
      from d4.models.d4_models import D4Dic
      from d4.serializers.d4_serializers import D4DicSerializer
      from rest_framework.generics import GenericAPIView
      from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
      
      
      class DicList(GenericAPIView, ListModelMixin, CreateModelMixin):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def get(self, request):
              print("GET 被執(zhí)行")
              return self.list(request)
      
          def post(self, request):
              print("POST 被執(zhí)行了")
              return self.create(request)
      
      
      class DicDetail(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def get(self, request, pk):
              print("GET 被執(zhí)行")
              return self.retrieve(request, pk)
      
          def put(self, request, pk):
              print("PUT 被執(zhí)行")
              return self.update(request, pk)
      
          def delete(self, request, pk):
              print("DELETE被執(zhí)行")
              return self.destroy(request, pk)
      
      

      7.3、Mixin 再封裝

      由7.2k也看出DicList類(lèi)和DicDetail類(lèi)表示了Dic資源的5個(gè)操作(增刪改查查),而其他資源如果也只想實(shí)現(xiàn)這5個(gè)操作,那么代碼相似度是極高的。因此可以對(duì)其進(jìn)行二次封裝,封裝的思路主要是簡(jiǎn)化了繼承關(guān)系和對(duì)應(yīng)的操作。封裝的類(lèi)如下:

      7.3.1、CreateAPIView

      繼承了CreateModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了post操作。

      class CreateAPIView(mixins.mixins.CreateModelMixin,
                          GenericAPIView):
          """
          Concrete view for creating a model instance.
          """
          def post(self, request, *args, **kwargs):
              return self.create(request, *args, **kwargs)
      

      7.3.2、ListAPIView

      繼承了ListModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了get操作。

      class ListAPIView(mixins.ListModelMixin,
                        GenericAPIView):
          """
          Concrete view for listing a queryset.
          """
          def get(self, request, *args, **kwargs):
              return self.list(request, *args, **kwargs)
      

      7.3.3、RetrieveAPIView

      繼承了RetrieveModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了get操作。

      class RetrieveAPIView(mixins.RetrieveModelMixin,
                            GenericAPIView):
          """
          Concrete view for retrieving a model instance.
          """
          def get(self, request, *args, **kwargs):
              return self.retrieve(request, *args, **kwargs)
      

      7.3.4、DestroyAPIView

      繼承了DestroyModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了delete操作。

      class DestroyAPIView(mixins.DestroyModelMixin,
                           GenericAPIView):
          """
          Concrete view for deleting a model instance.
          """
          def delete(self, request, *args, **kwargs):
              return self.destroy(request, *args, **kwargs)
      

      7.3.5、UpdateAPIView

      繼承了UpdateModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了putpatch操作。

      class UpdateAPIView(mixins.UpdateModelMixin,
                          GenericAPIView):
          """
          Concrete view for updating a model instance.
          """
          def put(self, request, *args, **kwargs):
              return self.update(request, *args, **kwargs)
      
          def patch(self, request, *args, **kwargs):
              return self.partial_update(request, *args, **kwargs)
      

      7.3.6、ListCreateAPIView

      繼承了ListModelMixinCreateModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了getpost操作。

      class ListCreateAPIView(mixins.ListModelMixin,
                              mixins.CreateModelMixin,
                              GenericAPIView):
          """
          Concrete view for listing a queryset or creating a model instance.
          """
          def get(self, request, *args, **kwargs):
              return self.list(request, *args, **kwargs)
      
          def post(self, request, *args, **kwargs):
              return self.create(request, *args, **kwargs)
      

      7.3.7、RetrieveUpdateAPIView

      繼承了RetrieveModelMixinUpdateModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了getputpatch操作。

      class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
                                  mixins.UpdateModelMixin,
                                  GenericAPIView):
          """
          Concrete view for retrieving, updating a model instance.
          """
          def get(self, request, *args, **kwargs):
              return self.retrieve(request, *args, **kwargs)
      
          def put(self, request, *args, **kwargs):
              return self.update(request, *args, **kwargs)
      
          def patch(self, request, *args, **kwargs):
              return self.partial_update(request, *args, **kwargs)
      

      7.3.8、RetrieveDestroyAPIView

      繼承了RetrieveModelMixinDestroyModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了getdelete操作。

      class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
                                   mixins.DestroyModelMixin,
                                   GenericAPIView):
          """
          Concrete view for retrieving or deleting a model instance.
          """
          def get(self, request, *args, **kwargs):
              return self.retrieve(request, *args, **kwargs)
      
          def delete(self, request, *args, **kwargs):
              return self.destroy(request, *args, **kwargs)
      

      7.3.9、RetrieveUpdateDestroyAPIView

      繼承了RetrieveModelMixinUpdateModelMixinDestroyModelMixin類(lèi)和GenericAPIView類(lèi),并實(shí)現(xiàn)了getputpatchdelete操作。

      class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
                                         mixins.UpdateModelMixin,
                                         mixins.DestroyModelMixin,
                                         GenericAPIView):
          """
          Concrete view for retrieving, updating or deleting a model instance.
          """
          def get(self, request, *args, **kwargs):
              return self.retrieve(request, *args, **kwargs)
      
          def put(self, request, *args, **kwargs):
              return self.update(request, *args, **kwargs)
      
          def patch(self, request, *args, **kwargs):
              return self.partial_update(request, *args, **kwargs)
      
          def delete(self, request, *args, **kwargs):
              return self.destroy(request, *args, **kwargs)
      

      7.4、Mixin再封裝后的API示例

      這里DicList類(lèi)和DicDetail類(lèi)分別繼承了ListCreateAPIViewRetrieveUpdateDestroyAPIView,簡(jiǎn)化了代碼。

      from d4.models.d4_models import D4Dic
      from d4.serializers.d4_serializers import D4DicSerializer
      from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
      
      
      class DicList(ListCreateAPIView):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
      
      class DicDetail(RetrieveUpdateDestroyAPIView):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
      

      八、ViewSet源碼解析

      ViewSetMixin 重構(gòu)了分發(fā)機(jī)制。
      通過(guò)路由設(shè)置改變操作綁定的處理函數(shù)。

      • eg.
        • get操作綁定操作函數(shù)為get_object,post操作綁定操作函數(shù)為post_object。
        • re_path(r'^dic/$', DicList.as_view("get":"get_object", "post":"post_object"))

      8.1、ViewSetMixin

      class ViewSetMixin:
          """
          This is the magic.
      
          Overrides `.as_view()` so that it takes an `actions` keyword that performs
          the binding of HTTP methods to actions on the Resource.
      
          For example, to create a concrete view binding the 'GET' and 'POST' methods
          to the 'list' and 'create' actions...
      
          view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
          """
      
          @classonlymethod
          def as_view(cls, actions=None, **initkwargs):
              """
              Because of the way class based views create a closure around the
              instantiated view, we need to totally reimplement `.as_view`,
              and slightly modify the view function that is created and returned.
              """
              # The name and description initkwargs may be explicitly overridden for
              # certain route configurations. eg, names of extra actions.
              cls.name = None
              cls.description = None
      
              # The suffix initkwarg is reserved for displaying the viewset type.
              # This initkwarg should have no effect if the name is provided.
              # eg. 'List' or 'Instance'.
              cls.suffix = None
      
              # The detail initkwarg is reserved for introspecting the viewset type.
              cls.detail = None
      
              # Setting a basename allows a view to reverse its action urls. This
              # value is provided by the router through the initkwargs.
              cls.basename = None
      
              # actions must not be empty
              if not actions:
                  raise TypeError("The `actions` argument must be provided when "
                                  "calling `.as_view()` on a ViewSet. For example "
                                  "`.as_view({'get': 'list'})`")
      
              # sanitize keyword arguments
              for key in initkwargs:
                  if key in cls.http_method_names:
                      raise TypeError("You tried to pass in the %s method name as a "
                                      "keyword argument to %s(). Don't do that."
                                      % (key, cls.__name__))
                  if not hasattr(cls, key):
                      raise TypeError("%s() received an invalid keyword %r" % (
                          cls.__name__, key))
      
              # name and suffix are mutually exclusive
              if 'name' in initkwargs and 'suffix' in initkwargs:
                  raise TypeError("%s() received both `name` and `suffix`, which are "
                                  "mutually exclusive arguments." % (cls.__name__))
              # 》》》》》《《《《《
              # 核心代碼重構(gòu)了VIew》》》》》《《《《《
              # 》》》》》《《《《《
              def view(request, *args, **kwargs):
                  self = cls(**initkwargs)
      
                  if 'get' in actions and 'head' not in actions:
                      actions['head'] = actions['get']
      
                  # We also store the mapping of request methods to actions,
                  # so that we can later set the action attribute.
                  # eg. `self.action = 'list'` on an incoming GET request.
                  self.action_map = actions
      
                  # Bind methods to actions
                  # This is the bit that's different to a standard view
                  # 》》》》》《《《《《
                  # 核心代碼,通過(guò)循環(huán)重新綁定了操作函數(shù)/方法。》》》》》《《《《《
                  # 》》》》》《《《《《
                  # actions  === .as_view({"get":"get_object", "post":"post_object"})
                  for method, action in actions.items():
                      handler = getattr(self, action)
                      # 將  get 操作的執(zhí)行函數(shù)設(shè)置成get_object
                      setattr(self, method, handler)
      
                  self.request = request
                  self.args = args
                  self.kwargs = kwargs
      
                  # And continue as usual
                  return self.dispatch(request, *args, **kwargs)
      
              # take name and docstring from class
              update_wrapper(view, cls, updated=())
      
              # and possible attributes set by decorators
              # like csrf_exempt from dispatch
              update_wrapper(view, cls.dispatch, assigned=())
      
              # We need to set these on the view function, so that breadcrumb
              # generation can pick out these bits of information from a
              # resolved URL.
              view.cls = cls
              view.initkwargs = initkwargs
              view.actions = actions
              return csrf_exempt(view)
      
          def initialize_request(self, request, *args, **kwargs):
              """
              Set the `.action` attribute on the view, depending on the request method.
              """
              request = super().initialize_request(request, *args, **kwargs)
              method = request.method.lower()
              if method == 'options':
                  # This is a special case as we always provide handling for the
                  # options method in the base `View` class.
                  # Unlike the other explicitly defined actions, 'metadata' is implicit.
                  self.action = 'metadata'
              else:
                  self.action = self.action_map.get(method)
              return request
      
          def reverse_action(self, url_name, *args, **kwargs):
              """
              Reverse the action for the given `url_name`.
              """
              url_name = '%s-%s' % (self.basename, url_name)
              namespace = None
              if self.request and self.request.resolver_match:
                  namespace = self.request.resolver_match.namespace
              if namespace:
                  url_name = namespace + ':' + url_name
              kwargs.setdefault('request', self.request)
      
              return reverse(url_name, *args, **kwargs)
      
          @classmethod
          def get_extra_actions(cls):
              """
              Get the methods that are marked as an extra ViewSet `@action`.
              """
              return [_check_attr_name(method, name)
                      for name, method
                      in getmembers(cls, _is_extra_action)]
      
          def get_extra_action_url_map(self):
              """
              Build a map of {names: urls} for the extra actions.
      
              This method will noop if `detail` was not provided as a view initkwarg.
              """
              action_urls = OrderedDict()
      
              # exit early if `detail` has not been provided
              if self.detail is None:
                  return action_urls
      
              # filter for the relevant extra actions
              actions = [
                  action for action in self.get_extra_actions()
                  if action.detail == self.detail
              ]
      
              for action in actions:
                  try:
                      url_name = '%s-%s' % (self.basename, action.url_name)
                      namespace = self.request.resolver_match.namespace
                      if namespace:
                          url_name = '%s:%s' % (namespace, url_name)
      
                      url = reverse(url_name, self.args, self.kwargs, request=self.request)
                      view = self.__class__(**action.kwargs)
                      action_urls[view.get_view_name()] = url
                  except NoReverseMatch:
                      pass  # URL requires additional arguments, ignore
      
              return action_urls
      

      8.2、ViewSet

      ViewSet繼承了ViewSetMixin和APIView,除了有APIView的所有功能外,還支持制定路由分發(fā)。

      8.2.1、ViewSet源碼

      class ViewSet(ViewSetMixin, views.APIView):
          """
          The base ViewSet class does not provide any actions by default.
          """
          pass
      

      8.2.2、繼承ViewSet時(shí)的 API 示例

      # urls.py
      from django.urls import re_path
      from d4.d4_views import DicViewSet
      
      
      urlpatterns = [
          re_path(r'^dic/$', DicViewSet.as_view({"get": "get_list", "post": "add"})),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicViewSet.as_view({"get": "get_object", "put": "update"})),
      ]
      
      
      # dic_view.py
      from rest_framework.response import Response
      from rest_framework import status
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import ViewSet
      import json
      
      
      class DicViewSet(ViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          def get_list(self, request):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              # 多條記錄查詢(xún)
              dics = D4Dic.objects.all()
              serializer = D4DicSerializer(dics, many=True)
              # serializer.data 轉(zhuǎn)換成 json
              # 可以使用 from rest_framework.renderers import JSONRenderer
              # data = JSONRenderer().render(serializer.data)
              # 可以使用 json.dumps(serializer.data)
              data = json.dumps(serializer.data)
              return Response(json.loads(data))
      
          def add(self, request):
              print("POST 被執(zhí)行了")
              # 添加數(shù)據(jù)請(qǐng)求
              serializer = D4DicSerializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)已經(jīng)被保存")
                  print(request.data)
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
      
          def get_object(self, request, pk):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  return Response(status=status.HTTP_404_NOT_FOUND)
      
              serializer = D4DicSerializer(dic)
              # 返回序列化數(shù)據(jù)
              return Response(serializer.data)
              # 返回給 html 模板視圖,自定義字典
              # return render(request,"dic.html",{"dic":"內(nèi)容"})
              # 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
              # return render(request, "dic.html", serializer.data)
      
          def update(self, request, pk):
              print("PUT 被執(zhí)行")
              # 修改數(shù)據(jù)請(qǐng)
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  return Response(status=status.HTTP_404_NOT_FOUND)
              serializer = D4DicSerializer(instance=dic, data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)被更新了。")
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          def delete(self, request, pk):
              print("DELETE被執(zhí)行")
              # 刪除數(shù)據(jù)請(qǐng)求
              # 單條記錄查詢(xún)
              try:
                  dic = D4Dic.objects.get(id=pk)
              except D4Dic.DoesNotExist:
                  print("查不到數(shù)據(jù)")
                  return Response(status=status.HTTP_404_NOT_FOUND)
              dic.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      
      

      8.3、GenericViewSet

      ViewSet繼承了ViewSetMixin和GenericAPIView,除了有GenericAPIView的所有功能外,還支持制定路由分發(fā)。

      8.3.1、GenericViewSet 源碼

      class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
          """
          The GenericViewSet class does not provide any actions by default,
          but does include the base set of generic view behavior, such as
          the `get_object` and `get_queryset` methods.
          """
          pass
      

      8.3.2、繼承GenericViewSet時(shí)的 API 示例

      # urls.py
      from django.urls import re_path
      from d4.d4_views import DicGVS
      
      
      urlpatterns = [
          re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "add"})),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"put": "update"})),
      ]
      
      
      # dic_view.py
      from rest_framework.response import Response
      from rest_framework import status
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import GenericViewSet
      
      
      class DicGVS(GenericViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def list(self, request):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              # 多條記錄查詢(xún)
              # list = self.queryset()
              # serializer = self.get_serializer_class()(self.queryset(), many=True)
              serializer = self.get_serializer(self.get_queryset(), many=True)
              return Response(serializer.data)
      
          def add(self, request):
              print("POST 被執(zhí)行了")
              # 添加數(shù)據(jù)請(qǐng)求
              # serializer = D4DicSerializer(data=request.data)
              serializer = self.get_serializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)已經(jīng)被保存")
                  print(request.data)
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          def get(self, request, pk):
              print("GET 被執(zhí)行")
              # GET請(qǐng)求
              # self.get_object() 基于有名路由分組進(jìn)行過(guò)濾
              serializer = self.get_serializer(instance=self.get_object(), many=False)
              # 返回序列化數(shù)據(jù)
              return Response(serializer.data)
              # 返回給 html 模板視圖,自定義字典
              # return render(request,"dic.html",{"dic":"內(nèi)容"})
              # 返回給 html 模板視圖,查詢(xún)出來(lái)的結(jié)果
              # return render(request, "dic.html", serializer.data)
      
          def update(self, request, pk):
              print("PUT 被執(zhí)行")
              # 修改數(shù)據(jù)請(qǐng)
              serializer = self.get_serializer(instance=self.get_object(), data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  print("數(shù)據(jù)被更新了。")
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          def delete(self, request, pk):
              print("DELETE被執(zhí)行")
              # 刪除數(shù)據(jù)請(qǐng)求
              # 單條記錄查詢(xún)
              self.get_object().delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      
      

      8.3.3、結(jié)合 Mixin 工具類(lèi)繼承GenericViewSet

      對(duì)8.3.2的dic_view.py進(jìn)行重寫(xiě)。

      # urls.py
      from django.urls import re_path
      from d4.d4_views import DicGVS
      
      
      urlpatterns = [
          re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "create"})),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
      ]
      
      # dic_view.py
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import GenericViewSet
      from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
      
      
      class DicGVS(GenericViewSet, ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
      

      8.4、ReadOnlyModelViewSet

      ViewSet繼承了RetrieveModelMixin、ListModelMixin和GenericViewSet,除了有GenericAPIView的所有功能外,還支持制定路由分發(fā)和簡(jiǎn)化了“查(單數(shù)據(jù)詳情)查(列表數(shù)據(jù))”操作代碼。

      8.4.1、ReadOnlyModelViewSet源碼

      class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
                                 mixins.ListModelMixin,
                                 GenericViewSet):
          """
          A viewset that provides default `list()` and `retrieve()` actions.
          """
          pass
      

      8.4.2、繼承ReadOnlyModelViewSet時(shí)的 API 示例

      # urls.py
      from django.urls import re_path
      from d4.d4_views import DicGVS
      
      
      urlpatterns = [
          re_path(r'^dic/$', DicGVS.as_view({"get": "list"})),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve"})),
      ]
      
      # dic_view.py
      # 只實(shí)現(xiàn)了兩個(gè)查詢(xún),即查詢(xún)符合條件的數(shù)據(jù)詳情和查詢(xún)數(shù)據(jù)列表。
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import ReadOnlyModelViewSet
      
      
      class DicGVS(ReadOnlyModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
      

      8.5、ModelViewSet

      ViewSet繼承了CreateModelMixin、RetrieveModelMixin、UpdateModelMixin、DestroyModelMixin、ListModelMixin和GenericViewSet,除了有GenericAPIView的所有功能外,還支持制定路由分發(fā)和簡(jiǎn)化了“增刪改查查”操作代碼。

      8.5.1、ModelViewSet源碼

      class ModelViewSet(mixins.CreateModelMixin,
                         mixins.RetrieveModelMixin,
                         mixins.UpdateModelMixin,
                         mixins.DestroyModelMixin,
                         mixins.ListModelMixin,
                         GenericViewSet):
          """
          A viewset that provides default `create()`, `retrieve()`, `update()`,
          `partial_update()`, `destroy()` and `list()` actions.
          """
          pass
      

      8.5.2、繼承ModelViewSet時(shí)的 API 示例

      # urls.py
      from django.urls import re_path
      from d4.d4_views import DicGVS
      
      
      urlpatterns = [
          re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "create"})),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
      ]
      
      # dic_view.py
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
      

      九、路由組件

      9.1、path和re_path的區(qū)別

      # urls.py
      from django.urls import re_path,path
      from d4.views import DicGVS
      
      urlpatterns = [
      
          # re_path可以使用正則表達(dá)式
          re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "create"})),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
          re_path(r'^dic/lastest$', DicGVS.as_view({"get": "lastest"})),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})/read$', DicGVS.as_view({"put": "read"})),
          
          # path無(wú)法使用正則表達(dá)式
          # path('dic/', DicGVS.as_view({"get": "list", "post": "create"})),
      ]
      

      9.2、ViewSet下的路由舉例

      ViewSet及其子類(lèi)都可以使用路由器routers.DefaultRouter和routers.SimpleRouter進(jìn)行路由注冊(cè)。

      # urls.py
      
      from d4.d4_views import DicGVS
      from rest_framework import routers
      
      
      router = routers.DefaultRouter()
      router.register('dic', DicGVS, basename='dic')
      # 相當(dāng)于自動(dòng)完成一下路由注冊(cè)
      #re_path(r'^dic/$', DicGVS.as_view({"get": "list", "post": "create"})),
      #re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})$', DicGVS.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
      
      urlpatterns = [
      ]
      
      urlpatterns += router.urls
      
      
      # dic_view.py
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      

      9.3、ViewSet視圖集中的額外行為路由

      9.3.1、方式一

      例如在dic_view.py中額外添加獲取最后一條數(shù)據(jù)的lastest操作和只更新valueread操作。

      • 為額外行為單獨(dú)設(shè)置路由
        • re_path(r'^dic/lastest$', DicGVS.as_view({"get": "lastest"})),
        • re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})/read$', DicGVS.as_view({"put": "read"})),
      • 注意
        • lastestget操作,且不指定 pk 值,做一只要在資源路徑末端直接加操作名稱(chēng)。
        • readput更新操作,是特定某一條數(shù)據(jù)的更新操作,需要pk 值,因此在特定pk值記錄的末端添加操作名稱(chēng)。
      # urls.py
      from django.urls import re_path
      from d4.views import DicGVS
      from rest_framework import routers
      
      router = routers.DefaultRouter()
      router.register('dic', DicGVS, basename='dic')
      
      urlpatterns = [
          re_path(r'^dic/lastest$', DicGVS.as_view({"get": "lastest"})),
          re_path(r'^dic/(?P<pk>[0-9a-zA-Z]{1,32})/read$', DicGVS.as_view({"put": "read"})),
      ]
      
      urlpatterns += router.urls
      
      
      # dic_view.py
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      from rest_framework.response import Response
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def lastest(self, request):
              dic = D4Dic.objects.latest();
              serializer = self.get_serializer(dic, many=False)
              return Response(serializer.data)
      
          def read(self, request):
              dic = self.get_object()
              dic = request.data.get('value')
              dic.save()
              serializer = self.get_serializer(dic, many=False)
              return Response(serializer.data)
      
      

      9.3.2、方式二

      • 使用@action裝飾器。
        • 導(dǎo)入from rest_framework.decorators import action
        • 在方法上添加裝飾器@action(methods=['get'], detail=False)
        • detail=False可以簡(jiǎn)單粗暴的理解為路徑中是否需要 pk 值。
      # urls.py
      from django.urls import re_path
      from d4.views import DicGVS
      from rest_framework import routers
      
      router = routers.DefaultRouter()
      router.register('dic', DicGVS, basename='dic')
      
      urlpatterns = [
      ]
      
      urlpatterns += router.urls
      
      
      # dic_view.py
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      from rest_framework.response import Response
      from rest_framework.decorators import action
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          @action(methods=['get'], detail=False)
          def latest(self, request):
              dic = D4Dic.objects.latest();
              serializer = self.get_serializer(dic, many=False)
              return Response(serializer.data)
      
          @action(methods=['put'], detail=True)
          def read(self, request):
              dic = self.get_object()
              dic = request.data.get('value')
              dic.save()
              serializer = self.get_serializer(dic, many=False)
              return Response(serializer.data)
      
      

      9.4、 DefaultRouter與SimpleRouter的區(qū)別

      二者僅有一點(diǎn)區(qū)別,就是DefaultRouter會(huì)自動(dòng)為資源生成一條跟路由,可以顯示類(lèi)似以下的默認(rèn)頁(yè)面,SimpleRouter則沒(méi)有。

      # urls.py
      from django.urls import re_path
      from d4.views import DicGVS
      from rest_framework import routers
      
      router = routers.DefaultRouter()
      router.register('dic', DicGVS, basename='dic')
      
      urlpatterns = [
      ]
      
      urlpatterns += router.urls
      

      OR

      # urls.py
      from django.urls import re_path
      from d4.views import DicGVS
      from rest_framework import routers
      
      router = routers.SimpleRouter()
      router.register('dic', DicGVS, basename='dic')
      
      urlpatterns = [
      ]
      
      urlpatterns += router.urls
      

      十、過(guò)濾、查詢(xún)、排序與分頁(yè)

      10.1、過(guò)濾器

      10.1.1、安裝過(guò)濾器擴(kuò)展組件

      # 安裝  django-filter
      > pip install django-filter
      
      Looking in indexes: https://dev.bolangit.cn/nexus/repository/pypi-public/simple/
      Collecting django-filter
        Downloading https://dev.bolangit.cn/nexus/repository/pypi-public/packages/97/73/4bc2445a673e768d5f5a898ad1c484d2c3b166b3a7077cf989efa21a80e8/django_filter-23.3-py3-none-any.whl (94 kB)
           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 94.3/94.3 kB 1.6 MB/s eta 0:00:00
      Requirement already satisfied: Django>=3.2 in /Users/ewordeword.name/projects/D4/D4venv/lib/python3.9/site-packages (from django-filter) (4.1.3)
      Requirement already satisfied: asgiref<4,>=3.5.2 in /Users/ewordeword.name/projects/D4/D4venv/lib/python3.9/site-packages (from Django>=3.2->django-filter) (3.5.2)
      Requirement already satisfied: sqlparse>=0.2.2 in /Users/ewordeword.name/projects/D4/D4venv/lib/python3.9/site-packages (from Django>=3.2->django-filter) (0.4.3)
      Installing collected packages: django-filter
      Successfully installed django-filter-23.3
      
      [notice] A new release of pip is available: 23.2.1 -> 23.3.1
      [notice] To update, run: pip install --upgrade pip
      
      # 更新依賴(lài)記錄文件
      > pip freeze > ./dependence.txt
      

      10.1.2、注冊(cè)組件

      # settings.py
       
      INSTALLED_APPS = [
          ...
          'django_filters', # 過(guò)濾器
          ...
      ]
       
      REST_FRAMEWORK = {
          ...
          # 全局 過(guò)濾器配置
           'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
          ...
      }
      

      10.1.3、配置過(guò)濾規(guī)則

      # dic_view.py
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      # from django_filters import rest_framework as filters
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
          
          # 配置過(guò)濾器
          # 也可以在類(lèi)中注冊(cè)過(guò)濾組件,可以在視圖中代替或覆蓋全局注冊(cè)
          # filter_backends = [filters.DjangoFilterBackend]
          # 配置可以過(guò)濾的字段,如果不配置,將無(wú)法過(guò)濾
          filterset_fields = ['name', 'code']
      
      • url 示例
        • http://127.0.0.1:8000/d4/dic/?name=111&code=1
        • http://127.0.0.1:8000/d4/dic/?name=111

      10.1.4、自定義過(guò)濾器類(lèi)

      上述過(guò)濾默認(rèn)是精準(zhǔn)匹配。使用DjangoFilterBackend如需要實(shí)現(xiàn)模糊匹配可以通過(guò)自定義過(guò)濾器類(lèi)實(shí)現(xiàn)。

      10.1.4.1、方式一
      # 自定義模型類(lèi) D4Dic 對(duì)應(yīng)的過(guò)濾器類(lèi)
      # d4.filters.dic_filter.py
      
      from d4.models import D4Dic
      from django_filters import rest_framework as filters
      
      
      class DicFilter(filters.FilterSet):
          """ 字典表 """
          # s_id = filters.CharFilter(field_name='id', lookup_expr='icontains')
          # s_fid = filters.CharFilter(field_name='fid', lookup_expr='icontains')
          s_name = filters.CharFilter(field_name='name', lookup_expr='icontains')
          s_value = filters.CharFilter(field_name='value', lookup_expr='icontains')
          s_code = filters.CharFilter(field_name='code', lookup_expr='icontains')
          # s_description = filters.CharFilter(field_name='description', lookup_expr='icontains')
          # s_is_locked = filters.CharFilter(field_name='is_locked', lookup_expr='icontains')
          # s_is_delete = filters.CharFilter(field_name='is_delete', lookup_expr='icontains')
          # s_sort = filters.NumberFilter(field_name='sort', lookup_expr='icontains')
          # s_created_by = filters.CharFilter(field_name='created_by', lookup_expr='icontains')
          # s_created_date = filters.DateTimeFilter(field_name='created_date', lookup_expr='icontains')
          # s_update_by = filters.CharFilter(field_name='update_by', lookup_expr='icontains')
          # s_update_date = filters.DateTimeFromToRangeFilter(field_name='update_date', lookup_expr='icontains')
          # s_type = filters.CharFilter(field_name='type', lookup_expr='icontains')
          # s_status = filters.CharFilter(field_name='status', lookup_expr='icontains')
      
          class Meta:
              # 模型類(lèi)名
              model = D4Dic
              # 可以過(guò)濾的字段
              # fields = ['name', 'code']
              # 如果不想要精準(zhǔn)匹配的查詢(xún)條件可以使用空數(shù)組
              # fields = []
      
      
      # d4.filters.__init__.py
      from d4.filters.dic_filter import D4DicFilter
      
      

      參數(shù)說(shuō)明
      field_name: 過(guò)濾字段名,一般應(yīng)該對(duì)應(yīng)模型中字段名
      lookup_expr: 查詢(xún)時(shí)所要進(jìn)行的操作,和ORM中運(yùn)算符一致
      Meta字段說(shuō)明
      model: 引用的模型,不是字符串
      fields:指明過(guò)濾字段,可以是列表,默認(rèn)是判等;也可以字典,字典可以自定義操作
      exclude = ['password'] 排除字段,不允許使用列表中字典進(jìn)行過(guò)濾

      url請(qǐng)求格式:
      http://127.0.0.1:8000/d4/dic/?name=111&code=222&s_name=333&s_value=444&s_code=555

      效果

      10.1.4.2、方式二
      # 自定義模型類(lèi) D4Dic 對(duì)應(yīng)的過(guò)濾器類(lèi)
      # d4.filters.dic_filter.py
      from d4.models import D4Dic
      from django_filters import rest_framework as filters
      
      
      class DicFilter(filters.FilterSet):
          """ 字典表 """
          s_value = filters.CharFilter(field_name='value', lookup_expr='icontains')
      
          class Meta:
              # 模型類(lèi)名
              model = D4Dic
              # 放到字典中
              fields = {
                  'name': ['exact', 'icontains'],
                  'code': ['exact', 'gte', 'lte']
              }
      
      # d4.filters.__init__.py
      from d4.filters.dic_filter import D4DicFilter
      
      
      • 在視圖中配置與方式一相同。
      • url 請(qǐng)求格式:
        http://127.0.0.1:8000/d4/dic/?name=111&name__icontains=222&code=333&code__gte=444&code__lte=555&s_value=666

      效果

      10.1.4.3、方式三

      重寫(xiě) BaseFilterBackend 并重寫(xiě).filter_queryset(self, request, queryset, view) 方法,該方法應(yīng)返回一個(gè)經(jīng)過(guò)過(guò)濾的新查詢(xún)集。除了允許客戶(hù)端執(zhí)行搜索和過(guò)濾外,對(duì)于限制通用過(guò)濾器后端僅響應(yīng)給定請(qǐng)求或用戶(hù)的對(duì)象也很有用。

      # d4.filters.is_owner_filter_backend.py
      from rest_framework.filters import BaseFilterBackend
      
      
      class IsOwnerFilterBackend(BaseFilterBackend):
          """
          僅允許用戶(hù)查看自己對(duì)象的過(guò)濾器。
          """
      
          def filter_queryset(self, request, queryset, view):
              return queryset.filter(owner=request.user)
      
      
      # d4.filters.__init__.py
      from d4.filters.is_owner_filter_backend import IsOwnerFilterBackend
      
      
      # 在視圖中配置 
      # dic_view.py
      
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer,
      from d4.filters import IsOwnerFilterBackend
      from rest_framework.viewsets import ModelViewSet
      from rest_framework.response import Response
      from django_filters import rest_framework as filters
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
          # 使用自定義過(guò)濾器類(lèi)
          filterset_class = IsOwnerFilterBackend
      
      

      10.1.4、定制過(guò)濾界面

      通用過(guò)濾器還可以在可瀏覽的 API 中提供接口實(shí)現(xiàn)一個(gè) to_html() 方法,呈現(xiàn)的 HTML 表示形式。

          to_html(self, request, queryset, view)
      

      10.1.5、過(guò)濾示例(非通用過(guò)濾器)

      基本原理是通過(guò)重寫(xiě).get_queryset(self)覆蓋初始查詢(xún)集。

      10.1.5.1、基于當(dāng)前用戶(hù)的過(guò)濾查詢(xún)
      # dic_view.py
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import ReadOnlyModelViewSet
      
      
      class DicGVS(ReadOnlyModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          # queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def get_queryset(self):
              '''
              該視圖返回當(dāng)前驗(yàn)證用戶(hù)創(chuàng)建的字典。
              '''
              
              user = self.request.user
              # 假設(shè) D4Dic表中created_by字段保存的是創(chuàng)建者的用戶(hù)名
              return D4Dic.objects.filter(created_by = user)
      
      10.1.5.2、基于url有名分組參數(shù)的過(guò)濾查詢(xún)
      re_path(r'^dic/(?P<code>[0-9a-zA-Z]{1,32})$', DicDetail.as_view())
      
      # dic_view.py
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import ReadOnlyModelViewSet
      
      
      class DicGVS(ReadOnlyModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          # queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def get_queryset(self):
              '''
              該視圖返回URL中code有名分組參數(shù)指定的字典清單
              '''
              
              _code = self.kwargs['code']
              return D4Dic.objects.filter(code = _code)
      
      10.1.5.1、基于查詢(xún)參數(shù)的過(guò)濾查詢(xún)
      # dic_view.py
      from d4.d4_models import D4Dic
      from d4.d4_serializers import D4DicSerializer
      from rest_framework.viewsets import ReadOnlyModelViewSet
      
      
      class DicGVS(ReadOnlyModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          # queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def get_queryset(self):
              '''
              該視圖返回code參數(shù)指定的字典清單
              '''
              queryset = D4Dic.objects.all()
              _code = self.request.query_params.get('code',None)
              if _code is not None:
                      queryset = queryset.filter(code = _code)
              return queryset
      

      10.2 、搜索過(guò)濾器SearchFilter

      10.2.1、全局配置SearchFilter

      # settings.py
        
      REST_FRAMEWORK = {
          ...
          # 全局 過(guò)濾器配置
           'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.SearchFilter'],
           # 自定義查詢(xún)參數(shù)名,默認(rèn)是search
           # 'SEARCH PARAM': 'search'
          ...
      }
      

      10.2.2、視圖中配置SearchFilter

      # 在視圖中配置 
      # dic_view.py
      
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      from rest_framework.filters import SearchFilter
      
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
          # 也可以在類(lèi)中注冊(cè)SearchFilter過(guò)濾組件,可以在視圖中代替或覆蓋全局注冊(cè)
          # filter_backends = [SearchFilter]
          # 配置模糊搜索字段
          search_fields = ['name', 'code']
      
      • url格式
        • http://127.0.0.1:8000/d4/dic/?search=4
      • 注意
        • 上述例子會(huì)根據(jù)search的值,去配置的字段 name 或者 code,只要有一個(gè)模糊匹配到search的值,就滿(mǎn)足條件。
      • 雙下劃線表示法
        • 使用雙下劃線表示法在 ForeignKeyManyToManyField 上執(zhí)行搜索。
          • search_fields = ['username', 'email', 'profile__profession']
        • 可以使用雙下劃線表示法對(duì)JSONFieldHStoreField 字段內(nèi)嵌套的數(shù)據(jù)按數(shù)據(jù)結(jié)構(gòu)進(jìn)行搜索。
          • search_fields = ['data__breed', 'data__owner__other_pets__0__name']
      • 多個(gè)關(guān)鍵字搜索
        • 默認(rèn)情況下搜索將使用不區(qū)分大小寫(xiě)的部分匹配,搜索參數(shù)可以包含多個(gè)搜索詞,多個(gè)搜索詞應(yīng)將其用空格和/或 逗號(hào)分隔,使用多個(gè)搜索詞則僅當(dāng)所有提供的詞都匹配時(shí)才符合條件。
      • 搜索行為修飾符
        • 可通過(guò)在 search_fields 數(shù)組元素字符前添加各種字符來(lái)限制搜索行為。
          • ‘^’:開(kāi)始搜索。
          • ‘=’:完全匹配。
          • ‘@’:全文搜索(當(dāng)前僅支持PostgreSQL數(shù)據(jù)庫(kù))。
          • ‘$’:正則表達(dá)式搜索。
        • search_fields = ['=username','=email']

      效果

      10.3、排序

      OrderingFilter默認(rèn)允許用戶(hù)對(duì) serializer_class屬性指定的序列化器上的任何可讀字段進(jìn)行排序。

      10.3.1、全局配置排序組件

      # settings.py
        
      REST_FRAMEWORK = {
          ...
          # 全局 排序組件 配置
           'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.OrderingFilter'],
          # 設(shè)置默認(rèn)排序參數(shù)名,默認(rèn)是ordering
          # 'ORDERING_ PARAM': 'ordering'
          ...
      }
      

      10.3.2、視圖中配置排序規(guī)則

      # 在視圖中配置 
      # dic_view.py
      
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      from rest_framework.filters import OrderingFilter
      
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
          # 也可以在類(lèi)中配置排序組件,可以在視圖中代替或覆蓋全局配置
          # filter_backends = [OrderingFilter]
          # 設(shè)置全字段可排序(默認(rèn))
          # ordering_fields = '__all__'
          # 配置可排序字段
          ordering_fields = ['name', 'code']
          # 設(shè)置默認(rèn)排序字段,或排序順序。ordering 可以是字符串列表、字符串元組。
          # ordering = ['name']
      

      10.4、分頁(yè)

      10.4.1、全局配置PageNumberPagination

      # settings.py
        
      REST_FRAMEWORK = {
          ...
          # 分頁(yè)(全局):全局分頁(yè)器, 如 省市區(qū)的數(shù)據(jù)不需要分頁(yè),可自定義分頁(yè)器
          'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
          # 每頁(yè)返回?cái)?shù)量,(默認(rèn) None)
          'PAGE_SIZE': 10
          ...
      }
      

      10.4.2、視圖中配置分頁(yè)器

      # 在視圖中配置 
      # dic_view.py
      
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      from rest_framework.filters import SearchFilter
      #導(dǎo)包 
      from rest_framework.pagination import PageNumberPagination
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
          # 也可以在類(lèi)中注冊(cè)SearchFilter過(guò)濾組件,可以在視圖中代替或覆蓋全局注冊(cè)
          # filter_backends = [SearchFilter]
          # 配置模糊搜索字段
          search_fields = ['name', 'code']
          # 設(shè)置分頁(yè)器, 可以在視圖中代替或覆蓋全局設(shè)置
          # pagination_class = PageNumberPagination
      
      

      10.4.3、自定義分頁(yè)器

      • 可以在子類(lèi)中自定義的分頁(yè)器屬性:
        • page_size_query_param = 'page_size' 前端發(fā)送的每頁(yè)數(shù)目關(guān)鍵字名,默認(rèn)為None
        • max_page_size = 4 前端最多能設(shè)置的每頁(yè)數(shù)量
        • page_size = 2 每頁(yè)數(shù)目
        • page_query_param = 'page' 前端發(fā)送的頁(yè)數(shù)關(guān)鍵字名,默認(rèn)為page
      # d4.serializers.pagination.page_number_pagination.py
      from rest_framework import pagination
      from rest_framework.response import Response
      
      #自定義分頁(yè)類(lèi)PageNumberPagination2
      class PageNumberPagination2(pagination.PageNumberPagination):
          page_size = 10  # 默認(rèn)每頁(yè)顯示的記錄數(shù)
          page_size_query_param = "page_size"  # URL中用于指定每頁(yè)記錄數(shù)的參數(shù)名
          max_page_size = 100  # 最大每頁(yè)顯示的記錄數(shù)
          max_page_size = 4
          
          # 添加 當(dāng)前頁(yè)碼、每頁(yè)數(shù)量、總頁(yè)數(shù)信息。
          # 從寫(xiě)get_paginated_response(self, data)
          def get_paginated_response(self, data):
              return Response(
                  {
                      "count": self.page.paginator.count,  # 總記錄數(shù)
                      "next": self.get_next_link(),  # 下一頁(yè)鏈接
                      "previous": self.get_previous_link(),  # 上一頁(yè)鏈接
                      "current_page": self.page.number,  # 當(dāng)前頁(yè)碼
                      "per_page": self.page.paginator.per_page,  # 每頁(yè)數(shù)量
                      "total_pages": self.page.paginator.num_pages,  # 總頁(yè)數(shù)
                      "results": data,  # 當(dāng)前頁(yè)的數(shù)據(jù)
                  }
              )
      
      
      # d4.serializers.pagination.__init__.py
      from d4.serializers.pagination.page_number_pagination import PageNumberPagination2
      
      
      # 在視圖中配置 
      # dic_view.py
      
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      from rest_framework.filters import SearchFilter
      from d4.serializers.pagination import PageNumberPagination2
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
          # 也可以在類(lèi)中注冊(cè)SearchFilter過(guò)濾組件,可以在視圖中代替或覆蓋全局注冊(cè)
          # filter_backends = [SearchFilter]
          # 配置模糊搜索字段
          search_fields = ['name', 'code']
          # 設(shè)置分頁(yè)器, 可以在視圖中代替或覆蓋全局設(shè)置
          pagination_class = PageNumberPagination2
      
      
      # settings.py
      # 全局使用
      
      REST_FRAMEWORK = {
          ...
          # 分頁(yè)(全局):全局分頁(yè)器, 如 省市區(qū)的數(shù)據(jù)不需要分頁(yè),可自定義分頁(yè)器
          'DEFAULT_PAGINATION_CLASS': 'd4.serializers.pagination.PageNumberPagination2',
          # 每頁(yè)返回?cái)?shù)量,(默認(rèn) None)
          'PAGE_SIZE': 10
          ...
      }
      

      10.4.4、請(qǐng)求示例

      • url格式
        • http://127.0.0.1:8000/d4/dic/?page=1

      10.4.5、手寫(xiě)分頁(yè)

      # dic_view.py
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      from rest_framework.response import Response
      # 導(dǎo)入包
      from django.core.paginator import Paginator
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          # queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          def list(self, request):
              # 手寫(xiě)分頁(yè)器
              # 1 為 默認(rèn)值
              num = request.GET.get('num', 1)
              num = int(num)
              queryset = D4Dic.objects.all()
              # 2 為page_size
              paginator = Paginator(queryset, 2)
              dataPage = paginator.get_page(num)
              data = {}
              data['data'] = D4DicSerializer(dataPage, many=True).data
              data['next_page'] = num + 1
              data['prev_page'] = num - 1
              data['page_range'] = [i for i in paginator.page_range]
              return Response(data)
      
      

      十一、限流Throttling

      可以對(duì)接口訪問(wèn)的頻次進(jìn)行限制,以減輕對(duì)服務(wù)器的壓力。

      10.4.1、全局配置限流Throttling

      # settings.py
        
      REST_FRAMEWORK = {
          ...
          # 限流(防爬蟲(chóng))
          'DEFAULT_THROTTLE_CLASSES': [
              'rest_framework.throttling.AnonRateThrottle',
              'rest_framework.throttling.UserRateThrottle',
          ],
          # 限流策略
          'DEFAULT_THROTTLE_RATES': {
              'user': '10/hour',  # 認(rèn)證用戶(hù)每小時(shí)10次
              'anon': '3/day',  # 未認(rèn)證用戶(hù)每天能訪問(wèn)3次
          },
          'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
          'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
          'DEFAULT_VERSIONING_CLASS': None,
          ...
      }
      
      • DEFAULT_THROTTLE_RATES 可以使用 secondminutehourday來(lái)指明周期。
      • 超出限定的次數(shù)會(huì)報(bào)錯(cuò)
        -"detail": "Request was throttled. Expected available in 86398 seconds."

      10.3.2、視圖中配置限流規(guī)則

      # 自定義限流類(lèi)
      # d4.throttling.custom_throttle.py
      from rest_framework import throttling
      
      
      class CustomThrottle(throttling.BaseThrottle):
      
          def __init__(self, **kwargs):  
              self.rate = kwargs.pop('rate', None)  
              self.burst = kwargs.pop('burst', None)  
              self.scope = kwargs.pop('scope', None)  
              self.methods = kwargs.pop('methods', None)  
              self.ip_based = kwargs.pop('ip_based', None)  
              self.proxy_based = kwargs.pop('proxy_based', None)  
              self.dynamic = kwargs.pop('dynamic', False)  
              self.log = kwargs.pop('log', False)  
              self.headers = kwargs.pop('headers', None)  
              self.cache = kwargs.pop('cache', None)  
              self.ident = kwargs.pop('ident', None)  
              self.backend = kwargs.pop('backend', None)  
              self.limit_by = kwargs.pop('limit_by', None)  
              super().__init__(**kwargs)  
      
          def allow_request(self, request, view):  
              # 在這里編寫(xiě)你的限流邏輯  
              # 返回 True 允許請(qǐng)求通過(guò),返回 False 拒絕請(qǐng)求  
              # 可以使用 request 和 view 對(duì)象來(lái)獲取請(qǐng)求的相關(guān)信息  
              # 例如,可以使用 request.user.id 來(lái)標(biāo)識(shí)每個(gè)用戶(hù)的請(qǐng)求頻率  
              # 示例:如果請(qǐng)求的 IP 地址是已知的攻擊源,則拒絕請(qǐng)求  
              if request.META['REMOTE_ADDR'] in self.blacklist:  
                  return False
              return True
      
          # def get_ident(self, request):  
             # # 返回用于標(biāo)識(shí)請(qǐng)求的唯一標(biāo)識(shí)符,例如 IP 地址、用戶(hù)認(rèn)證信息等  
             # return request.user.id
      
      # d4.throttling.__init__.py
      from d4.throttling.custom_throttle import CustomThrottle
      
      

      在上面的示例中,我們創(chuàng)建了一個(gè)名為 CustomThrottle 的自定義限流類(lèi)。它繼承了 throttling.BaseThrottle 類(lèi),并重寫(xiě)了 allow_request 方法來(lái)定義限流邏輯。在這個(gè)示例中,我們簡(jiǎn)單地檢查請(qǐng)求的 IP 地址是否在黑名單中,如果是,則拒絕請(qǐng)求。你可以根據(jù)自己的需求來(lái)修改和擴(kuò)展這個(gè)方法。

      # 在視圖中配置 
      # dic_view.py
      
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from d4.throttling import CustomThrottle
      from rest_framework.viewsets import ModelViewSet
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
      
          # 配置自定義限流類(lèi)
          throttle_classes = [CustomThrottle]
      
      

      自定義限流類(lèi)也可以在 settings.py中配置全局限流策略。

      # d4.throttling.__init__.py
      from d4.throttling.custom_throttle import CustomThrottle
      
      
      # settings.py
        
      REST_FRAMEWORK = {
          ...
          # 限流(防爬蟲(chóng))
          'DEFAULT_THROTTLE_CLASSES': [
              'd4.throttling.CustomThrottle',
          ],
          # 限流策略
          'DEFAULT_THROTTLE_RATES': {
              'custom_throttle': '10/hour',  # 每小時(shí)10次
          },
          ...
      }
      

      十二、認(rèn)證

      重要參考:Django REST Framework 之認(rèn)證、權(quán)限(超詳細(xì))

      系統(tǒng)需要登入后才能訪問(wèn),登入過(guò)程既認(rèn)證過(guò)程。

      DRF 認(rèn)證過(guò)程拆解:

      1. 身份驗(yàn)證是將傳入的請(qǐng)求對(duì)象(request)與一組標(biāo)識(shí)憑據(jù)(例如請(qǐng)求來(lái)自的用戶(hù)或其簽名的令牌token)相關(guān)聯(lián)的機(jī)制。
      2. REST framework 提供了一些開(kāi)箱即用的身份驗(yàn)證方案,并且還允許你實(shí)現(xiàn)自定義方案。DRF中每個(gè)認(rèn)證方案都被封裝成一個(gè)類(lèi)。你可以在視圖中使用一個(gè)或多個(gè)認(rèn)證方案類(lèi)。REST framework 將嘗試使用列表中的每個(gè)類(lèi)進(jìn)行身份驗(yàn)證,并使用成功完成驗(yàn)證的第一個(gè)類(lèi)的返回的元組設(shè)置 request.userrequest.auth
      3. 用戶(hù)通過(guò)認(rèn)證后request.user返回DjangoUser實(shí)例,否則返回AnonymousUser的實(shí)例。request.auth通常為None。如果使用token認(rèn)證,request.auth可以包含認(rèn)證過(guò)的token

      DRF 提供的認(rèn)證方案

      Session認(rèn)證SessionAuthentication類(lèi):此認(rèn)證方案使用Django的默認(rèn)session后端進(jìn)行身份驗(yàn)證。當(dāng)客戶(hù)端發(fā)送登錄請(qǐng)求通過(guò)驗(yàn)證后,Django通過(guò)session將用戶(hù)信息存儲(chǔ)在服務(wù)器中保持用戶(hù)的請(qǐng)求狀態(tài)。Session身份驗(yàn)證適用于與你的網(wǎng)站在相同的Session環(huán)境中運(yùn)行的AJAX客戶(hù)端。 (ps:這也是Session認(rèn)證的最大弊端)。

      基本認(rèn)證BasicAuthentication類(lèi):此認(rèn)證方案使用HTTP 基本認(rèn)證,針對(duì)用戶(hù)的用戶(hù)名和密碼進(jìn)行認(rèn)證。使用這種方式后瀏覽器會(huì)跳出登錄框讓用戶(hù)輸入用戶(hù)名和密碼認(rèn)證。(ps:基本認(rèn)證通常只適用于測(cè)試)。

      Token認(rèn)證TokenAuthentication類(lèi):該認(rèn)證方案是DRF提供的使用簡(jiǎn)單的基于TokenHTTP認(rèn)證方案。當(dāng)客戶(hù)端發(fā)送登錄請(qǐng)求時(shí),服務(wù)器便會(huì)生成一個(gè)Token并將此Token返回給客戶(hù)端,作為客戶(hù)端進(jìn)行請(qǐng)求的一個(gè)標(biāo)識(shí)以后客戶(hù)端只需帶上這個(gè)Token前來(lái)請(qǐng)求數(shù)據(jù)即可,(ps:學(xué)習(xí)筆記中會(huì)詳細(xì)介紹基于TokenJWT認(rèn)證方案)。

      遠(yuǎn)程認(rèn)證RemoteUserAuthentication類(lèi):此認(rèn)證方案為用戶(hù)名不存在的用戶(hù)自動(dòng)創(chuàng)建用戶(hù)實(shí)例。

      12.1、全局認(rèn)證

      settings.py中設(shè)置默認(rèn)的全局認(rèn)證方案

      • settings.py配置文件中 REST_FRAMEWORK 添加配置
      # settings.py
      
      REST_FRAMEWORK = {
           ……
          # 1.認(rèn)證器(全局)
          "DEFAULT_AUTHENTICATION_CLASSES": [
              # 在DRF中配置JWT認(rèn)證
              "rest_framework_simplejwt.authentication.JWTAuthentication",
              # 使用session時(shí)的認(rèn)證器
              # "rest_framework.authentication.SessionAuthentication",
              # 提交表單時(shí)的認(rèn)證器  
              # "rest_framework.authentication.BasicAuthentication"
          ],
          ……
      }
      
      • 如果在全局認(rèn)證下,有些接口不想加上認(rèn)證,
        可以在這個(gè)類(lèi)的屬性 authentication_classes = [] 即可。
      • 除了上述自己實(shí)現(xiàn)的認(rèn)證類(lèi),REST Framework為我們提供了四種認(rèn)證類(lèi):
      1. BasicAuthentication                     # 基于用戶(hù)名密碼
      2. SessionAuthentication                # 基于session
      3. TokenAuthentication                   # 基于token
      4. RemoteUserAuthentication  # 遠(yuǎn)程用戶(hù)認(rèn)證
      
      # 直接在視圖類(lèi)中使用即可
      # 導(dǎo)包
      from rest_framework.authentication import BaseAuthentication,SessionAuthentication
      

      12.2、局部認(rèn)證

      局部認(rèn)證會(huì)在局部?jī)?nèi)覆蓋全局認(rèn)證的配置。

      12.2.1、基于類(lèi)視圖(CBV)的局部認(rèn)證

      
      from rest_framework.authentication import SessionAuthentication, BasicAuthentication
      from rest_framework.permissions import IsAuthenticated
      from rest_framework.response import Response
      from rest_framework.views import APIView
      
      class ExampleView(APIView):
          authentication_classes = (SessionAuthentication, BasicAuthentication)
          permission_classes = (IsAuthenticated,)
      
      

      12.2.2、基于函數(shù)視圖(FBV)的局部認(rèn)證

      @api_view(['GET'])
      @authentication_classes((SessionAuthentication, BasicAuthentication))
      @permission_classes((IsAuthenticated,))
      def example_view(request, format=None):
          content = {
              'user': unicode(request.user),  # `django.contrib.auth.User` 實(shí)例。
              'auth': unicode(request.auth),  # None
          }
          return Response(content)
      
      
      

      12.3、基于Token的認(rèn)證

      12.3.1、DRF自帶的Token認(rèn)證示例

      # setting.py 
      
      INSTALLED_APPS = (
          ...
          'rest_framework.authtoken'
          )
      
      

      12.3.2、基于JWT的Token認(rèn)證示例

      • JWT(JSON Web Token)是一種使用Token進(jìn)行身份認(rèn)證的開(kāi)放標(biāo)準(zhǔn)
      • 與DRF內(nèi)置的TokenAuthentication方案不同,JWT身份驗(yàn)證不需要使用數(shù)據(jù)庫(kù)來(lái)驗(yàn)證令牌,JWT生產(chǎn)的Token自包含了業(yè)務(wù)信息。這些信息包括Token的有效期、附帶的業(yè)務(wù)信息例如UserId,加密標(biāo)準(zhǔn)等。
      • JWT可以輕松設(shè)置token失效期或刷新token, 是API開(kāi)發(fā)中當(dāng)前最流行的跨域認(rèn)證解決方案。
      • 學(xué)習(xí)筆記中將詳細(xì)介紹JWT認(rèn)證的工作原理以及如何通過(guò)djangorestframework-simplejwt 這個(gè)第三方包輕松實(shí)現(xiàn)JWT認(rèn)證。
      12.3.2.1、JWT原理

      JWT用于為應(yīng)用程序創(chuàng)建訪問(wèn)token,通常適用于API身份驗(yàn)證和服務(wù)器到服務(wù)器的授權(quán)。
      JWT 定義了一種緊湊且自包含的方式。該方式用于各方之間安全地將信息以JSON對(duì)象傳輸。

      • 緊湊(簡(jiǎn)潔):Token數(shù)據(jù)量比較少,可以通過(guò)url參數(shù),http請(qǐng)求提交的數(shù)據(jù)以及http header多種方式來(lái)傳遞。
      • 自包含:Token字符串可以包含很多信息,比如用戶(hù)id用戶(hù)名訂單號(hào)id等,
        • PS:雖然其他人拿到該信息,就可以拿到關(guān)鍵業(yè)務(wù)信息。但由于此信息是經(jīng)過(guò)數(shù)字簽名的,因此可以被驗(yàn)證和信任。

      JWT的組成

      JSON Web Token由三部分組成,這些部分由點(diǎn)(.)分隔,分別是header(頭部),payload(有效負(fù)載)和signature(簽名)。

      • header(頭部): 識(shí)別以何種算法來(lái)生成簽名;
      • pyload(有效負(fù)載): 用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù);
      • signature(簽名): 安全驗(yàn)證token有效性,防止數(shù)據(jù)被篡改。

      JWT用戶(hù)認(rèn)證過(guò)程

      首先客戶(hù)端提交用戶(hù)登錄信息驗(yàn)證身份通過(guò)后,服務(wù)器生成一個(gè)用于證明用戶(hù)身份的令牌(token),也就是一個(gè)加密后的長(zhǎng)字符串,并將其發(fā)送給客戶(hù)端。在后續(xù)請(qǐng)求中,客戶(hù)端以各種方式(比如通過(guò)url參數(shù)或者請(qǐng)求頭)將這個(gè)令牌發(fā)送回服務(wù)器,服務(wù)器就知道請(qǐng)求來(lái)自哪個(gè)特定身份的用戶(hù)了。具體步驟如下。

      1. 首先,前端通過(guò)Web表單將自己的用戶(hù)名和密碼發(fā)送到后端的接口。這一過(guò)程一般是一個(gè)HTTP POST請(qǐng)求。建議的方式是通過(guò)SSL加密的傳輸(https協(xié)議),從而避免敏感信息被嗅探。
      2. 后端核對(duì)用戶(hù)名和密碼成功后,將用戶(hù)的id等其他信息作為JWT Payload(負(fù)載),將其與頭部分別進(jìn)行Base64編碼拼接后簽名,形成一個(gè)JWT。形成的JWT就是一個(gè)形同aaa.bbb.ccc的字符串。
      3. 后端將JWT字符串作為登錄成功的返回結(jié)果返回給前端。前端可以將返回的結(jié)果保存在localStoragesessionStorage上,退出登錄時(shí)前端刪除保存的JWT即可。
      4. 前端在每次請(qǐng)求時(shí)將JWT放入HTTP Header中的Authorization位。(解決XSSXSRF問(wèn)題)
      5. 后端檢查是否存在,如存在驗(yàn)證JWT的有效性。例如,檢查簽名是否正確;檢查Token是否過(guò)期;檢查Token的接收方是否是自己(可選)。

      PS:通過(guò)http傳輸?shù)臄?shù)據(jù)實(shí)際上是加密后的JWT,它是由兩個(gè)點(diǎn)分割的base64-URL長(zhǎng)字符串組成,解密后我們可以得到header, payloadsignature三部分。

      PS:DRF接口會(huì)自動(dòng)驗(yàn)證token的有效性。Simple JWT中的access token默認(rèn)只有5分鐘有效。access token過(guò)期后訪問(wèn)將得到token已失效或過(guò)期的提示。

      PS:Simple JWT中的refresh token默認(rèn)有效期為24小時(shí)。

      12.3.2.2、JWT安裝

      本學(xué)習(xí)筆記使用djangorestframework-simplejwt舉例。

      >  pip install djangorestframework-simplejwt
      
      12.3.2.3、JWT 全局配置

      settings.py中設(shè)置默認(rèn)的全局認(rèn)證方案

      • settings.py配置文件中 REST_FRAMEWORK 添加配置
      # settings.py
      
      REST_FRAMEWORK = {
           ……
          # 1.認(rèn)證器(全局)
          "DEFAULT_AUTHENTICATION_CLASSES": [
              # 在DRF中配置JWT認(rèn)證
              "rest_framework_simplejwt.authentication.JWTAuthentication"
          ],
          ……
      }
      

      urls.py中設(shè)置獲取和刷新tokenurls地址。

      # urls.py
      from django.contrib import admin
      from django.urls import path, include
      from rest_framework import routers
      from d4svr.quickstart import views
      from rest_framework_simplejwt.views import (
          TokenObtainPairView,
          TokenRefreshView,
      )
      router = routers.DefaultRouter()
      router.register(r"users", views.UserViewSet)
      router.register(r"groups", views.GroupViewSet)
      
      urlpatterns = [
          # 使用自動(dòng)URL路由連接的API。
          # 括支持瀏覽器瀏覽API的登錄URL。
          path("", include(router.urls)),
          # 認(rèn)證
          path(r"api-auth/", include("rest_framework.urls", namespace="rest_framework")),
          path("admin/", admin.site.urls),
          # 獲取Token的地址
          path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
          # 刷新Token的地址
          path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
          # App
          path(r"d4/", include("d4.urls"), name="d4"),
      ]
      
      
      12.3.2.4、解決跨域問(wèn)題

      安裝支持包

      > pip install django-cors-headers
      

      注冊(cè)App

      # setting.py
      INSTALLED_APPS = [
          ……
          'corsheaders',  # 注冊(cè)跨域app
          ……
      ]
      
      MIDDLEWARE = [
      ……
          'corsheaders.middleware.CorsMiddleware',  # 跨域中間件
      ……
      ]
      
      12.3.2.5、測(cè)試認(rèn)證是否生效

      Postman 登入請(qǐng)求獲取Token。(注意跨域問(wèn)題)。

      使用JWT Token請(qǐng)求數(shù)據(jù)

      如果Token 不正確或?yàn)榭铡?/p>

      12.4、自定義認(rèn)證

      12.4.1、自定義令牌(Token)

      如果希望在payload部分提供更多信息,比如用戶(hù)的username,可通過(guò)自定義令牌(token)實(shí)現(xiàn)。

      1. 首先,編寫(xiě)自定義序列化器MyTokenObtainPairSerializer,該序列化器繼承了TokenObtainPairSerializer類(lèi)。
      from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
      
      class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
          @classmethod
          def get_token(cls, user):
              token = super(MyTokenObtainPairSerializer, cls).get_token(user)
      
              # Add custom claims
              token['username'] = user.username
              return token
      
      
      1. 自定義視圖MyObtainTokenPairView
      from rest_framework_simplejwt.views import TokenObtainPairView
      from rest_framework.permissions import AllowAny
      from .serializers import MyTokenObtainPairSerializer
      
      class MyObtainTokenPairView(TokenObtainPairView):
          permission_classes = (AllowAny,)
          serializer_class = MyTokenObtainPairSerializer
      
      
      1. 修改路由urls.py
      # urls.py
      from django.contrib import admin
      from django.urls import path, include
      from rest_framework import routers
      from d4svr.quickstart import views
      from rest_framework_simplejwt.views import (
          TokenObtainPairView,
          TokenRefreshView,
      )
      router = routers.DefaultRouter()
      router.register(r"users", views.UserViewSet)
      router.register(r"groups", views.GroupViewSet)
      
      urlpatterns = [
          # 使用自動(dòng)URL路由連接的API。
          # 括支持瀏覽器瀏覽API的登錄URL。
          path("", include(router.urls)),
          # 認(rèn)證
          path(r"api-auth/", include("rest_framework.urls", namespace="rest_framework")),
          path("admin/", admin.site.urls),
          
          # 獲取Token的地址(自定義token視圖) ****
          path("token/", MyObtainTokenPairView.as_view(), name="token_obtain_pair"),
          
          # 刷新Token的地址
          path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
          # App
          path(r"d4/", include("d4.urls"), name="d4"),
      ]
      

      對(duì)重新獲取的access token進(jìn)行解碼,將看到payload部分多了username的內(nèi)容。
      在實(shí)際API開(kāi)發(fā)過(guò)程中,通常會(huì)通過(guò)Json Web Token傳遞更多數(shù)據(jù)。

      12.4.2、自定義認(rèn)證方式一:自定義認(rèn)證器(重寫(xiě)authenticate

      自定義認(rèn)證類(lèi)繼承BaseAuthentication類(lèi)并且重寫(xiě).authenticate(self, request)方法。
      如果認(rèn)證成功,該方法應(yīng)返回(user, auth)的二元元組,否則返回None

      from django.contrib.auth.models import User
      from rest_framework import authentication
      from rest_framework import exceptions
      
      class ExampleAuthentication(authentication.BaseAuthentication):
          def authenticate(self, request):
              # 獲取請(qǐng)求頭中的傳入的認(rèn)證信息
              username = request.META.get('USER_EMAIL')
              if not username:
                  return None
      
              try:
                  user = User.objects.get(username=username)
              except User.DoesNotExist:
                  raise exceptions.AuthenticationFailed('No such user')
      
              return (user, None)
      
      

      使用自定義認(rèn)證器ExampleAuthentication

      注冊(cè) 自定義認(rèn)證器ExampleAuthentication**

      #setting.py
          # 1.認(rèn)證器(全局)
          "DEFAULT_AUTHENTICATION_CLASSES": [
              # 在DRF中配置JWT認(rèn)證
              "rest_framework_simplejwt.authentication.JWTAuthentication",
              # 使用session時(shí)的認(rèn)證器
              # "rest_framework.authentication.SessionAuthentication",
              # 提交表單時(shí)的認(rèn)證器
              # "rest_framework.authentication.BasicAuthentication",
              # 自定義認(rèn)證器
              # "xxx.xxxx.ExampleAuthentication"
          ],
      

      12.4.3、自定義認(rèn)證方式二:自定義認(rèn)證后臺(tái)(Backend)

      如果需要支持username以外的登入方式,可以通過(guò)自定義認(rèn)證后臺(tái)(Backend)實(shí)現(xiàn)。
      例如希望為系統(tǒng)額外提供通過(guò)email進(jìn)行登入的功能,即同時(shí)支持username和email登入。

      自定義認(rèn)證類(lèi)MyCustomBackend

      
      from django.contrib.auth.backends import ModelBackend
      #django的Q對(duì)象將SQL表達(dá)式封裝在Python對(duì)象中,該對(duì)象可用于與數(shù)據(jù)庫(kù)相關(guān)的操作。使用Q對(duì)象,我們可以使用更少和更簡(jiǎn)單的代碼進(jìn)行復(fù)雜查詢(xún)。
      from django.db.models import Q
      from django.contrib.auth import get_user_model
      
      User = get_user_model()
      
      class MyCustomBackend(ModelBackend):
          def authenticate(self, request, username=None, password=None, **kwargs):
              try:
                  user = User.objects.get(Q(username=username) | Q(email=username) )
                  if user.check_password(password):
                      return user
              except Exception as e:
                  return None
      
      

      使用自定義認(rèn)證類(lèi)MyCustomBackend

      注冊(cè) 自定義認(rèn)證類(lèi)MyCustomBackend**

      # setting.py
      
      AUTHENTICATION_BACKENDS = [
          "d4.views.MyCustomBackend",
      ]
      
      

      測(cè)試驗(yàn)證

      14.4.4、兩種方式的區(qū)別

      在Django中,自定義認(rèn)證通常涉及到修改或擴(kuò)展默認(rèn)的認(rèn)證過(guò)程。當(dāng)你想要自定義認(rèn)證時(shí),可以選擇繼承ModelBackendBaseAuthentication。但是,這兩個(gè)類(lèi)服務(wù)于不同的目的,并位于Django的不同認(rèn)證系統(tǒng)中,因此它們的用途和實(shí)現(xiàn)方式存在顯著的區(qū)別。

      1. 繼承ModelBackend

      ModelBackend是Django默認(rèn)的身份驗(yàn)證后端,它使用Django的用戶(hù)模型(通常是auth.User)來(lái)驗(yàn)證用戶(hù)的憑證。這個(gè)類(lèi)實(shí)現(xiàn)了authenticate方法,該方法接收用戶(hù)名和密碼作為參數(shù),并返回一個(gè)用戶(hù)對(duì)象(如果憑證有效)或None

      當(dāng)你需要擴(kuò)展或修改默認(rèn)的基于模型的認(rèn)證邏輯時(shí),可以繼承ModelBackend并重寫(xiě)authenticate方法。例如,你可能想要支持額外的認(rèn)證字段,或者添加自定義的驗(yàn)證邏輯。

      優(yōu)點(diǎn)

      • 易于擴(kuò)展默認(rèn)的基于模型的認(rèn)證邏輯。
      • 可以保留Django用戶(hù)模型及其關(guān)聯(lián)的功能(如權(quán)限和組)。

      缺點(diǎn)

      • 如果你的認(rèn)證需求與Django用戶(hù)模型差異很大,那么使用ModelBackend可能不夠靈活。
      1. 繼承BaseAuthentication

      BaseAuthentication是Django REST framework(DRF)中的一個(gè)類(lèi),用于實(shí)現(xiàn)API的身份驗(yàn)證。DRF提供了一套強(qiáng)大的工具來(lái)構(gòu)建Web API,包括認(rèn)證和權(quán)限管理。BaseAuthentication是一個(gè)基類(lèi),用于創(chuàng)建自定義的身份驗(yàn)證類(lèi)。

      當(dāng)你需要為DRF API實(shí)現(xiàn)自定義的身份驗(yàn)證邏輯時(shí),可以繼承BaseAuthentication并重寫(xiě)authenticate方法。例如,你可能想要支持令牌認(rèn)證、OAuth2或其他非標(biāo)準(zhǔn)的認(rèn)證機(jī)制。

      優(yōu)點(diǎn)

      • 專(zhuān)為Web API設(shè)計(jì),提供了與DRF的無(wú)縫集成。
      • 支持多種認(rèn)證機(jī)制,靈活性高。

      缺點(diǎn)

      • 僅適用于DRF API,不適用于傳統(tǒng)的Django視圖或表單。
      • 需要安裝和使用DRF及其相關(guān)依賴(lài)。

      總結(jié)

      • 如果你正在使用Django的默認(rèn)用戶(hù)模型,并且只需要擴(kuò)展或修改基于模型的認(rèn)證邏輯,那么繼承ModelBackend是更合適的選擇。
      • 如果你正在使用DRF構(gòu)建Web API,并且需要實(shí)現(xiàn)自定義的身份驗(yàn)證邏輯(如令牌認(rèn)證、OAuth2等),那么繼承BaseAuthentication是更好的選擇。

      十三、權(quán)限

      13.1、全局配置權(quán)限

      # setting.py 
      
      INSTALLED_APPS = [
          'rest_framework',
      ]
      
      
      # setting.py 
      
      REST_FRAMEWORK = {
          # 2.權(quán)限配置(全局): 順序靠上的嚴(yán)格,如果未指定,則此設(shè)置默認(rèn)為允許無(wú)限制訪問(wèn):
          "DEFAULT_PERMISSION_CLASSES": [
              # 'rest_framework.permissions.IsAdminUser',  # 管理員可以訪問(wèn)
              "rest_framework.permissions.IsAuthenticated",  # 認(rèn)證用戶(hù)可以訪問(wèn)
              # 'rest_framework.permissions.IsAuthenticatedOrReadOnly',  # 認(rèn)證用戶(hù)可以訪問(wèn), 否則只能讀取
              # 'rest_framework.permissions.AllowAny',  # 所有用戶(hù)都可以訪問(wèn)
          ],
      }
      
      

      13.2、視圖中配置權(quán)限

      13.2.1、函數(shù)視圖配置權(quán)限

      from rest_framework.decorators import api_view, permission_classes
      from rest_framework.permissions import IsAuthenticated
      from rest_framework.response import Response
      
      @api_view(['GET'])
      @permission_classes((IsAuthenticated, ))
      def example_view(request, format=None):
          content = {
              'status': 'request was permitted'
          }
          return Response(content)
      
      

      13.2.3、類(lèi)試圖配置權(quán)限

      # 在視圖中配置 
      # dic_view.py
      
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      from rest_framework import  permissions
      from rest_framework.permissions import IsAuthenticatedOrReadOnly
      
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
          # 在類(lèi)中配置權(quán)限,可以在視圖中代替或覆蓋全局配置
          permission_classes = (IsAuthenticatedOrReadOnly,)
      

      13.3、配置登入頁(yè)面路由

      # urls.py
      from django.contrib import admin
      from django.urls import path, include
      from rest_framework import routers
      from d4svr.quickstart import views
      router = routers.DefaultRouter()
      router.register(r"users", views.UserViewSet)
      router.register(r"groups", views.GroupViewSet)
      
      urlpatterns = [
          # 使用自動(dòng)URL路由連接的API。
          # 括支持瀏覽器瀏覽API的登錄URL。
          path("", include(router.urls)),
          # 登入頁(yè)面路由
          path(r"api-auth/", include("rest_framework.urls", namespace="rest_framework")),
          # 管理頁(yè)面路由
          path("admin/", admin.site.urls),
          # App
          path(r"d4/", include("d4.urls"), name="d4"),
      ]
      

      訪問(wèn) http://127.0.0.1:8000/api-auth/login/ 登入頁(yè)面
      DRF提供的默認(rèn)登入頁(yè)面

      13.3、自定義權(quán)限類(lèi)

      13.3.1、DRF提供常用權(quán)限類(lèi)

      • IsAuthenticated類(lèi):僅限已經(jīng)通過(guò)身份驗(yàn)證的用戶(hù)訪問(wèn);
      • AllowAny類(lèi):允許任何用戶(hù)訪問(wèn);
      • IsAdminUser類(lèi):僅限管理員訪問(wèn);
      • DjangoModelPermissions類(lèi):只有在用戶(hù)經(jīng)過(guò)身份驗(yàn)證并分配了相關(guān)模型權(quán)限時(shí),才會(huì)獲得授權(quán)訪問(wèn)相關(guān)模型。
      • DjangoModelPermissionsOrReadOnly類(lèi):與前者類(lèi)似,但可以給匿名用戶(hù)訪問(wèn)API的可讀權(quán)限。
      • DjangoObjectPermissions類(lèi):只有在用戶(hù)經(jīng)過(guò)身份驗(yàn)證并分配了相關(guān)對(duì)象權(quán)限時(shí),才會(huì)獲得授權(quán)訪問(wèn)相關(guān)對(duì)象。通常與django-gaurdian聯(lián)用實(shí)現(xiàn)對(duì)象級(jí)別的權(quán)限控制。

      13.3.2、自定義權(quán)限類(lèi)

      # d4.permissions.is_owner_or_read_only.py
      from rest_framework import permissions
      
      class IsOwnerOrReadOnly(permissions.BasePermission):
          """
          添加自定義權(quán)限,只允許對(duì)象的創(chuàng)建者才能編輯它。
          """
          def has_object_permission(self, request, view, obj):
              # 讀取權(quán)限被允許用于任何請(qǐng)求,
              # 所以我們始終允許 GET,HEAD 或 OPTIONS 請(qǐng)求。
              if request.method in permissions.SAFE_METHODS:
                  return True
              # 寫(xiě)入權(quán)限只允許給 article 的作者。
              return obj.author == request.user
      
      
      # d4.permissions.__init__.py
      from d4.permissions.is_owner_or_read_only import IsOwnerOrReadOnly
      
      

      13.3.3、在視圖中使用自定義權(quán)限類(lèi)

      # 在視圖中配置 
      # dic_view.py
      
      from d4.models import D4Dic
      from d4.serializers import D4DicSerializer
      from rest_framework.viewsets import ModelViewSet
      from rest_framework import  permissions
      from rest_framework.permissions import IsAuthenticatedOrReadOnly
      from d4.permissions import IsOwnerOrReadOnly
      
      class DicGVS(ModelViewSet):
          '''
          CBV下的系統(tǒng)字典管理API
          '''
          queryset = D4Dic.objects.all()
          serializer_class = D4DicSerializer
          # 在類(lèi)中配置權(quán)限,可以在視圖中代替或覆蓋全局配置
          permission_classes = (IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
          
      

      13.3.4、全局添加自定義權(quán)限類(lèi)

      # d4.permissions.__init__.py
      from d4.permissions.is_owner_or_read_only import IsOwnerOrReadOnly
      
      
      # setting.py
      
          "DEFAULT_PERMISSION_CLASSES": [
              # 'rest_framework.permissions.IsAdminUser',  # 管理員可以訪問(wèn)
              "rest_framework.permissions.IsAuthenticated",  # 認(rèn)證用戶(hù)可以訪問(wèn)
              # 'rest_framework.permissions.IsAuthenticatedOrReadOnly',  # 認(rèn)證用戶(hù)可以訪問(wèn), 否則只能讀取
              # 'rest_framework.permissions.AllowAny',  # 所有用戶(hù)都可以訪問(wèn)
              "d4.permissions.IsOwnerOrReadOnly",  # 自定義權(quán)限類(lèi)
          ],
      

      參見(jiàn)
      http://www.rzrgm.cn/tjw-bk/p/13952297.html

      附錄、D4Dic數(shù)據(jù)模型

      # DicModel模型
      from django.db import models
      
      
      class D4Dic(models.Model):
          """ 字典表 """
          id = models.CharField(help_text ='字典 id', primary_key=True, max_length=32, null=False)
          fid = models.CharField(help_text ='父id', max_length=32, blank=True, null=True)
          name = models.CharField(help_text ='字典名稱(chēng)', max_length=90, null=False)
          value = models.CharField(help_text ='字典值', max_length=255, blank=True, null=True)
          code = models.CharField(help_text ='簡(jiǎn)碼', max_length=32, blank=True, null=True)
          description = models.CharField(help_text ='字典備注', max_length=900, blank=True, null=True)
          is_locked = models.CharField(help_text ='鎖定編輯功能', max_length=1, null=False)
          is_delete = models.CharField(help_text ='已刪除', max_length=1, null=False)
          sort = models.IntegerField(help_text ='排序', null=False)
          created_by = models.CharField(help_text ='創(chuàng)建人id', max_length=32, null=False)
          created_date = models.DateTimeField(help_text ='創(chuàng)建日期', null=False)
          update_by = models.CharField(help_text ='更新人id', max_length=32, blank=True, null=True)
          update_date = models.DateTimeField(help_text ='更新日期', blank=True, null=True)
          type = models.CharField(help_text ='類(lèi)別', max_length=90, blank=True, null=True)
          status = models.CharField(help_text ='狀態(tài);', max_length=6, null=False)
      
          class Meta:
              managed = False
              db_table = 'd4_dic'
      

      附錄、settings設(shè)置指南

      附錄、Simple JWT的默認(rèn)設(shè)置

      # JWT配置
      SIMPLE_JWT = {
          'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # Access Token的有效期
          'REFRESH_TOKEN_LIFETIME': timedelta(days=7),  # Refresh Token的有效期
          
          # 對(duì)于大部分情況,設(shè)置以上兩項(xiàng)就可以了,以下為默認(rèn)配置項(xiàng)目,可根據(jù)需要進(jìn)行調(diào)整
          
          # 是否自動(dòng)刷新Refresh Token
          'ROTATE_REFRESH_TOKENS': False,  
          # 刷新Refresh Token時(shí)是否將舊Token加入黑名單,如果設(shè)置為False,則舊的刷新令牌仍然可以用于獲取新的訪問(wèn)令牌。需要將'rest_framework_simplejwt.token_blacklist'加入到'INSTALLED_APPS'的配置中
          'BLACKLIST_AFTER_ROTATION': False,  
          'ALGORITHM': 'HS256',  # 加密算法
          'SIGNING_KEY': settings.SECRET_KEY,  # 簽名密匙,這里使用Django的SECRET_KEY
      ?
          # 如為T(mén)rue,則在每次使用訪問(wèn)令牌進(jìn)行身份驗(yàn)證時(shí),更新用戶(hù)最后登錄時(shí)間
          "UPDATE_LAST_LOGIN": False, 
          # 用于驗(yàn)證JWT簽名的密鑰返回的內(nèi)容。可以是字符串形式的密鑰,也可以是一個(gè)字典。
          "VERIFYING_KEY": "",
          "AUDIENCE": None,# JWT中的"Audience"聲明,用于指定該JWT的預(yù)期接收者。
          "ISSUER": None, # JWT中的"Issuer"聲明,用于指定該JWT的發(fā)行者。
          "JSON_ENCODER": None, # 用于序列化JWT負(fù)載的JSON編碼器。默認(rèn)為Django的JSON編碼器。
          "JWK_URL": None, # 包含公鑰的URL,用于驗(yàn)證JWT簽名。
          "LEEWAY": 0, # 允許的時(shí)鐘偏差量,以秒為單位。用于在驗(yàn)證JWT的過(guò)期時(shí)間和生效時(shí)間時(shí)考慮時(shí)鐘偏差。
      ?
          # 用于指定JWT在HTTP請(qǐng)求頭中使用的身份驗(yàn)證方案。默認(rèn)為"Bearer"
          "AUTH_HEADER_TYPES": ("Bearer",), 
          # 包含JWT的HTTP請(qǐng)求頭的名稱(chēng)。默認(rèn)為"HTTP_AUTHORIZATION"
          "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", 
           # 用戶(hù)模型中用作用戶(hù)ID的字段。默認(rèn)為"id"。
          "USER_ID_FIELD": "id",
           # JWT負(fù)載中包含用戶(hù)ID的聲明。默認(rèn)為"user_id"。
          "USER_ID_CLAIM": "user_id",
          
          # 用于指定用戶(hù)身份驗(yàn)證規(guī)則的函數(shù)或方法。默認(rèn)使用Django的默認(rèn)身份驗(yàn)證方法進(jìn)行身份驗(yàn)證。
          "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule",
      ?
          #  用于指定可以使用的令牌類(lèi)。默認(rèn)為"rest_framework_simplejwt.tokens.AccessToken"。
          "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
          # JWT負(fù)載中包含令牌類(lèi)型的聲明。默認(rèn)為"token_type"。
          "TOKEN_TYPE_CLAIM": "token_type",
          # 用于指定可以使用的用戶(hù)模型類(lèi)。默認(rèn)為"rest_framework_simplejwt.models.TokenUser"。
          "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
      ?
          # JWT負(fù)載中包含JWT ID的聲明。默認(rèn)為"jti"。
          "JTI_CLAIM": "jti",
      ?
          # 在使用滑動(dòng)令牌時(shí),JWT負(fù)載中包含刷新令牌過(guò)期時(shí)間的聲明。默認(rèn)為"refresh_exp"。
          "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
          # 滑動(dòng)令牌的生命周期。默認(rèn)為5分鐘。
          "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5),
          # 滑動(dòng)令牌可以用于刷新的時(shí)間段。默認(rèn)為1天。
          "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
          # 用于生成訪問(wèn)令牌和刷新令牌的序列化器。
          "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
          # 用于刷新訪問(wèn)令牌的序列化器。默認(rèn)
          "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
          # 用于驗(yàn)證令牌的序列化器。
          "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
          # 用于列出或撤銷(xiāo)已失效JWT的序列化器。
          "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
          # 用于生成滑動(dòng)令牌的序列化器。
          "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
          # 用于刷新滑動(dòng)令牌的序列化器。
          "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
      }
      
      

      如果要覆蓋Simple JWT的默認(rèn)設(shè)置,可以修改settings.py, 如下所示。下例將refresh token的有效期改為了15天。

      from datetime import timedelta
      
      SIMPLE_JWT = {
          'REFRESH_TOKEN_LIFETIME': timedelta(days=15),
          'ROTATE_REFRESH_TOKENS': True,
      }
      
      
      posted @ 2024-04-05 23:27  影烏  閱讀(119)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 国产精品自拍实拍在线看| 亚洲国产大胸一区二区三区| 强奷漂亮雪白丰满少妇av| 精品无码三级在线观看视频| 国产成人精品无人区一区| 亚洲成在人线在线播放无码 | 成人看的污污超级黄网站免费| 波多野结衣av无码| 国产精品成人99一区无码| 日本边添边摸边做边爱| 在线观看特色大片免费视频| 亚洲综合激情五月色一区| 一区二区三区激情都市| 日本一高清二区视频久二区 | 18禁美女裸体爆乳无遮挡| 国产精品午夜福利片国产| 精品国产免费人成网站| 日本高清久久一区二区三区| 97人人添人人澡人人澡人人澡| 性男女做视频观看网站| 国产成人理论在线视频观看| 一区二区三区四区在线不卡高清| 丝袜老师办公室里做好紧好爽| 久久九九兔免费精品6| 国产亚洲综合一区二区三区| 国产不卡一区二区精品| 亚洲愉拍一区二区三区| 欧美一进一出抽搐大尺度视频| 国产黄色一区二区三区四区| 激情综合网激情五月激情| 国产免费无遮挡吃奶视频| 国产午夜亚洲精品福利| 国产亚洲综合另类色专区| 亚洲精品视频免费| 国产一区二区亚洲一区二区三区 | 日韩一区二区三区理伦片| 免费看国产曰批40分钟| 国产精品色内内在线播放| 天堂V亚洲国产V第一次| 久久日韩精品一区二区五区| 亚洲综合av永久无码精品一区二区|