1. 程式人生 > 實用技巧 >第十五週

第十五週

一. 前言#

Django REST framwork 提供的檢視的主要作用

Copy
1. 控制序列化器的執行(檢驗、儲存、轉換資料)
2. 控制資料庫查詢的執行

二. 兩個檢視基類#

兩個檢視基類: APIView, GenericAPIView

1. APIView#

1) models.py#

Copy
class Book(models.Model):
    name = models.CharField(max_length=32, null=True)
    price = models.DecimalField(max_digits=8, decimal_places=2
, null=True) author = models.CharField(max_length=32, null=True) publish = models.CharField(max_length=32, null=True)

2) serializer.py 自定義序列化.py檔案#

Copy
from rest_framework import serializers

from .models import Book

def check_name(data):
    return data


class BookSerializer(serializers.Serializer)
:
nid = serializers.CharField(read_only=True, source='pk') name = serializers.CharField(required=True, error_messages={ 'required': '必須輸入內容' }, validators=[check_name]) price = serializers.DecimalField(max_digits=8, decimal_places=2, max_value=1000000, min_value=1) author = serializers.CharField(max_length=32
, min_length=0, trim_whitespace=True) publish = serializers.CharField(allow_blank=True) def update(self, instance, validate_data): for key, value in validate_data.items(): if hasattr(instance, key): setattr(instance, key, value) instance.save() return instance def create(self, validate_data): return Book.objects.create(**validate_data)

3) views.py#

Copy
# 自定義封裝的SelfResponse類
'''
from rest_framework.response import Response


class SelfResponse(Response):
    def __init__(self, status=1000, messages='成功', results=None, error=None, *args, **kwargs):
        self.data = {
            'status': status,
            'messages': messages,
        }
        if results:
            self.data['results'] = results
        elif results:
            self.data['error'] = error
        super().__init__(data=self.data, *args, **kwargs)
'''
from rest_framework.views import APIView
from rest_framework.renderers import JSONRenderer

from .models import Book
from .serializer import BookSerializer
from drf_views.utils import SelfResponse


# Create your views here.
class BookListCreateView(APIView):
    # 控制響應格式. 預設有2種: 一種響應瀏覽器, 一種響應json格式資料
    renderer_classes = [JSONRenderer]

    def get(self, request):
        book_queryset = Book.objects.filter()
        serializer = BookSerializer(instance=book_queryset, many=True)
        return SelfResponse(results=serializer.data)

    def post(self, request):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            obj = SelfResponse(results=serializer.data)
        else:
            obj = SelfResponse(2000, '失敗', error=serializer.errors)
        return obj


class BookDestroyRetrieveUpdate(APIView):
    def get(self, request, pk):
        book_obj = Book.objects.filter(pk=pk).first()
        serializer = BookSerializer(instance=book_obj)
        return SelfResponse(results=serializer.data)

    def put(self, request, pk):
        book_obj = Book.objects.filter(pk=pk).first()
        serializer = BookSerializer(instance=book_obj, data=request.data)
        if serializer.is_valid():
            serializer.save()
            obj = SelfResponse(results=serializer.data)
        else:
            obj = SelfResponse(2000, '失敗', error=serializer.errors)
        return obj

    def delete(self, request, pk):
        Book.objects.filter(pk=pk).delete()
        return SelfResponse()

4) urls.py#

Copy
from django.conf.urls import url
from .views import BookListCreateView
from .views import BookDestroyRetrieveUpdate

urlpatterns = [
    url(r'^books/$', BookListCreateView.as_view()),
    url(r'^books/(?P<pk>\d+)', BookDestroyRetrieveUpdate.as_view()),
]

5) 總結#

Copy
1. 繼承關係: APIView繼承View
2. APIView基於View的拓展:
    APIView重寫了View的dispatch方法, 在該方法中實現了實現了一下功能:
    1) 對來的原生請求物件request進行了封裝.
    2) 提供了對包裝過後的請求物件的三段認證: 認證, 許可權控制, 頻率控制
    3) 重寫了View中通過本次請求的方式動態的反射到自定義繼承APIView類例項化的物件中定義的請求方法
    4) 使用異常處理處理2,3步驟中的異常
    5) 處理完畢異常以後使用drf的response物件對請求響應
3. 針對路由配置
    路由中的有名分組必須指定pk, 檢視中使用必須使用相同的關鍵字引數接受

2. GenericAPIView和5個檢視擴充套件類寫的介面#

1) models.py#

Copy
from django.db import models

