drf-視圖

一、基於APIView的五個介面

首先,APIView是Rest Framework提供的所有視圖的基類,繼承了Django的View

其次,APIView和View的不同在於:

  • 他們傳入視圖方法中的request對象不是同一個,APIView的是Request對象,View的是HttpResponse對象。
  • APIView返回的是Response對象,是符合前端Accept要求的格式
  • 在dispatch分發前會進行身份認證,許可權檢查,流量控制

五個介面實例:

from rest_framework.views import APIView
from rest_framework.response import Response
from app01.ser import BooksSerializers
from app01.models import Books


# 基於APIView
class BooksView(APIView):
    # 查所有
    def get(self, request):
        books = Books.objects.all()
        books_ser = BooksSerializers(instance=books, many=True)
        return Response(books_ser.data)
	
    # 增加
    def post(self, request):
        books_ser = BooksSerializers(data=request.data)
        if books_ser.is_valid():
            books_ser.save()
        return Response(books_ser.data)


class BookView(APIView):
    # 查單個
    def get(self, request, pk):
        print(pk, type(pk))
        book = Books.objects.filter(pk=pk).first()
        book_ser = BooksSerializers(instance=book)
        return Response(book_ser.data)

	# 修改
    def put(self, request, pk):
        book = Books.objects.filter(pk=pk).first()
        book_ser = BooksSerializers(instance=book, data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.data)
        else:
            return Response({'message': book_ser.errors})

	# 刪除
    def delete(self, request, pk):
        Books.objects.filter(pk=pk).delete()
        return Response({'message': '刪除成功'})
    
    
# urls.py
path('books/', views.BooksView.as_view()),
re_path(r'^book/(?P<pk>\d+)/', views.BookView.as_view()),

二、基於GenericAPIView的五個介面

首先,GenericAPIView繼承了APIView,並增加了操作序列化器和資料庫查詢方法

並提供了兩個屬性和兩個方法

queryset:指要傳的queryset對象

get_queryset():返回queryset對象

serializer_class:指明視圖使用的序列化器,也就是哪個序列化類來序列化這堆數據

get_serializer():返回序列化器類

get_object():返回視圖所需的模型類數據對象

源碼如下:

# from rest_framework.generics import GenericAPIView
class GenericAPIView(views.APIView):
    queryset = None				# 指要傳的queryset對象
    serializer_class = None		# 指明視圖使用的序列化器,也就是哪個序列化類來序列化這堆數據
    ......
    
	def get_queryset(self):
    	......
        queryset = self.queryset
        # 判斷傳入的queryset是不是QuerySet的對象
        if isinstance(queryset, QuerySet):
            queryset = queryset.all()	# 是就加all()——>Book.objects.all()
        return queryset		# 返回queryset對象


	def get_serializer(self, *args, **kwargs):
        serializer_class = self.get_serializer_class()
        
        # 向序列化器對象的context屬性補充三個數據,
        # request,當前視圖的請求對象
        # format,當前請求的類視圖對象
        # view,當前請求期望返回的數據格式
        kwargs.setdefault('context', self.get_serializer_context())
        return serializer_class(*args, **kwargs)	# 返回序列化器類

五個介面實例:

from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from app01.ser import BooksSerializers
from app01.models import Books


# 基於GenericAPIView
class BooksView(GenericAPIView):
    queryset = Books.objects
    serializer_class = BooksSerializers
	
    # 查所有
    def get(self, request):
        books = self.get_queryset() # 相當於Books.objects.all()
        books_ser = self.get_serializer(instance=books, many=True)
        return Response(books_ser.data)
	
    # 新增
    def post(self, request):
        books_ser = self.get_serializer(data=request.data)
        if books_ser.is_valid():
            books_ser.save()
        return Response(books_ser.data)


class BookView(GenericAPIView):
    queryset = Books.objects
    serializer_class = BooksSerializers

	# 查單個
    def get(self, request, pk):
        book = self.get_object()
        book_ser = self.get_serializer(instance=book)
        return Response(book_ser.data)

	# 修改
    def put(self, request, pk):
        book = self.get_object()
        book_ser = self.get_serializer(instance=book, data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.data)
        else:
            return Response({'message': book_ser.errors})

	# 刪除
    def delete(self, request, pk):
        self.get_object().delete()
        return Response({'message': '刪除成功'})

三、GenericAPIView的五個視圖括展類

因為之前程式碼的處理流程重複程式碼太多,所以可以使用相應的擴展類來減少編寫的程式碼量。

而這五個擴展類需要搭配GenericAPIView父類,並且還要調用GenericAPIView提供的序列化器和數據查詢方法。

ListModeMixin

列表視圖擴展類(類似於查詢所有),提供了快速獲取所有列表的視圖,並且該方法會對數據進行過濾和分頁,成功返回200

源程式碼:

class ListModelMixin:
    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)	# 返回序列化後的數據

CreateModeMixin

創建視圖擴展類(創建數據),提供了快速創建資源的視圖,成功返回201,失敗400

原程式碼:

class CreateModelMixin:
    # 創建模型實例
    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 {}

RetrieveModelMixin

詳情視圖擴展類(查詢單個),返回一個存在的數據對象,成功返回201,失敗404

原程式碼:

