""" 1)在序列化類中自定義字段,名字與model類中屬性名一致,就稱之為覆蓋操作 (覆蓋的是屬性的所有規則:extra_kwargs中指定的簡易規則、model字段提供的默認規則、數據庫唯一約束等哪些規則) 2)外鍵覆蓋字段用PrimaryKeyRelatedField來實現,可以做到只讀、只寫、可讀可寫三種形式 只讀:read_only=True 只寫:queryset=關聯表的queryset, write_only=True 可讀可寫:queryset=關聯表的queryset 3)當外界關聯的數據是多個時,需標識many=True條件 """
class BookModelSerializer(serializers.ModelSerializer): # 如何覆蓋外鍵字段 publish是別的表的主鍵 # publish = serializers.PrimaryKeyRelatedField(read_only=True) # 只讀 # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all(), write_only=True) # 只寫 # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all()) # 可讀可寫 publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all()) # queryset后面的就是綁定的外鍵對象 authors = serializers.PrimaryKeyRelatedField(queryset=models.Author.objects.all(), many=True) class Meta: model = models.Book fields = ('name', 'price', 'image', 'publish', 'authors')
""" def __init__(self, instance=None, data=empty, **kwargs): pass instance:是要被賦值對象的 - 對象類型數據賦值給instance data:是要被賦值數據的 - 請求來的數據賦值給data kwargs:內部有三個屬性:many、partial、context many:操作的對象或數據,是單個的還是多個的 partial:在修改需求時使用,可以將所有校驗字段required校驗規則設置為False context:用於視圖類和序列化類直接傳參使用 """ # 常見使用 # 單查接口 UserModelSerializer(instance=user_obj) # 群查接口 UserModelSerializer(instance=user_query, many=True) # 單增接口,request.data是字典 UserModelSerializer(data=request.data) # 群增接口,request.data是列表 UserModelSerializer(data=request.data, many=True) # 單整體改接口,request.data是字典 UserModelSerializer(instance=user_obj, data=request.data) # 群整體改接口,request.data是列表,且可以分離出pks,轉換成user_queryset UserModelSerializer(instance=user_queryset, data=request.data, many=True) # 單局部改接口,request.data是字典 UserModelSerializer(instance=user_obj, data=request.data, partial=True) # 群局部改接口,request.data是列表,且可以分離出pks,轉換成user_queryset UserModelSerializer(instance=user_queryset, data=request.data, partial=True, many=True) # 刪接口,用不到序列化類
""" 1)初始化序列化類,設置partial=True可以將所有反序列化字段的 required 設置為 False(提供就校驗,不提供不校驗),可以運用在局部修改接口 2)初始化序列化類,設置context={...},在序列化類操作self.context,實現視圖類和序列化類數據互通 3)只有要完成資源的群改這種特殊需求時,才需要自定義ListSerializer綁定給自定義的ModelSerializer,重寫update方法,來完成需求 """
# 基類:是抽象的(不會完成數據庫遷移),目的是提供共有字段的 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)
url(r'^books/$', views.BookAPIView.as_view()), url(r'^books/(?P<pk>\d+)/$', views.BookAPIView.as_view()),
from rest_framework import serializers from . import models # 群增群改輔助類(了解) class BookListSerializer(serializers.ListSerializer): # 群增是通過ListSserializer完成的,但是源碼只寫了create方法,update群改方法需要自己寫 """ 1)create群增方法不需要重新 2)update群改方法需要重寫,且需要和views中處理request.data的邏輯配套使用 3)self.child就代表該ListSerializer類綁定的ModelSerializer類 BookListSerializer的self.child就是BookModelSerializer """ # 重新update方法 def update(self, queryset, validated_data_list): return [ # self.child 自定義的屬性(源碼內部定義ModelSerializer中的ListSerializer類有child屬性,對應外部的ModelSerializer,此處也就是BookModelSerilizer對象,調用其內部的update方法) 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 # 改完繼承ListSerializer的類后,配置。源碼就是先查詢list_serializer_class,沒有再用自己的。就能用自己寫的群改方法 model = models.Book fields = ('name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list') # fields = ('name', 'price', 'image', 'publish', 'authors', 'abc') extra_kwargs = { 'image': { 'read_only': True, }, 'publish': { # 系統原有的外鍵字段,要留給反序列化過程使用,序列化外鍵內容,用@property自定義 'write_only': True, }, 'authors': { 'write_only': True, }, } # 需求:內外傳參 # 1)在鈎子函數中,獲得請求請求對象request 視圖類 => 序列化類 # 2)序列化鈎子校驗過程中,也會產生一些數據,這些數據能不能傳遞給外界使用:序列化類 => 視圖類 # 序列化類的context屬性,被視圖類與序列化類共享 def validate(self, attrs): print(self.context) # 可以獲得視圖類在初始化序列化對象時傳入的context # self.context.update({'a': 10}) # 序列化類內部更新context,傳遞給視圖類 return attrs
from rest_framework.views import APIView from . import models, serializers from .response import APIResponse # 六個必備接口:單查、群查、單增、單刪、單整體改(了解)、單局部改 # 四個額外接口:群增、群刪、群整體改、群局部改 class BookAPIView(APIView): # 單查群查 """ 單查:接口:/books/(pk)/ 群查:接口:/books/ """ def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: obj = models.Book.objects.filter(is_delete=False, pk=pk).first() serializer = serializers.BookModelSerializer(instance=obj) return APIResponse(result=serializer.data) else: queryset = models.Book.objects.filter(is_delete=False).all() serializer = serializers.BookModelSerializer(instance=queryset, many=True) return APIResponse(results=serializer.data) # 單增群增 """ 單增:接口:/books/ 數據:dict 群增:接口:/books/ 數據:list """ def post(self, request, *args, **kwargs): # 如何區別單增群增:request.data是{}還是[] if not isinstance(request.data, list): # 單增 serializer = serializers.BookModelSerializer(data=request.data) serializer.is_valid(raise_exception=True) # 如果校驗失敗,會直接拋異常,返回給前台 obj = serializer.save() # 為什么要將新增的對象重新序列化給前台:序列化與反序列化數據不對等 return APIResponse(result=serializers.BookModelSerializer(obj).data, http_status=201) else: # 群增 serializer = serializers.BookModelSerializer(data=request.data, many=True) serializer.is_valid(raise_exception=True) # 如果校驗失敗,會直接拋異常,返回給前台 objs = serializer.save() # 為什么要將新增的對象重新序列化給前台:序列化與反序列化數據不對等 return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data, http_status=201) # 單刪群刪 """ 單刪:接口:/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 APIResponse(1, '數據有誤') if rows: # 只要有受影響的行,就代表刪除成功 return APIResponse(0, '刪除成功') return APIResponse(2, '刪除失敗') # 單整體改群整體改 """ 單整體改:接口:/books/(pk)/ 數據:dict 群整體改:接口:/books/ 數據:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]} """ def put(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: # 單 try: instance = models.Book.objects.get(is_delete=False, pk=pk) except: return APIResponse(1, 'pk error', http_status=400) # 序列化類同時賦值instance和data,代表用data重新更新instance => 修改 serializer = serializers.BookModelSerializer(instance=instance, data=request.data) serializer.is_valid(raise_exception=True) obj = serializer.save() return APIResponse(result=serializers.BookModelSerializer(obj).data) else: # 群 """ 分析request.data數據 [{'pk':1, 'name': '', 'publish': 1, 'authors': [1, 2]}, ...] 1)從 request.data 中分離出 pks 列表 2)pks中存放的pk在數據庫中沒有對應數據,或者對應的數據已經被刪除了,這些不合理的pk要被剔除 3)pks最終轉換得到的 objs 列表長度與 request.data 列表長度不一致,就是數據有誤 """ 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 APIResponse(1, '數據有誤', http_status=400) serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True) serializer.is_valid(raise_exception=True) objs = serializer.save() return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data) # 單局部改群局部改 """ 單局部改:接口:/books/(pk)/ 數據:dict 群局部改:接口:/books/ 數據:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]} """ def patch(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: # 單 try: instance = models.Book.objects.get(is_delete=False, pk=pk) except: return APIResponse(1, 'pk error', http_status=400) # partial=True就是將所有反序列化字段的 required 設置為 False(提供就校驗,不提供不校驗) serializer = serializers.BookModelSerializer(instance=instance, data=request.data, partial=True) serializer.is_valid(raise_exception=True) obj = serializer.save() return APIResponse(result=serializers.BookModelSerializer(obj).data) else: # 群 pks = [] try: # 只要不是要求的標准數據,一定會在下方三行代碼某一行拋出異常 for dic in request.data: pks.append(dic.get('pk')) objs = models.Book.objects.filter(is_delete=False, pk__in=pks) assert len(objs) == len(request.data) # 兩個列表長度必須一致 except: return APIResponse(1, '數據有誤', http_status=400) serializer = serializers.BookModelSerializer( instance=objs, data=request.data, many=True, partial=True, context={'request': request} # 初始化時,對context賦值,將視圖類中數據傳遞給序列化類 ) serializer.is_valid(raise_exception=True) objs = serializer.save() print(serializer.context) # 在完成序列化類校驗后,可以重新拿到序列化類內部對context做的值更新 return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data)
""" 視圖基類:APIView、GenericAPIView 視圖工具類:mixins包下的五個類(六個方法) 工具視圖類:generics包下的所有GenericAPIView的子類 視圖集:viewsets包下的類 """ """ GenericAPIView基類(基本不會單獨使用,了解即可,但是是高級視圖類的依賴基礎) 1)GenericAPIView繼承APIView,所有APIView子類寫法在繼承GenericAPIView時可以保持一致 2)GenericAPIView給我們提供了三個屬性 queryset、serializer_class、lookup_field 3)GenericAPIView給我們提供了三個方法 get_queryset、get_serializer、get_obj """ """ mixins包存放了視圖工具類(不能單獨使用,必須配合GenericAPIView使用) CreateModelMixin:單增工具類 create方法 ListModelMixin:群查工具類 list方法 RetrieveModelMixin:單查工具類 retrieve方法 UpdateModelMixin:單整體局部改工具類 update方法 DestroyModelMixin:單刪工具類 destory方法 """ """ generics包下的所有GenericAPIView的子類(就是繼承GenericAPIView和不同mixins下的工具類的組合) 1)定義的視圖類,繼承generics包下已有的特點的GenericAPIView子類,可以在只初始化queryset和serializer_class兩個類屬性后,就獲得特定的功能 2)定義的視圖類,自己手動繼承GenericAPIView基類,再任意組合mixins包下的一個或多個工具類,可以實現自定義的工具視圖類,獲得特定的功能或功能們 注: i)在這些模式下,不能實現單查群查共存(可以加邏輯區分,也可以用視圖集知識) ii)DestroyModelMixin工具類提供的destory方法默認是從數據庫中刪除數據,所以一般刪除數據的需求需要自定義邏輯 """
from django.conf.urls import url from . import views urlpatterns = [ # ... url(r'^v1/books/$', views.BookV1APIView.as_view()), url(r'^v1/books/(?P<pk>\d+)/$', views.BookV1APIView.as_view()), url(r'^v2/books/$', views.BookV2APIView.as_view()), url(r'^v2/books/(?P<pk>\d+)/$', views.BookV2APIView.as_view()), url(r'^v3/books/$', views.BookV3APIView.as_view()), url(r'^v3/books/(?P<pk>\d+)/$', views.BookV3APIView.as_view()), ]
# ----------------------------- 過渡寫法:了解 ----------------------------- from rest_framework.generics import GenericAPIView class BookV1APIView(GenericAPIView): # 將數據和序列化提示為類屬性,所有的請求方法都可以復用 queryset = models.Book.objects.filter(is_delete=False).all() # orm有優化機制(懶加載),一次只查21條數據,不夠了,再往后查21條 serializer_class = serializers.BookModelSerializer lookup_field = 'pk' # 可以省略,源碼默認是pk,與url有名分組對應的。如果url有名分組是name等,此處就要改成'name' # 群查 def get(self, request, *args, **kwargs): # queryset = models.Book.objects.filter(is_delete=False).all() # => 方法+屬性兩行代碼 queryset = self.get_queryset()# serializer = serializers.BookModelSerializer(instance=queryset, many=True) # => 方法+屬性兩行代碼 serializer = self.get_serializer(instance=queryset, many=True) return APIResponse(results=serializer.data) # 單查 # def get(self, request, *args, **kwargs): # obj = self.get_object() # 通過lookup_field設定的字段內容,和get_queryset,去庫中拿對應的queryset對象,源碼已經寫好了 # serializer = self.get_serializer(obj) # return APIResponse(results=serializer.data) # 單增 def post(self, request, *args, **kwargs): # serializer = serializers.BookModelSerializer(data=request.data) serializer = self.get_serializer(data=request.data) # 同樣的步驟多了,好處就來了 serializer.is_valid(raise_exception=True) obj = serializer.save() return APIResponse(result=self.get_serializer(obj).data, http_status=201) # ----------------------------- 過渡寫法:了解 ----------------------------- from rest_framework.generics import GenericAPIView from rest_framework import mixins class BookV2APIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.CreateModelMixin): queryset = models.Book.objects.filter(is_delete=False).all() serializer_class = serializers.BookModelSerializer # 單查 def get(self, request, *args, **kwargs): # obj = self.get_object() # serializer = self.get_serializer(obj) # return APIResponse(results=serializer.data) # return self.retrieve(request, *args, **kwargs) # 內部完成上面三條功能,直接返回結果 response = self.retrieve(request, *args, **kwargs) # 獲取返回的結果,通過獲得里面的data,通過二次封裝的response進行返回 return APIResponse(result=response.data) # 單增 def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) # ----------------------------- 開發寫法:常用 ----------------------------- from rest_framework.generics import RetrieveAPIView class BookV3APIView(RetrieveAPIView): queryset = models.Book.objects.filter(is_delete=False).all() serializer_class = serializers.BookModelSerializer # 單查 pass
總結
""" 1)序列化類初始化各參數含義: instance:被賦值對象 data:被賦值數據 many:表示操作的對象或數據是否是多個 partial:修改校驗字段均為選填 context:視圖類與序列化類間傳參 查:instance | instance, many 增:data 改:instance, data | instance, data, partial 刪:orm操作 2)覆蓋外鍵字段(認證規則) 字段 = serializers.PrimaryKeyRelatedField(queryset) 字段 = serializers.PrimaryKeyRelatedField(read_only) 字段 = serializers.PrimaryKeyRelatedField(queryset, write_only) 3)十大接口: 群增群改需要ListSerializer類輔助,在ModelSerializer的Meta list_serializer_class配置中建立關聯,create方法可以繼承,update方法需要重寫 4)視圖家族: APIView:基類 GenericAPIView:基類 i)繼承APIView的,所以擁有APIView的全部 ii)三個類屬性:queryset、serializer_class、lookup_field iii)三個方法:get_queryset、get_serializer、get_object 過渡:單獨繼承GenericAPIView類的視圖類,需要自己定義請求方法如get,還需要屬性方法體完成請求 mixins包:工具類 - eg:RetrieveModelMixin:retrieve retrieve、list、create、update、partial_update、destory 過渡:因為上方六個方法體都是依賴與GenericAPIView類的,所以必須配合GenericAPIView類使用 generics包:工具視圖類 i)系統完成的GenericAPIView與mixins包下工具類的不同組合 只需要配置三個類屬性 ii)自己完成GenericAPIView與mixins包下工具類的不同組合 需要配置三個類屬性,需要書寫請求方法,如post,內部直接調用self.create方法 iii)自己繼承一堆generics包下的工具視圖,完成組合 只需要配置三個類屬性,但是單查、群查不能共存 """