# Create your models here.
class Book(models.Model):
    name = models.CharField(max_length=32, null=True)
    price = models.DecimalField(max_digits=8, decimal_places=2, null=True)
    author = models.CharField(max_length=32, null=True)
    publish = models.CharField(max_length=32, null=True)
    publish_time = models.DateTimeField(auto_now_add=True)

2) serializer.py 自定義序列化.py檔案#

Copy
from rest_framework import serializers
from .models import Book


class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'
        # exclude = ('id', )
        # exclude = ['id']
        extra_kwargs = {
            # 'price': {'write_only': True}
        }
        read_only_fields = ['id', ]
        # 該方式在drf3.2版本以後就被棄用了, 使用ModelSerializer現在使用的的extra_kwargs
        # write_only_fields = []

3) views.py#

Copy
# 自定義封裝的SelfResponse類
'''
from rest_framework.response import Response


class SelfResponse(Response):
    def __init__(self, status=1000, messages='成功', results=None, error=None, *args, **kwargs):
        self.data = {
            'status': status,
            'messages': messages,
        }
        if results:
            self.data['results'] = results
        elif results:
            self.data['error'] = error
        super().__init__(data=self.data, *args, **kwargs)
'''
from rest_framework.generics import GenericAPIView

from drf_views.utils import SelfResponse
from .models import Book
from .serializer import BookModelSerializer


# Create your views here.

class BookListCrateView(GenericAPIView):
    # queryset = Book.objects.all()
    queryset = Book.objects
    serializer_class = BookModelSerializer

    def get(self, request):
        book_obj = self.get_queryset()
        serializer = self.get_serializer(instance=book_obj, many=True)
        return SelfResponse(results=serializer.data)

    def post(self, request):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            obj = SelfResponse(results=serializer.data)
        else:
            obj = SelfResponse(2000, '失敗', error=serializer.errors)
        return obj


class BookUpdateDestroyRetrieve(GenericAPIView):
    queryset = Book.objects
    serializer_class = BookModelSerializer
    lookup_url_kwarg = 'www'

    def get(self, request, www):
        book_obj = self.get_object()
        serializer = self.get_serializer(instance=book_obj)
        return SelfResponse(results=serializer.data)

    def put(self, request, pk):
        book_obj = self.get_object()
        serializer = self.get_serializer(instance=book_obj, data=request.data)
        if serializer.is_valid():
            serializer.save()
            obj = SelfResponse(results=serializer.data)
        else:
            obj = SelfResponse(2000, '失敗', error=serializer.errors)
        return obj

    def delete(self, request, pk):
        self.get_object().delete()
        return SelfResponse()

4) GenericAPIView提供的三種方法的原始碼分析#

Copy
# 1. self.get_queryset
    def get_queryset(self):
        # 1) 斷言繼承GenericAPIView的檢視類例項化的物件時候有queryset物件
        '''
        如果沒有: 那麼斷言成功丟擲異常
        如果有:   那麼將會繼續往下執行
        因此為什麼檢視類中要為類新增一個queryset屬性的原因就明白了
        '''
        assert self.queryset is not None, (
            "'%s' should either include a `queryset` attribute, "
            "or override the `get_queryset()` method."
            % self.__class__.__name__
        )
        # 2) 由自定義檢視類例項化出來的物件獲取類中定義的queryset物件. 進行判斷
        ''''
        如果我們自定義檢視類中書寫的屬性是queryset物件, 那麼就會幫我們自動.all(). 因此我們可以不用點all了.
        如果不是, 那麼就直接返回資料物件
        '''
        queryset = self.queryset
        if isinstance(queryset, QuerySet):
            # Ensure queryset is re-evaluated on each request.
            queryset = queryset.all()
        return queryset

# 2. self.get_serializer
    def get_serializer(self, *args, **kwargs):
        # 1) 獲取繼承GenericAPIView檢視類例項化物件中找get_serializer_class
        '''
        def get_serializer_class(self):
            # ① 斷言繼承GenericAPIVIew的自定義檢視類例項化出來的物件中serializer_class時候為None
            '''None斷言成功, 丟擲指定異常
            不為None斷言失敗, 繼續往下執行
            '''
            assert self.serializer_class is not None, (
                "'%s' should either include a `serializer_class` attribute, "
                "or override the `get_serializer_class()` method."
                % self.__class__.__name__
            )
            # ② 發現本質就是獲取檢視類中定義的serializer_class序列化器類, 因此從這裡我們就明白為什麼檢視類中要寫序列化器類了.
            return self.serializer_class
        '''
        # 2) get_serializer_class方法就是獲取到了我們自定義檢視類中定義的serializer_classes屬性
        serializer_class = self.get_serializer_class()
        # 3) 內部就是return, 獲取我們自定義檢視類中的上下文一些資訊
        '''
        def get_serializer_context(self):
            return {
                'request': self.request,
                'format': self.format_kwarg,
                'view': self
            }
        '''
        kwargs['context'] = self.get_serializer_context()
        # 4) 這裡的serializer_class就是自定義檢視類中的類屬性, 通過類屬性呼叫傳值
        return serializer_class(*args, **kwargs)

