Django rest_framework實現增刪改查接口
本文使用Django的rest_framework框架的ModelSerializer模塊和ListSerializer模塊實現單查群查、單刪群刪、單增群增、單改群改接口。
寫接口前的知識准備
__all__的使用方法
在默認情況下,如果使用“from 模塊名 import *”這樣的語句來導入模塊,程序會導入該模塊中所有不以下畫線開頭的成員(包括變量、函數和類)。但在一些場景中,我們並不希望每個成員都被暴露出來供外界使用,此時可借助於模塊的 __all__ 變量,將變量的值設置成一個列表,只有該列表中的成員才會被暴露出來。
以下划線_開頭的變量在導包時用“from 模塊名 import *”是無法導入的,可以通過__all__來指定導入的_變量。
例如,下面程序定義了一個包含 __all__ 變量的模塊:
'''測試__all__變量的模塊'''
def hello():
print("Hello, Python")
def world():
print("Pyhton World is funny")
def test():
print('--test--')
# 定義__all__變量,指定默認只導入hello和world兩個成員__all__ = ['hello', 'world']
上面的 __all__ 變量指定該模塊默認只被導入 hello 和 world 兩個成員。下面程序示范了模塊中 __all__ 變量的用處:
# 導入all_module模塊內所有成員from all_module import *hello()world()test() # 會提示找不到test()函數
上面第 2 行代碼使用“from all_module import *”導入了 all_module 模塊下所有的成員。由於該模塊包含了 __all__ 變量,因此該語句只導入 __all__ 變量所列出的成員。
序列化類配置
內嵌類Meta的三個屬性介紹:
fields = ['name', 'address', 'books']或者" __all__"fields可以指定字段進行序列化、反序列化,以及連表查詢時可以查詢到的字段。
exclude = ['name']指查詢的時候不包括該字段。
depth = 1 值代表深度次數,深度查詢指的是當一張表有關聯的表時,在查詢查自己的表時順便將關聯的表的內容也查出來,如果被深度查詢的外鍵采用__all__,會將所關聯表的所有字段都查出來。如果將深度值設置為2則將所關聯表的其他關聯的表也查出來,就這樣一層一層深入,已經查過的表就不查了,所以不會出現死循環。
class BookModelSerializer(serializers.ModelSerializer):
# 配置depth:自動深度查詢的是關聯表的所有字段,數據量太多
class Meta:
list_serializer_class = BookListSerializer
model = models.Book
fields = ['name', 'price', 'publish', 'authors', 'publish_info', 'author_list']
extra_kwargs = {
'publish': {
'write_only': True
},
'authors': {
'write_only': True
}
}
class PublishModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Publish
fields = ['name', 'address', 'books']
# 了解配置
# fields = '__all__'
# exclude = ['name']
# depth = 2 # 自動深度,值代表深度次數,但是被深度的外鍵采用__all__,顯示所以字段
Response二次封裝
對rest_framework的Response類進行二次封裝可以按照我們自己的要求去定義response的功能。
from rest_framework.response import Response
class APIResponse(Response):
def __init__(self, status=0, msg='ok', results=None, http_status=None,
headers=None, exception=False, content_type=None, **kwargs):
# 將status、msg、results、kwargs格式化成data
data = {
'status': status,
'msg': msg,
}
# results只要不為空都是數據:False、0、'' 都是數據 => 條件不能寫if results
if results is not None:
data['results'] = results
# 將kwargs中額外的k-v數據添加到data中
data.update(**kwargs)
super().__init__(data=data, status=http_status, headers=headers, exception=exception, content_type=content_type)
連表深度查詢
連表深度查詢的方式有三種:
第一種:子序列化:必須有子序列化類配合,不能反向查詢
第二種:配置depth:自動深度查詢的是關聯表的所有字段,數據量太多
第三種:插拔式@property:名字不能與外鍵名同名(最常用方式)
下面介紹插拔式:
如果書要連表查詢出版社,首先在書的模板類中定義@property的方法如下:
@property
def publish_info(self): # 單個數據
return {
'name': self.publish.name,
'address': self.publish.address,
}#return出我們需要查詢出來的第二張表的字段和數據,前提方法名不能和外鍵字段名重名
然后在序列化類BookModelSerializer中的meta的fields屬性中添加上面定義的方法名,這樣就可以實現連表查詢。
class BookModelSerializer(serializers.ModelSerializer):
# 外鍵字段默認顯示的是外鍵值(int類型),不會自己進行深度查詢
# 深度查詢方式:
# 1)子序列化:必須有子序列化類配合,不能反序列化了
# 2)配置depth:自動深度查詢的是關聯表的所有字段,數據量太多
# 3)插拔式@property:名字不能與外鍵名同名
class Meta:
# ModelSerializer默認配置了ListSerializer輔助類,幫助完成群增群改
# list_serializer_class = serializers.ListSerializer
# 如果只有群增,是不需要自定義配置的,但要完成群改,必須自定義配置
list_serializer_class = BookListSerializer
model = models.Book
fields = ['name', 'price', 'publish', 'authors', 'publish_info', 'author_list']
插拔式還可以在模型類中導入所鏈表的序列化的數據達到連查的目的如:
@property
def publish_info(self): # 單個數據
from .serializers import PublishModelSerializer
return PublishModelSerializer(self.publish).data
然后在序列化類BookModelSerializer中的meta的fields屬性中添加上面定義的方法名。
單查群查接口
class BookAPIView(APIView):
# 單查群查
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
book_obj = models.Book.objects.filter(is_delete=False, pk=pk).first()
book_ser = serializers.BookModelSerializer(book_obj)
else:
book_query = models.Book.objects.filter(is_delete=False).all()
book_ser = serializers.BookModelSerializer(book_query, many=True)
return APIResponse(results=book_ser.data)
# return Response(data=book_ser.data)
單刪群刪接口
def delete(self, request, *args, **kwargs):
"""
單刪:接口:/books/(pk)/ 數據:空
群刪:接口:/books/ 數據:[pk1, ..., pkn]
邏輯:修改is_delete字段,修改成功代表刪除成功,修改失敗代表刪除失敗
"""
pk = kwargs.get('pk')
if pk:
pks = [pk] # 將單刪格式化成群刪一條
else:
pks = request.data # 群刪
try: # 數據如果有誤,數據庫執行會出錯
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(1, '刪除失敗')
單增,群增接口
def post(self, request, *args, **kwargs):
"""
單增:接口:/books/ 數據:{...}
群增:接口:/books/ 數據:[{...}, ..., {...}]
邏輯:將數據給系列化類處理,數據的類型關系到 many 屬性是否為True
"""
if isinstance(request.data, dict):
many = False
elif isinstance(request.data, list):
many = True
else:
return Response(data={'detail': '數據有誤'}, status=400)
book_ser = serializers.BookModelSerializer(data=request.data, many=many)
book_ser.is_valid(raise_exception=True)
book_obj_or_list = book_ser.save()
return APIResponse(results=serializers.BookModelSerializer(book_obj_or_list, many=many).data)
整體單改群改接口
def put(self, request, *args, **kwargs):
"""
單改:接口:/books/(pk)/ 數據:{...}
群增:接口:/books/ 數據:[{pk, ...}, ..., {pk, ...}]
邏輯:將數據給系列化類處理,數據的類型關系到 many 屬性是否為True
"""
pk = kwargs.get('pk')
if pk: # 單改
try:
# 與增的區別在於,需要明確被修改的對象,交給序列化類
book_instance = models.Book.objects.get(is_delete=False, pk=pk)
except:
return Response({'detail': 'pk error'}, status=400)
book_ser = serializers.BookModelSerializer(instance=book_instance, data=request.data)
book_ser.is_valid(raise_exception=True)
book_obj = book_ser.save()
return APIResponse(results=serializers.BookModelSerializer(book_obj).data)
else: # 群改
# 分析(重點):
# 1)數據是列表套字典,每個字典必須帶pk,就是指定要修改的對象,如果有一條沒帶pk,整個數據有誤
# 2)如果pk對應的對象已被刪除,或是對應的對象不存在,可以認為整個數據有誤(建議),可以認為將這些錯誤數據拋出即可
request_data = request.data
try:
pks = []
for dic in request_data:
pk = dic.pop('pk') # 解決分析1,沒有pk pop方法就會拋異常
pks.append(pk)
book_query = models.Book.objects.filter(is_delete=False, pk__in=pks).all()
if len(pks) != len(book_query):
raise Exception('pk對應的數據不存在')
except Exception as e:
return Response({'detail': '%s' % e}, status=400)
book_ser = serializers.BookModelSerializer(instance=book_query, data=request_data, many=True)
book_ser.is_valid(raise_exception=True)
book_list = book_ser.save()
return APIResponse(results=serializers.BookModelSerializer(book_list, many=True).data)
局部修改數據
# 局部單改群改
def patch(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk: # 單改
try:
book_instance = models.Book.objects.get(is_delete=False, pk=pk)
except:
return Response({'detail': 'pk error'}, status=400)
# 設置partial=True的序列化類,參與反序列化的字段,都會置為選填字段
# 1)提供了值得字段發生修改。
# 2)沒有提供的字段采用被修改對象原來的值
# 設置context的值,目的:在序列化完成自定義校驗(局部與全局鈎子)時,可能需要視圖類中的變量,如請求對象request
# 可以通過context將其傳入,在序列化校驗方法中,self.context就能拿到傳入的視圖類中的變量
book_ser = serializers.BookModelSerializer(instance=book_instance, data=request.data, partial=True, context={'request': request})
book_ser.is_valid(raise_exception=True)
book_obj = book_ser.save()
return APIResponse(results=serializers.BookModelSerializer(book_obj).data)
else: # 群改
request_data = request.data
try:
pks = []
for dic in request_data:
pk = dic.pop('pk')
pks.append(pk)
book_query = models.Book.objects.filter(is_delete=False, pk__in=pks).all()
if len(pks) != len(book_query):
raise Exception('pk對應的數據不存在')
except Exception as e:
return Response({'detail': '%s' % e}, status=400)
book_ser = serializers.BookModelSerializer(instance=book_query, data=request_data, many=True, partial=True)
book_ser.is_valid(raise_exception=True)
book_list = book_ser.save()
return APIResponse(results=serializers.BookModelSerializer(book_list, many=True).data)
視圖給序列化傳參
def patch(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk: # 單改
try:
book_instance = models.Book.objects.get(is_delete=False, pk=pk)
except:
return Response({'detail': 'pk error'}, status=400)
# 設置partial=True的序列化類,參與反序列化的字段,都會置為選填字段
# 1)提供了值得字段發生修改。
# 2)沒有提供的字段采用被修改對象原來的值
# 設置context的值,目的:在序列化完成自定義校驗(局部與全局鈎子)時,可能需要視圖類中的變量,如請求對象request
# 可以通過context將其傳入,在序列化校驗方法中,self.context就能拿到傳入的視圖類中的變量
book_ser = serializers.BookModelSerializer(instance=book_instance,
data=request.data,
partial=True,
context={'request': request})
book_ser.is_valid(raise_exception=True)
book_obj = book_ser.save()
return APIResponse(results=serializers.BookModelSerializer(book_obj).data)