【Django drf】檢視類APIView之五層封裝 ApiView的類屬性 drf配置檔案

passion2021發表於2023-02-04

ApiView的類屬性

如下是ApiView所有的類屬性,我們抽取一部分進行介紹:
可見這些類屬性,都是使用drf配置檔案的預設配置。

下列策略可以在全域性設定 或者 在每一個檢視類中設定。
允許依賴注入其他的設定檔案, ApiView原始碼的settings類屬性使測試更容易 (也就是不使用drf的api_settings)

image-20230204162954141
  • renderer_classes: 用於設定檢視類的響應格式。預設情況會有兩種響應格式,一種是響應json字串,一種是對瀏覽器的響應。
  • parser_classes:用於定義檢視類能夠解析的請求格式。預設情況下使用3個解析類進行解析,可以解析如下媒體型別:multipart/form-dataapplication/x-www-form-urlencodedapplication/json
  • authentication_classes:認證類相關配置
  • throttle_classes:頻率類相關配置
  • permission_classes:許可權類相關配置

複習:

# APIView跟之前的View區別
    -傳入到檢視方法中的是REST framework的Request物件,而不是Django的HttpRequeset物件;
    -檢視方法可以返回REST framework的Response物件-
    -任何APIException異常都會被捕獲到,並且處理成合適的響應資訊;
    -在進行dispatch()分發前,會對請求進行身份認證、許可權檢查、流量控制

drf 配置檔案之查詢順序

在apiView中使用了drf配置檔案的預設配置。以下是配置的查詢順序。

方式三:查詢順序(一般就用內建的即可)
     1. 檢視類  (區域性配置)
     2. django settings  (全域性配置) 
     3. drf api_settings (內建配置)
說明:
優先使用檢視類中renderer_classes的配置,其次使用django專案配置檔案settings中的配置,最後使用drf內建的api_settings的配置

在django的settings中應該按照如下格式寫:

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [    # 配置響應
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer',
    ],
    'DEFAULT_PARSER_CLASSES': [  # 配置請求
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser',
    ],
}
# 注意:所有配置都寫在一個REST_FRAMEWORK裡面!

drf之請求

APIView之請求相關配置

# 為什麼需要進行請求相關配置?
可以定製某些CBV只能只能接收json格式,不能接收其他格式。也就是為了自定義該介面可接受編碼格式。

# 預設情況下
前端上傳json                  request.data裡面是   ---> python字典
前端上傳urlencode\formdata    request.data裡面是   ---> QueryDict


# 方式一,在繼承自APIView及其子類的的檢視類中配置(區域性配置)
# 總共有三個:from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
class BookView(APIView):
    parser_classes = [JSONParser,]
    
# 方式二:在配置檔案中配置(影響所有,全域性配置)
    -django有套預設配置,每個專案有個配置
    -drf有套預設配置,每個專案也有個配置---》就在django的配置檔案中
    REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': [
        # 'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        # 'rest_framework.parsers.MultiPartParser',
    ],
}
       
# 方式三:全域性配了1個,某個檢視類想要3個,怎麼配?
    -只需要在檢視類,配置3個即可
    -因為:先從檢視類自身找,找不到,去專案的drf配置中找,再找不到,去drf預設的配置找
    
# 檢視類方法中的request
    -data
    -__getattr__
    -query_params

drf之響應

APIView之響應相關配置

# 為什麼要在CBV中設定響應相關配置?
因為對於drf的響應,如果使用瀏覽器和postman訪問同一個介面,Response返回的格式是不一樣的
	-drf做了個判斷,如果是瀏覽器,好看一些,如果是postman只要json資料
    
    
# 方式一:在檢視類中寫(區域性配置)
	-兩個響應類---》找---》drf的配置檔案中找--》兩個類
    -from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
    class BookView(APIView):
    	renderer_classes=[JSONRenderer,]