# 3. self.get_object
    def get_object(self):
        # 1) 執行過濾操作, 預設沒有配置過濾類. 因此queryset還是queryset
        queryset = self.filter_queryset(self.get_queryset())

        # 2) 運用短路運算, lookup_url_kwarg預設就是None,  lookup_field預設就是pk, 因此返回值就是pk
        '''
        提示: 繼承GenericAPIView的自定義檢視類中沒有定義以下引數. 預設使用的是GenericAPIView類中配置的
        lookup_field = 'pk'
        lookup_url_kwarg = None
        '''
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

        # 3) 斷言剛剛的pk時候在self.kwargs中
        '''
        查詢順序: 檢視物件 -> 檢視類 -> GenericAPIView -> APIView -> dispatch.
        kwargs就是路由匹配來了, 走到APIVIew中的dispatch方法中將傳入的關鍵字引數形式的key:value對到kwargs字典中,
         再存到的物件當中, 直至此刻拿出來進行判斷.
         本質就是必須安裝關鍵字pk=xxx的形式傳參, 路由中必須指定又名分組pk, 檢視中必須指定接受的關鍵字引數是pk
        '''
        assert lookup_url_kwarg in self.kwargs, (
            'Expected view %s to be called with a URL keyword argument '
            'named "%s". Fix your URL conf, or set the `.lookup_field` '
            'attribute on the view correctly.' %
            (self.__class__.__name__, lookup_url_kwarg)
        )
        # 4) 這裡就是獲取預設定義lookup_field充當字典的key, 又從self.kwargs這個dispatch方法就賦值的字典中, 將通過key取值, lookup_url_kwarg就是第2步分析出來的值
        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        # 5) get_object_or_404的功能就是獲取物件 或者 丟擲丟擲404異常
        '''
        **filter_kwargs就是步驟4的字典. 字典的結構{'pk': value},
        **就將字典拆散, 以關鍵字的形式傳參, 傳給了get_object_or_404方法

        def get_object_or_404(queryset, *filter_args, **filter_kwargs):
            try:
                # _get_object_or_404內部就是從闖傳進來的queryset物件中使用get以關鍵字的形式查詢,但是get方法會在2種情況下, 會丟擲異常, 因此內部也做了異常處理
                return _get_object_or_404(queryset, *filter_args, **filter_kwargs)
            except (TypeError, ValueError, ValidationError):
                # 這裡就獲取到了_get_object_or_404中丟擲異常, 由Http404例項化類例項化出來的物件返回到上一層
                raise Http404

        拓展: 修改必須使用pk作為有名分組!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        本質就是通過get將傳過來的欄位拆散, 以關鍵字的形式獲取對應的資料物件
        因此在自定義的檢視類中重寫 lookup_url_kwarg 就可以實現, 修改路由中, 對應的又名分組必須取名是pk了.
        例如: 有名分組是(?P<num>), 關鍵字必須num接受
            那麼self.kwargs在給檢視物件中方法傳參的時候就是{'num': param}而,
            self.kwargs[lookup_url_kwarg]這一步操作直接獲取到的就是param了.
        '''
        obj = get_object_or_404(queryset, **filter_kwargs)

        # 5) 這一步是APIView中dispatch方法中定義的三段認證中的許可權認證. 在drf的認證中會講到
        self.check_object_permissions(self.request, obj)

        return obj

5) urls.py#

Copy
from django.conf.urls import url
from .views import BookListCrateView
from .views import BookUpdateDestroyRetrieve

urlpatterns = [
    url(r'^books/$', BookListCrateView.as_view()),
    url(r'^books/(?P<www>\d+)', BookUpdateDestroyRetrieve.as_view()),
]

6) 總結#

Copy
1. 繼承GenericAPIView的檢視類, 當需要修改介面指定操作的模型類, 以及序列化的模型類直接在檢視類中修改即可了.其他都不用動就可以實現偷換模型類以及序列化類
2. GenericAPIView提供了3個主要的方法
    self.get_object()     獲取單條資料
    self.get_queryset()   獲取多條資料
    self.serializer_classes(引數同原來即可)  執行自定義檢視類中定義的序列化類進行序列化, 將ORM物件的資料轉換成python的物件
3. GenericAPIView提供了可修改路由又名分組的指定不再是預設的pk
    自定義的檢視類

三. 五個檢視擴充套件類#

1) models.py#

Copy
from django.db import models


# Create your models here.
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    author = models.CharField(max_length=32)

    # 這裡的定義可以讓序列化類中當作序列化欄位來處理, return的結果是什麼就是什麼.
    @property
    def current_time(self):
        import time
        return time.strftime("%Y-%m-%d %X")

    publish = models.ForeignKey(to='Publish')


class Publish(models.Model):
    name = models.CharField(max_length=32)
    email = models.EmailField()

2) serializer.py 自定義序列化.py檔案#

Copy
from rest_framework import serializers

from .models import Book
from .models import Publish


class PublishModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Publish
        fields = '__all__'


class BookModelSerializer(serializers.ModelSerializer):
    publish = PublishModelSerializer()
    '''
    publish = serializers.SerializerMethodField(read_only=True, source='publish')
    # publish = serializers.CharField()
    def get_publish(self, instance):
        print('instance:', instance)
        fields_list = ['name', 'email']
        fields_dict = {}
        for field in fields_list:
            if hasattr(instance.publish, field):
                fields_dict[field] = getattr(instance.publish, field)
        return fields_dict
    '''
    # 這裡不做任何格外的校驗處理, 只是看看校驗成功以後的結果
    def validated_price(self, data):
        # print('data:', data)
        return data

    # 這裡也是
    def validate(self, validate_data):
        # print('validate validate_data:', validate_data)
        return validate_data

    class Meta:
        model = Book
        # fields = ['id', 'name', ...]
        # exclude = ['id']
        fields = '__all__'
        extra_kwargs = {
            'price': {'max_value': 10000, 'min_value': 0}
        }

    def create(self, validated_data):
        publish_dict = validated_data.pop('publish')
        publish_obj = Publish.objects.create(**dict(publish_dict))

        Book.objects.create(**validated_data, publish=publish_obj)
        validated_data.update({'publish': publish_dict})

        return validated_data

    def update(self, instance, validated_data):
        print('validated_data:', validated_data)
        publish_dict = validated_data.pop('publish')
        Publish.objects.filter(book__pk=instance.pk).update(**publish_dict)

        Book.objects.filter(pk=instance.pk).update(**validated_data)
        validated_data.update({'publish': publish_dict})
        return validated_data

3) views.py#

Copy
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, DestroyModelMixin, UpdateModelMixin, CreateModelMixin, \
    RetrieveModelMixin

from .models import Book
from .serializer import BookModelSerializer


class BookCreateListView(CreateModelMixin, ListModelMixin, GenericAPIView):
    queryset = Book.objects
    serializer_class = BookModelSerializer

    def get(self, request):
        """
        list 的實現是 ListModelMixin 類中定義的 list 方法,
        並在 GenericAPIView 類中定義的的方法基礎之上實現查詢資料的功能
        """
        return self.list(request)

    def post(self, request):
        """
        create 的實現是 CreateModelMixin 類中定義的 create 方法,
        並在 GenericAPIView 類中定義的的方法基礎之上實現新增資料的功能
        """
        print('request.data:', request.data)
        return self.create(request)


class BookUpdateDestroyRetrieveView(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin, GenericAPIView):
    queryset = Book.objects
    serializer_class = BookModelSerializer

    def get(self, request, pk):
        """
        retrieve 的實現是 RetrieveModelMixin 類中定義的 retrieve 方法,
        並在 GenericAPIView 類中定義的的方法基礎之上實現查詢一條資料的功能
        """
        return self.retrieve(request, pk)

    def put(self, request, pk):
        """
        update 的實現是 UpdateModelMixin 類中定義的 update 方法,
        並在 GenericAPIView 類中定義的的方法基礎之上實現更新一條資料的功能
        """
        return self.update(request, pk)

    def delete(self, request, pk):
        """
        destroy 的實現是 DestroyModelMixin 類中定義的 destroy 方法,
        並在 GenericAPIView 類中定義的的方法基礎之上實現刪除一條資料的功能
        """
        return self.destroy(request, pk)

4) urls.py#

Copy
from django.conf.urls import url
from .views import BookUpdateDestroyRetrieveView
from .views import BookCreateListView

urlpatterns = [
    url(r'^books/$', BookCreateListView.as_view()),
    url(r'^books/(?P<pk>\d+)', BookUpdateDestroyRetrieveView.as_view()),
]

5) 總結#

