第十五週
阿新 • • 發佈:2020-07-27
一. 前言#
Django REST framwork 提供的檢視的主要作用
Copy1. 控制序列化器的執行(檢驗、儲存、轉換資料)
2. 控制資料庫查詢的執行
二. 兩個檢視基類#
兩個檢視基類: APIView, GenericAPIView
1. APIView#
1) models.py#
Copyclass 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檔案#
Copyfrom 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#
Copyfrom 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) 總結#
Copy1. 繼承關係: 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#
Copyfrom 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檔案#
Copyfrom 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#
Copyfrom 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) 總結#
Copy1. 繼承GenericAPIView的檢視類, 當需要修改介面指定操作的模型類, 以及序列化的模型類直接在檢視類中修改即可了.其他都不用動就可以實現偷換模型類以及序列化類
2. GenericAPIView提供了3個主要的方法
self.get_object() 獲取單條資料
self.get_queryset() 獲取多條資料
self.serializer_classes(引數同原來即可) 執行自定義檢視類中定義的序列化類進行序列化, 將ORM物件的資料轉換成python的物件
3. GenericAPIView提供了可修改路由又名分組的指定不再是預設的pk
自定義的檢視類
三. 五個檢視擴充套件類#
1) models.py#
Copyfrom 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檔案#
Copyfrom 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#
Copyfrom 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#
Copyfrom 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#
Copyfrom 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#
Copyfrom 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#
Copyfrom 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#
Copyfrom 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#
Copyfrom 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中的類#
Copy1. 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):
...