# 方式二:在專案配置檔案中寫(全域性配置)
    REST_FRAMEWORK = {
      'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}
    
# 方式三:使用順序(一般就用內建的即可)
     1. renderer_classes
     2. django settings
     3. drf api_settings
說明:
優先使用檢視類中renderer_classes的配置,其次使用django專案配置檔案settings中的配置,最後使用drf內建的api_settings的配置

Response物件屬性

# drf 的Response 原始碼分析
    -from rest_framework.response import Response
    -檢視類的方法返回時,retrun Response ,走它的__init__, init中可以傳什麼引數
    -Responses最終繼承httpresponse.
    
    
# Response init可以傳的引數
    def __init__(self, 
                 data=None, 
                 status=None,
                 template_name=None, 
                 headers=None,
                 exception=False, 
                 content_type=None)
    
   -data:之前我們們寫的ser.data  可以是字典或列表,字串---》序列化後返回給前端---》前端在響應體中看到的就是這個 

   -status: http響應的狀態碼,預設是200,你可以改
        -drf在status包下,把所有http響應狀態碼都寫了一遍,常量
        -from rest_framework.status import HTTP_200_OK
        -Response('dddd',status=status.HTTP_200_OK)
      
   -template_name:瞭解即可,修改響應模板的樣子,BrowsableAPIRenderer定死的樣子,後期公司可以自己定製

   -headers:響應頭,http響應的響應頭 示例:header={'xxx':'yyy'}
    
   -content_type :響應編碼格式,一般不動
   
# 重點:data,status,headers

# 原生djagno,如何在響應頭中加東西?

           '''
           四件套 render,redirect,HttpResponse,JsonResponse
           方法: 產生HttpResponse然後新增屬性
           '''
         # 示例:
            obj = HttpResponse('dddd')
            obj['xxc'] = 'yyc'
            return obj
        
         響應頭新增屬性涉及知識 ---> 跨域 

drf Response提供很多的狀態碼:
image-20230203105225383

在status內將所有狀態碼都寫了一遍:
image-20230203105248081

基於APIView + ModelSerializer寫五個介面

檢視類

from .models import Book
from .serializer import BookSerializer


class BookView(APIView):
    def get(self, request):
        books = Book.objects.all()
        ser = BookSerializer(instance=books, many=True)
        return Response(ser.data)

    def post(self, request):
        ser = BookSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            # 我們們現在只有ser序列化類的物件,但是我們們想要,新增的物件---》序列化成字典---》大前提,序列化類中的create方法一定要返回新增的物件
            return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
        else:
            return Response({'code': 101, 'msg': ser.errors})


class BookDetailView(APIView):
    def get(self, request, pk):
        books = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=books)
        return Response(ser.data)

    def put(self, request, pk):
        books = Book.objects.filter(pk=pk).first()
        ser = BookSerializer(instance=books, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '修改成功', 'result': ser.data})
        else:
            return Response({'code': 101, 'msg': ser.errors})

    def delete(self, request, pk):
        Book.objects.filter(pk=pk).delete()
        return Response({'code': 100, 'msg': '刪除成功'})

序列化類

### ModelSerializer的使用
class BookSerializer(serializers.ModelSerializer):
    # 跟表有關聯
    class Meta:
        model = Book
        fields = ['name', 'price', 'publish_detail', 'author_list', 'publish', 'authors']
        extra_kwargs = {'name': {'max_length': 8},
                        'publish_detail': {'read_only': True},
                        'author_list': {'read_only': True},
                        'publish': {'write_only': True},
                        'authors': {'write_only': True},
                        }

路由

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

基於GenericAPIview 寫五個介面

如果需要再新寫關於作者的五個介面 ----> 又需要寫一個CBV
但是這兩個CBV的區別僅僅在於使用的 表模型序列化類 不同,其他都是重複的程式碼。
這豈不是很麻煩?能不能透過繼承的方式,最佳化程式碼?
於是就寫了一個 GenericAPIview --繼承於--> APIView ,以後可以基於這個類來寫5個介面。

GenericAPIview必備設定

image-20230203114428183

如果你想使用GenericAPIview,你需要從如下二種選擇其一:

  1. 在檢視類中設定如下屬性 (常用)

    querysetserializer_class

  2. 重寫GenericAPIview類的get_queryset()/get_serializer_class()方法

如果你重寫了一個檢視方法,那麼重要的是 你應該呼叫get_queryset() 而不是直接的訪問queryset屬性。
因為queryset將只被設定一次,並且為了後續到來的所有請求,這個結果會被快取。

總而言之,不要直接訪問querysetserializer_class屬性,而是使用GenericAPIview提供的各種方法獲取。

查詢所有

image-20230204145645336