class RetrieveModelMixin:
    def retrieve(self, request, *args, **kwargs):
        # 獲取對象,並檢查對象的許可權
        instance = self.get_object()
        # 序列化
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

UpdateModelMixin

更新視圖擴展類(更新數據),快速更新一個存在的數據對象,也可實現局部更新 partial_update() 成功返回201,失敗400

源程式碼

class UpdateModelMixin:
    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):
            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)

DestroyModelMixin

刪除視圖擴展類(刪除數據),快速刪除一個存在的數據對象,成功返回204,不存在返回404。

源程式碼

class DestroyModelMixin:
    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()

五個視圖擴展類的使用實例

from rest_framework.generics import GenericAPIView
from app01.ser import BooksSerializers
from app01.models import Books
from rest_framework.mixins import ListModelMixin, CreateModelMixin
from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin


# 基於GenericAPIView的五個視圖擴展類
class BooksView(GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = Books.objects
    serializer_class = BooksSerializers

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

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


class BookView(GenericAPIView, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin):
    queryset = Books.objects
    serializer_class = BooksSerializers

    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)

四、GenericAPIView的視圖子類

二話不說先來看其中一個源碼!!!其他的子類都是一樣的原理

源碼

# 首先它繼承了CreateModelMixin創建視圖擴展類
class CreateAPIView(mixins.CreateModelMixin, GenericAPIView):	
    # 並且它提供了post方法,那我們就不用在視圖中寫post方法了,這裡直接寫好了直接調用self.create
    # 只需要傳入對象和序列化器就可以了
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

實例:

from rest_framework.generics import ListAPIView, UpdateAPIView
from rest_framework.generics import CreateAPIView, DestroyAPIView, RetrieveAPIView
from app01.ser import BooksSerializers
from app01.models import Books


# 基於GenericAPIView的五個視圖子類
class BooksView(ListAPIView, UpdateAPIView):    # 獲取所有,新增一個
    queryset = Books.objects
    serializer_class = BooksSerializers


# 獲取一個,修改一個,刪除一個
class BookView(CreateAPIView, DestroyAPIView, RetrieveAPIView):
    queryset = Books.objects
    serializer_class = BooksSerializers

GenericAPIView的視圖子類除了這五個還有四個,也就是這五個方法的其他組合:

  • ListCreateAPIView
    • 提供get、post方法,是 ListAPIView 和 UpdateAPIView 的組合
  • RetrieveDestroyAPIView
    • 提供get、delete方法,是 RetrieveAPIView 和 DestroyAPIView 的組合
  • RetrieveUpdateAPIView
    • 提供get、update方法,是 RetrieveAPIView 和 UpdateAPIView 的組合
  • RetrieveUpdateDestroyAPIView
    • 提供get、update、delete方法,是 RetrieveAPIView 、 UpdateAPIView 和 DestroyAPIView 的組合

五、ModelViewSet

首先它繼承了 GenericViewSet,並且還有 ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。

ModelViewSet五個介面

先來看一下它的使用:

# views.py
from rest_framework.viewsets import ModelViewSet
from app01.ser import BooksSerializers
from app01.models import Books

class BooksView(ModelViewSet):		# 五個介面都有
    queryset = Books.objects
    serializer_class = BooksSerializers
    
    
# urls.py
from django.contrib import admin
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 當路徑匹配,又是get請求,會執行BooksView的list方法
    path('books/', views.BooksView.as_view({'get': 'list', 'post': 'create'})),
    re_path(r'^book/(?P<pk>\d+)/', views.BooksView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}))
]

源碼分析

由於 ModelViewSet 繼承了 GenericViewSet ,並且 GenericViewSet 又繼承了 ViewSetMixin,ViewSetMixin中又重寫了 as_view 方法, 也就是說當路徑匹配上了會執行這裡重寫的 as_view 方法。

分析: as_view 方法

@classonlymethod
def as_view(cls, actions=None, **initkwargs):
    
	...

    def view(request, *args, **kwargs):
        self = cls(**initkwargs)
        
        # 核心程式碼,所有路由中只要配置了對應關係,比如{'get': 'list'},當get請求來,就會執行list方法
        for method, action in actions.items():
            # 遍歷我們傳入的對應關係
            # method = get
            # action = list
            
            # 並且通過反射獲取list方法的記憶體地址
            handler = getattr(self, action)		# handler已經是list的記憶體地址
            
            # 點攔截(對象.get = list)
            setattr(self, method, handler)
            
        # 當循環完畢之後,對象.get:對應list方法,對象.post:對應create方法

        ...

        # 後面照常繼續
        return self.dispatch(request, *args, **kwargs)

繼承ViewSetMixin的視圖類

由上面的分析可以得出,我們也可以直接繼承 ViewSetMixin 寫視圖,如下:

from rest_framework.viewsets import ViewSetMixin
from rest_framework.response import Response
from app01.ser import BooksSerializers
from app01.models import Books


class BooksView(ViewSetMixin, APIView):
    def get_all_books(self, request):
        book_list = Books.objects.all()
        book_ser = BooksSerializers(book_list, many=True)
        return Response(book_ser.data)
    
    
# urls.py
# 當匹配上了路由,又是get請求,就調用get_all_books方法
path('books/', views.BooksView.as_view({'get': 'get_all_books'})),
Tags: