這篇文章主要介紹兩種方式實現(xiàn)批量操作
,
一種是使用 Django restframework提供的裝飾器action缩赛,可以更具實際情況擴(kuò)展默認(rèn)的增刪改查操作璧亮,擴(kuò)展性很好乖订;另外一種是使用第三方模塊 djangorestframework-bulk
,這個模塊簡化了我們對于 對象本身增刪改查的批量化操作
,各有優(yōu)缺點贞让。實際工作中選擇合適的就好周崭。
本次分享中使用到的model模型
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32, unique=True, verbose_name='用戶名')
def __str__(self):
return self.name
# Student 是后來添加的,`User` 在使用restframework-bulk 模塊的時候存在問題
# 注意這里兩個模型存在的差異性喳张,你發(fā)現(xiàn)差在哪里了嗎续镇?
class Student(models.Model):
name = models.CharField(max_length=32)
def __str__(self):
return self.name
注意這里兩個模型存在的差異性,你發(fā)現(xiàn)差在哪里了嗎销部?
1摸航、使用action裝飾器自定義批量方法
為了分享方便,這里把serializers舅桩、viewset等都放到了一個文件(selfaction.py)
中去
from django.shortcuts import get_object_or_404
from app.models import Student, User
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
# serializers
class UserModelSerializer(ModelSerializer):
class Meta:
model = User
fields = '__all__'
# viewset
class UserModelViewSet(ModelViewSet):
serializer_class = UserModelSerializer
queryset = User.objects.all()
# 通過 many=True 改造原有的API, 使其支持批量創(chuàng)建
# 核心就是如果過來的數(shù)據(jù)是list酱虎,則使用many=True
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs.setdefault('context', self.get_serializer_context())
if isinstance(self.request.data, list):
return serializer_class(many=True, *args, **kwargs)
else:
return serializer_class(*args, **kwargs)
# 使用action自定義方法
# detail 是針對記錄是單條還是多條,這里是自定義批量操作擂涛,所以肯定是多條數(shù)據(jù)读串,detail=False
# 自定義批量刪除, 方法名就是url的一部分撒妈,比如這里的 /v1/user/multi_delete/?ids=1,2,3
@action(methods=['delete'], detail=False)
def multi_delete(self, request, *args, **kwargs):
ids = request.query_params.get('ids', None)
if not ids:
return Response(status=status.HTTP_404_NOT_FOUND)
for id in ids.split(','):
get_object_or_404(User, id=int(id)).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# 自定義批量更新操作恢暖,
# 更新涉及到 全部 和 局部 更新,所以這里的methods要支持 put(全部)和 patch(局部)
@action(methods=['put', 'PATCH'], detail=False)
def multi_update(self, request, *args, **kwargs):
print("args: ", args, " kwargs: ", kwargs)
partial = kwargs.pop('partial', None)
print("partial: ", partial)
# 報錯更新后的結(jié)果給前端
instances = []
if isinstance(self.request.data, list):
for item in request.data:
print(item)
instance = get_object_or_404(User, id=int(item['id']))
# partial 允許局部更新
serializer = super().get_serializer(instance, data=item, partial=partial)
serializer.is_valid(raise_exception=True)
serializer.save()
instances.append(serializer.data)
else:
pass
return Response(instances)
分析說明:
1踩身、通過action裝飾器可以實現(xiàn)自定義接口胀茵,可以擴(kuò)展當(dāng)前默認(rèn)的增刪改查操作
2、action的methods是該方法可執(zhí)行的http method挟阻, detail參數(shù)是確定方法是針對單條數(shù)據(jù)還是多條數(shù)據(jù)
3琼娘、實際驗證過發(fā)現(xiàn)partial = kwargs.pop('partial', None)
不起作用,因為不管是PUT還是PATCH方法附鸽,該參數(shù)都是None脱拼,所以這里對于 partial的來源和用法有待進(jìn)一步研究
args: () kwargs: {}
partial: None
4、使用DRF的Response
返回的時候要求是JSON serializable
坷备,網(wǎng)上有些教程直接把get_object_or_404
的結(jié)果instance追加到一個列表進(jìn)行返回會報如下錯誤:
TypeError: Object of type User is not JSON serializable
所以追加到結(jié)果列表的時候使用 instances.append(serializer.data)
請求的接口驗證
1熄浓、常規(guī)增刪改查這里就贅述,可以參考文末整理的 《數(shù)據(jù)接口驗證》
2省撑、批量新增還是使用 /api/v1/users/
接口赌蔑,只不過body體是 List 格式,
3竟秫、批量更新(put/patch) 接口/api/v1/users/multi_update/
4娃惯、批量刪除() 接口/api/v1/users/multi_delete/?ids=1,2,3,4
這里的ids可以根據(jù)實際自定義
2、djangorestframework-bulk
以下配置可以放到一個文件或者放到幾個獨立的問題都可以
serializers.py
# serializers.py
from rest_framework.serializers import ModelSerializer
from rest_framework.filters import SearchFilter
from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin, BulkModelViewSet
from app.models import User
from app.models import Student
class UserSerializer(BulkSerializerMixin, ModelSerializer):
class Meta:
model = User
fields = '__all__'
list_serializer_class = BulkListSerializer
# filter_backends = (SearchFilter, )
class StudentSerializer(BulkSerializerMixin, ModelSerializer):
class Meta:
model = Student
fields = '__all__'
list_serializer_class = BulkListSerializer
filter_backends = (SearchFilter, )
filters.py
# fitlers.py
from django_filters.rest_framework.filterset import FilterSet
from django_filters import filters
from app.models import User
from app.models import Student
class UserFilterSet(FilterSet):
class Meta:
model = User
fields = {
'id': ['in'],
'name': ['icontains'],
}
class StudentFilterSet(FilterSet):
class Meta:
model = Student
fields = {
'id': ['in'],
'name': ['icontains'],
}
views.py
# views.py
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework_bulk import BulkModelViewSet
from app.models import User, Student
from app.serializers import UserSerializer, StudentSerializer
from app.filters import UserFilterSet, StudentFilterSet
# Create your views here.
class UserViewSet(BulkModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
print(queryset)
filter_backends = (DjangoFilterBackend, )
filter_class = UserFilterSet
def allow_bulk_destroy(self, qs, filtered):
print("here: qs-> ", qs, "\n filtered-> ", filtered)
return qs is not filtered
class StudentViewSet(BulkModelViewSet):
serializer_class = StudentSerializer
queryset = Student.objects.all()
filter_backends = (DjangoFilterBackend, )
filter_class = StudentFilterSet
def allow_bulk_destroy(self, qs, filtered):
print("here: qs-> ", qs, "\n filtered-> ", filtered)
return qs is not filtered
urls.py
from rest_framework_bulk.routes import BulkRouter
from app.views import UserViewSet
from app.views import StudentViewSet
from django.urls import path, include
router = BulkRouter()
router.register('users', UserViewSet)
router.register('students', StudentViewSet)
urlpatterns = [
path('', include(router.urls)),
]
數(shù)據(jù)接口驗證
下表整理了常規(guī)接口和批量操作的接口匯總肥败,大家可以按照這個實際進(jìn)行測試
方法 | api接口 | 參數(shù) | 說明 |
---|---|---|---|
GET | http://127.0.0.1:8001/api/students/ | 獲取列表 | |
GET | http://127.0.0.1:8001/api/students/1/ | 獲取單個記錄 | |
GET | http://127.0.0.1:8001/api/students/ | ?id__in=2,3&name__icontains= | 自定義過濾查詢 |
POST | http://127.0.0.1:8001/api/students/ | { 'name': 'Stu-01'} | 新增單條記錄 |
POST | http://127.0.0.1:8001/api/students/ | [{ 'name': 'Stu-02'},{ 'name': 'Stu-03'}] | 批量新增 |
PUT/PATCH | http://127.0.0.1:8001/api/students/ | { 'id': 1, 'name': 'Stu-01-ch'} | 單個更新 |
PUT/PATCH | http://127.0.0.1:8001/api/students/ | [{ 'id': 2, 'name': 'Stu-02-ch'},{ 'id': 3, 'name': 'Stu-03-ch'}] | 批量更新 |
DELETE | http://127.0.0.1:8001/api/students/1/ | 單個刪除 | |
DELETE | http://127.0.0.1:8001/api/students/ | ?id__in=2,3 | 批量刪除 |
知識點說明:
1趾浅、使用 restframework-bulk
批量刪除時愕提,是通過 allow_bulk_destroy
這樣的hook
來判斷要刪除的結(jié)果是否是經(jīng)過過濾的 qs is not filtered
;
1.1皿哨、所以必須定義過濾才可以浅侨,不然執(zhí)行delete會不會刪除
class StudentViewSet(BulkModelViewSet):
serializer_class = StudentSerializer
queryset = Student.objects.all()
# 注意注釋這兩個就不會刪除,接口直接報 400 錯誤
# filter_backends = (DjangoFilterBackend, )
# filter_class = StudentFilterSet
def allow_bulk_destroy(self, qs, filtered):
print("here: qs-> ", qs, "\n filtered-> ", filtered)
return qs is not filtered
1.2证膨、allow_bulk_destroy
返回True 代表刪除如输,返回 False 代表不刪除,如果一旦沒有上面的過濾限制
椎例,然后這個函數(shù)又直接返回True挨决, 那就悲劇了.... 這個model的所有數(shù)據(jù)都被刪除了...
class StudentViewSet(BulkModelViewSet):
serializer_class = StudentSerializer
queryset = Student.objects.all()
# 注意注釋這兩個就不會刪除,接口直接報 400 錯誤
# filter_backends = (DjangoFilterBackend, )
# filter_class = StudentFilterSet
def allow_bulk_destroy(self, qs, filtered):
# print("here: qs-> ", qs, "\n filtered-> ", filtered)
# return qs is not filtered
# 直接返回True 是災(zāi)難的
return True
2订歪、還記得剛開始的User模型么脖祈,除了批量更新
之外所有的操作都是沒有問題,為什么單獨 批量更新
就問題呢刷晋? 報錯如下盖高,提示沒有pk屬性,但是實際上該表是有id作為主鍵的
, id對應(yīng)的就是 pk
明明和Student的代碼一模一樣眼虱。
一模一樣嗎喻奥? 當(dāng)然存在差異,就是User模型中的name字段多了個 unique=True
去掉這個屬性之后捏悬,再次執(zhí)行批量更新操作沒有任何問題撞蚕。 但是最終未能找到問題的根源是什么? 大家有知道原因的过牙,歡迎指導(dǎo)~
Refer:
1甥厦、https://zhuanlan.zhihu.com/p/369898862
感興趣的可以搜索微信公眾號 全棧運維
,或者添加QQ 1209755822
備注技術(shù)交流