# 首先指定模型物件 和 序列化類
class BookView(GenericAPIView):
    queryset = Book.objects.all()
    # queryset = Book.objects 這樣也是可行的
    serializer_class = BookSerializer

# 以下程式碼都是等效的
objs = Book.objects.all()
objs = self.get_queryset()

# 以下程式碼都是等效的
ser = self.get_serializer(instance=objs, many=True)
BookSerializer(instance=objs, many=True)

get_queryset()

image-20230203173342133

get_queryset方法得到檢視中的列表物件!
這個列表物件必須是一個可迭代的,也可以是一個queryset物件。

預設使用self.queryset來獲取(檢視中的列表物件):

image-20230203202821381

應該總是使用這個方法來獲取,而不是直接呼叫self.queryset

get_queryset原始碼做了些什麼事?

  1. 如果沒有在檢視類中寫queryset屬性,然後就呼叫get_queryset,會丟擲異常。
  2. 獲取我們設定的queryset屬性,如果是Queryset物件,則呼叫all()方法,最後將我們設定的queryset類屬性返回出去。

get_serializer()

image-20230203201429473

使用get_serializer()方法可以返回序列化器的例項,此序列化器,被應用於校驗、反序列化前端輸入和序列化後端輸出。

get_serializer原始碼做了些什麼事?

  1. 透過get_serializer_class方法獲取了我們在檢視類中指定的序列化類

  2. 新增了一個'context'引數傳入我們的序列化類。

    相當於BookSerializer(instance=objs, many=True, context={一些資料...})

image-20230203202858354

get_serializer_class()

get_serializer_class方法基本上什麼事情都沒有做,直接將序列化器返回,有需求可以重寫get_serializer_class。
可以實現:不同的介面使用的序列化類不一樣。序列化使用某一個序列化類,反序列化用另一個序列化類。

image-20230203203043304

重寫:

image-20230203224942685

查詢一個

image-20230203212916443

這裡會根據傳入的pk引數查詢出對應的模型物件,
正常情況下寫查詢一個的介面我們需要手動寫orm( 比如Book.objects.filter(pk=pk)),根據主鍵將物件查出來。
這裡因為使用了GenericAPIview,他會自動幫我們查。

get_object()

就是透過pk引數和get_object方法將模型物件查詢出來的。

返回應用於詳細檢視的物件例項。預設使用 lookup_field 引數過濾基本的查詢集。
該方法可以被重寫以提供更復雜的行為,例如基於多個 URL 引數的物件查詢。

lookup_field屬性

image-20230203202258635

如果你想使用pk之外的物件查詢方式,可以設定lookup_field。如果有更復雜的查詢需求,可以重寫get_object()

以下給出一個示例:
image-20230203214531929

修改查詢條件為書籍的名字:

image-20230203214630826

filter_queryset()

image-20230204141418521

可見在get_object方法中,呼叫了get_queryset()獲取了我們放在檢視中的queryset,然後使用了filter_queryset()對我們從資料庫中獲取的queryset進行了過濾操作。

image-20230204142438060

關於filter_queryset的解釋是:
給他一個queryset,他會使用任何一個你正在使用的後端過濾器,進行過濾。

self.filter_backends:由於我們類中沒有配置,所以會指向GenericAPIview類中的filter_backends。

在GenericAPIview類中預設使用的是drf配置檔案中指定的預設過濾器。
image-20230204143250366

然後drf配置檔案中,預設是不過濾:
image-20230204143605380

所有總而言之,預設就是不過濾,但是我們可以透過在自己的檢視類中寫filter_backends屬性,來指定過濾器。

新增一個

    def post(self, request):
        ser = self.get_serializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
        else:
            return Response({'code': 101, 'msg': ser.errors})

修改一個

 def put(self, request, pk):
        obj = self.get_object()
        ser = self.get_serializer(instance=obj, data=request.data)
        if ser.is_valid():
            ser.save()
            return Response({'code': 100, 'msg': '修改成功', 'result': ser.data})
        else:
            return Response({'code': 101, 'msg': ser.errors})

刪除一個

  def delete(self, request, pk):
        obj = self.get_object()
        obj.delete()
        return Response({'code': 100, 'msg': '刪除成功'})

更多GenericAPIview 類屬性