Copy
提示: 以下的5種檢視擴充套件類必須和GenericAPIView連用
ListModelMixin     內部封裝了list方法, 實現了查詢所有資料
CreateModelMixin   內部封裝了create方法, 實現了新增資料
RetrieveModelMixin 內部封裝了retrieve方法, 實現了查詢一條資料
UpdateModelMixin   內部封裝了update方法, 實現了更新一條資料
DestroyModelMixin  內部封裝

四. GenericAPIView的9個檢視子類#

1) 9個GenericAPIView的檢視子類快速介紹#

Copy
提示: 以下都是基與對應的5種不同的檢視擴充套件類ModelMixin和GenericAPiView. 在原來的基礎之上剔除了對應的上一個節需要定義的重複的方法
CreateAPIView
DestroyAPIView
UpdateAPIView
ListAPIView
RetrieveAPIView
ListCreateAPIView
RetrieveDestroyAPIView
RetrieveUpdateDestroyAPIView
RetrieveUpdateAPIView

2) views.py#

Copy
from rest_framework.generics import GenericAPIView, \
    CreateAPIView, DestroyAPIView, UpdateAPIView, ListAPIView, RetrieveAPIView, \
    ListCreateAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView, RetrieveUpdateAPIView

class BookListCreateAPIView(ListCreateAPIView):
    queryset = Book
    serializer_class = BookModelSerializer


class BookRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
    queryset = Book
    serializer_class = BookModelSerializer

3) urls.py#

Copy
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^books/$', views.BookModelViewSet.as_view(actions={'get': 'list',})),
    url(r'^books/(?P<pk>\d+)',
        views.BookModelViewSet.as_view(actions={'get': 'retrieve', 'put': 'update',})),
]

五. ViewSetMixin#

1) views.py#

Copy
from rest_framework.viewsets import ViewSetMixin
class Book6View(ViewSetMixin,APIView): #一定要放在APIVIew前
    def get_all_book(self,request):
        print("xxxx")
        book_list = Book.objects.all()
        book_ser = BookSerializer(book_list, many=True)
        return Response(book_ser.data)

2) urls.py#

Copy
# 繼承ViewSetMixin的檢視類,路由可以改寫成這樣
path('books6/', views.Book6View.as_view(actions={'get': 'get_all_book'})),

3) 總結#

Copy
只要是基礎 ViewSetMixin 的類都可以修改檢視類中方法的呼叫名

六. ModelViewSet#

1) views.py#

Copy
from rest_framework.viewsets import ModelViewSet, ViewSetMixin, GenericViewSet, ViewSet, ReadOnlyModelViewSet

class BookModelViewSet(ModelViewSet):
    queryset = Book.objects
    serializer_class = BookModelSerializer

    # ModelViewSet -> GenericViewSet -> ViewSetMixin 因此繼承了ModelViewSet的檢視類, 也可以在檢視類中自定義方法, 只是路由中指定的要一致
    def get_all(self, request):
        return self.list(request)

2) urls.py#

Copy
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^books/$', views.BookModelViewSet.as_view(actions={'get': 'get_all', 'post': 'create'})),
    url(r'^books/(?P<pk>\d+)',
        views.BookModelViewSet.as_view(actions={'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]

3) 其他viewsets中的類#

Copy
1. GenericViewSet(
    ViewSetMixin, generics.GenericAPIView
)
2. ViewSet(
    ViewSetMixin, views.APIView
)
3. ReadOnlyModelViewSet(
    mixins.RetrieveModelMixin,
    mixins.ListModelMixin,
    GenericViewSet
)

4) 總結#

Copy
介紹: ModelViewSet繼承關係非常多, 通過一層層的繼承, 只需要很少的程式碼就能實現API的5中檢視介面
    它, 還可以和路由元件使用, 這樣就直接使用路由直接生成對應功能的路由介面, 以及在不指定路由的基礎之上預設幫我們書寫了路由中在as_view()類方法中新增的actions引數了

龐大的繼承可實現的功能:
    1. APIView提供的
        request封裝
        三段認證
        全域性異常處理
        response響應格式
    2. GenericAPIView提供的
        檢視類中宣告queryset 和 serializer_classes 類屬性
        self.get_object	()
        self.get_queryset()
        self.get_serializer()
    3. 五個基本檢視擴充套件類ModelMixin系列提供的
        self.list()
        self.create()
        self.update()
        self.retrieve()
        self.destroy()
    4. ViewSetMixin提供的
        修改路由中ac_view()類方法的actions引數
        如:
            路由配置: url(r'index/', BookView.as_view(actions={'get': 'get_list'}))
            檢視使用:
                class BookView(ViewSetMixin, APIView):
                    def get_list(self, request):
                        ...

七. 繼承關係流程圖#