准備工作:
models.py

from django.db import models # 基類:是抽象的(不會完成數據庫遷移),目的是提供共有字段的 class BaseModel(models.Model): is_delete = models.BooleanField(default=False) updated_time = models.DateTimeField(auto_now_add=True) class Meta: abstract = True # 必須完成該配置 class Book(BaseModel): name = models.CharField(max_length=64) price = models.DecimalField(max_digits=5, decimal_places=2, null=True) image = models.ImageField(upload_to='img', default='img/default.png') publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING) authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False) @property # @property字段默認就是read_only,且不允許修改 def publish_name(self): return self.publish.name @property # 自定義序列化過程 def author_list(self): temp_author_list = [] for author in self.authors.all(): author_dic = { "name": author.name } try: author_dic['phone'] = author.detail.phone except: author_dic['phone'] = '' temp_author_list.append(author_dic) return temp_author_list class Publish(BaseModel): name = models.CharField(max_length=64) class Author(BaseModel): name = models.CharField(max_length=64) class AuthorDetail(BaseModel): phone = models.CharField(max_length=11) author = models.OneToOneField(to=Author, related_name='detail', db_constraint=False, on_delete=models.CASCADE)
from rest_framework import serializers from . import models # 只有在資源需要提供群改,才需要定義ListSerializer,重寫update方法 class BookListSerializer(serializers.ListSerializer): def update(self, queryset, validated_data_list): return [ self.child.update(queryset[index], validated_data) for index, validated_data in enumerate(validated_data_list) ] class BookModelSerializer(serializers.ModelSerializer): class Meta: list_serializer_class = BookListSerializer model = models.Book fields = ['name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list'] extra_kwargs = { 'publish': { 'write_only': True }, 'authors': { 'write_only': True } }
from rest_framework.generics import GenericAPIView from rest_framework import mixins from . import models,serializers from rest_framework.response import Response
# 十大接口 # 1) 單查、群查、單增、單整體改、單局部改都可以直接使用 # 2) 單刪不能直接使用,因為默認提供的功能是刪除數據庫數據,不是我們自定義的is_delete字段值的修改 # 3) 除了群查以外的接口,都要自己實現 class BookV1APIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin): queryset = models.Book.objects.filter(is_delete=False).all() serializer_class = serializers.BookModelSerializer def get(self, request, *args, **kwargs): if 'pk' in kwargs: return self.retrieve(request, *args, **kwargs) # 單查 # queryset = models.Book.objects.filter(is_delete=False).all() # 注:給序列化類context賦值{'request': request},序列化類就可以自動補全后台圖片鏈接 # serializer = serializers.BookModelSerializer(queryset, many=True, context={'request': request}) # return Response(serializer.data) return self.list(request,*args,**kwargs) # 群查 def post(self, request, *args, **kwargs):
if not isinstance(request.data,list): return self.create(request, *args, **kwargs) # 單增 serializer = self.get_serializer(data=request.data,many=True) serializer.is_valid(raise_exception=True) objs = serializer.save() return Response(serializer.BookModelSerializer(objs,many=True).data,status=200) # 群增 """ 單刪:接口:/books/(pk)/ 群刪:接口:/books/ 數據:[pk1, ..., pkn] """ def delete(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: pks = [pk] # 將單刪偽裝成群刪一條 else: pks = request.data # 群刪的數據就是群刪的主鍵們 try: # request.data可能提交的是亂七八糟的數據,所以orm操作可能會異常 rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True) except: return Response(status=400) if rows: # 只要有受影響的行,就代表刪除成功 return Response(status=204) return Response(status=400) def put(self,request,*args,**kwargs): # 單整體改 if 'pk' in kwargs: return self.update(request,*args,**kwargs) # 群整體改 pks = [] try: # 只要不是要求的標准數據,一定會在下方四行代碼某一行拋出異常 for dic in request.data: pks.append(dic.pop('pk')) objs = models.Book.objects.filter(is_delete=False, pk__in=pks) assert len(objs) == len(request.data) # 兩個列表長度必須一致 except: return Response(status=400) # 序列化類同時賦值instance和data,代表用data重新更新instance => 修改 serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True) serializer.is_valid(raise_exception=True) objs = serializer.save() # 為什么要將新增的對象重新序列化給前台,因為序列化與反序列化數據不對等 return Response(serializers.BookModelSerializer(objs, many=True).data) def patch(self,request,*args,**kwargs): # 單局部改 if 'pk' in kwargs: return self.partial_update(request,*args,**kwargs) # 群局體改 pks = [] try: for dic in request.data: pks.append(dic.pop('pk')) objs = models.Book.objects.filter(is_delete=False, pk__in=pks) assert len(objs) == len(request.data) except: return Response(status=400) # partial=True就是將所有反序列化字段的 required 設置為 False(提供就校驗,不提供不校驗) serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True,partial=True) serializer.is_valid(raise_exception=True) objs = serializer.save() return Response(serializers.BookModelSerializer(objs, many=True).data)
views.py
''' 六大基礎接口 1)直接繼承generics包下的工具視圖類,可以完成六大基礎接口 2)單查群查不能共存 3)單刪一般會重寫 ''' from rest_framework import generics class BookV2APIView(generics.ListAPIView, generics.RetrieveAPIView, generics.CreateAPIView, generics.UpdateAPIView, generics.DestroyAPIView): queryset = models.Book.objects.filter(is_delete=False).all() serializer_class = serializers.BookModelSerializer def get(self, request, *args, **kwargs): if 'pk' in kwargs: return self.retrieve(request, *args, **kwargs) return self.list(request, *args, **kwargs)
""" ViewSetMixin類存在理由推導 1)工具視圖類,可以完成六大基礎接口,唯一缺點是單查與群查接口無法共存 (只配置queryset、serializer_class、lookup_field) 2)不能共存的原因:RetrieveAPIView和ListAPIView都是get方法,不管帶不帶pk的get請求,只能映射給一個get方法 3)能不能修改映射關系: 比如將/books/的get請求映射給list方法, 將/books/(pk)/的get請求映射給retrieve方法, 甚至可以隨意自定義映射關系 """ """ 繼承視圖集的視圖類的as_view都是走ViewSetMixin類的,核心源碼分析 @classonlymethod def as_view(cls, actions=None, **initkwargs): # ... # 沒有actions,也就是調用as_view()沒有傳參,像as_view({'get': 'list'}) if not actions: raise TypeError("The `actions` argument must be provided when " "calling `.as_view()` on a ViewSet. For example " "`.as_view({'get': 'list'})`") # ... # 請求來了走view函數 def view(request, *args, **kwargs): # ... # 解析actions,修改 請求分發 - 響應函數 映射關系 self.action_map = actions for method, action in actions.items(): # method:get | action:list handler = getattr(self, action) # 從我們視圖類用action:list去反射,所以handler是list函數,不是get函數 setattr(self, method, handler) # 將get請求對應list函數,所以在dispath分發請求時,會將get請求分發給list函數 # ... # 通過視圖類的dispatch完成最后的請求分發 return self.dispatch(request, *args, **kwargs) # ... # 保存actions映射關系,以便后期使用 view.actions = actions return csrf_exempt(view) """
視圖集的使用總結
1)可以直接繼承ModelViewSet,實現六大繼承接口(是否重寫destroy方法,或其他方法,根據需求決定) 2)可以直接繼承ReadOnlyModelViewSet,實現只讀需求(只有單查群查) 3)繼承ViewSet類,與Model類關系不是很密切的接口:登錄的post請求,是查詢操作;短信驗證碼發生接口,借助第三方平台 4)繼承GenericViewSet類,就代表要配合mixins包,自己完成任意組合 5)繼承以上4個視圖集任何一個,都可以與路由as_view({映射})配合,完成自定義請求響應方法
url(r'^v3/books/$', views.BookV3APIView.as_view( {'get': 'list', 'post': 'create', 'delete': 'multiple_destroy'} )), url(r'^v3/books/(?P<pk>\d+)/$', views.BookV3APIView.as_view( {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'} )),
from rest_framework.viewsets import ModelViewSet from rest_framework.response import Response class BookV3APIView(ModelViewSet): queryset = models.Book.objects.filter(is_delete=False).all() serializer_class = serializers.BookModelSerializer # 可以在urls.py中as_view({'get': 'my_list'})自定義請求映射 def my_list(self, request, *args, **kwargs): return Response('ok') # 需要完成字段刪除,不是重寫delete方法,而是重寫destroy方法 def destroy(self, request, *args, **kwargs): pk = kwargs.get('pk') models.Book.objects.filter(is_delete=False, pk=pk).update(is_delete=True) return Response(status=204) # 群刪接口 def multiple_destroy(self, request, *args, **kwargs): try: models.Book.objects.filter(is_delete=False, pk__in=request.data).update(is_delete=True) except: return Response(status=400) return Response(status=204)