基本設定

  • queryset - 用於從檢視返回物件的查詢結果集。通常,你必須設定此屬性或者重寫 get_queryset() 方法。如果你重寫了一個檢視的方法,重要的是你應該呼叫 get_queryset() 方法而不是直接訪問該屬性,因為 queryset 將被計算一次,這些結果將為後續請求快取起來。
  • serializer_class - 用於驗證和反序列化輸入以及用於序列化輸出的Serializer類。 通常,你必須設定此屬性或者重寫get_serializer_class() 方法。
  • lookup_field - 用於執行各個model例項的物件查詢的model欄位。預設為 'pk'。 請注意,在使用超連結API時,如果需要使用自定義的值,你需要確保在API檢視序列化類設定查詢欄位。
  • lookup_url_kwarg - 應用於物件查詢的URL關鍵字引數。它的 URL conf 應該包括一個與這個值相對應的關鍵字引數。如果取消設定,預設情況下使用與 lookup_field相同的值。

配置檔案相關

以下屬性用於在與列表檢視一起使用時控制分頁。

  • pagination_class - 當分頁列出結果時應使用的分頁類。預設值與 DEFAULT_PAGINATION_CLASS 設定的值相同,即 'rest_framework.pagination.PageNumberPagination'

  • filter_backends - 用於過濾查詢集的過濾器後端類的列表。預設值與DEFAULT_FILTER_BACKENDS 設定的值相同。

基於GenericAPIView + 5個檢視擴充套件類寫五個介面

雖然使用了GenericAPIview類寫五個介面,但是寫的程式碼還是太多了,並沒有減少程式碼呀!
CBV類中的方法 getpostputdelete程式碼都是重複的,是不是可以再進行最佳化?

drf的作者自然想到了這一點,他提供了5個檢視擴充套件類,幫我們寫了這一部分程式碼!
先匯入五個檢視擴充套件類:

from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin

查詢所有

class BookView(GenericAPIView, ListModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request):
        return self.list(request)

現在就只需要使用self.list呼叫ListModeMixin類中寫的程式碼:
image-20230204151025047

和我們之前寫的差不多,先獲取queryset然後將其傳入序列化類。中間還進行了過濾,分頁操作。

查詢一個

class BookDetailView(GenericAPIView, RetrieveModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request, pk):  # 注意要傳入這個pk
        return self.retrieve(request)

對應關係

這樣很方便的就可以實現五個介面,我們只需要知道方法直接的對應關係就行了:

ListModelMixin      -->  list      -->  查詢所有
RetrieveModelMixin  -->  retrieve  -->  查詢一個
CreateModelMixin    -->  create    -->  新增一個
UpdateModelMixin    -->  update    -->  修改一個
DestroyModelMixin   -->  destroy   -->  刪除一個

程式碼:

class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request):
        return self.list(request)

    def post(self, request):
        return self.create(request)

class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
    queryset = Book.objects
    serializer_class = BookSerializer

    def get(self, request, pk):
        return self.retrieve(request)

    def put(self, request, pk):
        return self.update(request)

    def delete(self, request, pk):
        return self.destroy(request)

雖然已經簡化了程式碼,但是其實還是有重複的部分比如這些請求方法,之後還會進行封裝。

繼承具體檢視類寫五個介面

什麼叫具體檢視類?Concrete View Classes
也就是drf作者已經幫你把CBV需要寫的介面程式碼都寫好了,打包成了一個個類,你只需要直接繼承這些類,你的檢視類就會有相應的介面,是不是很神奇 ~ ~

drf提供如下具體檢視類:

image-20230204154227345

注意:沒有DestroyUpdateAPIView

直接上程式碼:

from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView


class BookView(ListCreateAPIView):  # 查詢所有 新增一個 
    queryset = Book.objects
    serializer_class = BookSerializer


class BookDetailView(RetrieveUpdateDestroyAPIView):   # 查詢一個 修改一個 刪除一個
    queryset = Book.objects
    serializer_class = BookSerializer
    

使用ModelViewSet寫五個介面

之前我們使用兩個CBV寫五個介面的原因是:
查詢一個和查詢所有都是使用get請求,為了解耦合,避免在類中的get方法中寫太多程式碼,所以將其拆成兩個CBV。

而使用ModelViewSet可以實現,一個檢視類寫5個介面。

from rest_framework.viewsets import ModelViewSet

class BookView(ModelViewSet):
    queryset = Book.objects
    serializer_class = BookSerializer

參考

相關文章