背景
之前寫過一篇文章 《Django restframework實現(xiàn)批量操作》 介紹了兩種方案實現(xiàn) Django批量操作板丽,其中一種是通過 restframework-bulk
來實現(xiàn)拜姿,但是當時遇到一個 QuerySet object has no attribute pk
問題沒有搞明白木柬。
今天看到博客中有朋友提問,就打開源碼分析debug了一下赡矢,找到了根本愿意所在
先說結果
User模型中的name字段使用 unique=True
愿卸, 程序在運行過程中就進行 UniqueValidator
嚴重听诸,根源是這個類中exclude_current_instance
方法沒有對參數(shù)instance
做類型判斷抄瓦,在批量
操作的時候instance
是 List 類型潮瓶,不是對象類型陶冷,所以報錯沒有pk屬性
穿插
如果想更好的理解和實戰(zhàn)Django restframework實現(xiàn)批量操作
涉及的知識點钙姊,可以獲取該案例的源碼
下載地址 django multi operations demo code
分析
1)、批量更新接口http://127.0.0.1:8002/api/v2/users/
定位到 urls.py
文件中相關代碼
from demoapp.views import UserViewSet
router.register('users', UserViewSet)
2)埂伦、看到 views.py
文件中 UserViewSet
的定義
# Create your views here.
class UserViewSet(BulkModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
filter_backends = (DjangoFilterBackend, )
filterset_class = UserFilterSet
def allow_bulk_destroy(self, qs, filtered):
print("here: qs-> ", qs, "\n filtered-> ", filtered)
return qs is not filtered
3)煞额、ctrl+鼠標右鍵 BulkModelViewSet
上找到 restframework_bulk/generic.py
看到
class BulkModelViewSet(bulk_mixins.BulkCreateModelMixin,
bulk_mixins.BulkUpdateModelMixin,
bulk_mixins.BulkDestroyModelMixin,
ModelViewSet):
pass
這里我們分析 bulk_mixins.BulkUpdateModelMixin
4)、同樣的方式定位到 restframework_bulk/drf3/mixins.py
看 BulkUpdateModelMixin
的實現(xiàn)
class BulkUpdateModelMixin(object):
... ...
def bulk_update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
serializer = self.get_serializer(
self.filter_queryset(self.get_queryset()),
data=request.data,
many=True,
partial=partial,
)
# Attention:
# 1、注意上面的 self.get_serializer() 中有了many=True膊毁,所以不是序列化是沒有問題
# 2胀莹、但是從錯誤中知道這里 is_valid() 觸發(fā)了異常
# 3、那么就需要根據(jù)錯誤去追 rest_framework中對應 serializers 相關的邏輯
serializer.is_valid(raise_exception=True)
self.perform_bulk_update(serializer)
return Response(serializer.data, status=status.HTTP_200_OK)
Attention:
- 1婚温、注意上面的 self.get_serializer() 中有了many=True描焰,所以不是序列化是沒有問題
- 2、但是從錯誤中知道這里 is_valid() 觸發(fā)了異常
- 3栅螟、那么就需要根據(jù)錯誤去追 rest_framework中對應 serializers 相關的邏輯
5)荆秦、從錯誤中有如下信息
... ...
File "/Users/colinspace/.pyenv/versions/py2022/lib/python3.9/site-packages/rest_framework/serializers.py", line 756, in is_valid
self._validated_data = self.run_validation(self.initial_data)
.... ...
所以去 rest_framework/serializers.py
中 756行檢查,源碼中可以得知是定位到 ListSerializer
類中的is_valid()
方法力图,然后調用相關的內部方法步绸,這里列舉出 調用順序,具體源碼太多這里不做展示
is_valid()
-> self.run_validation()
-> self.to_internal_value(data)
-> self.child.run_validation(item)
-> Serializer類中的self.to_internal_value(data)
這里需要把 Serializer類中的self.to_internal_value(data)
展開說明下
fields = self._writable_fields
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
這里是對模型序列化中的可寫字段
進行run_validation
處理吃媒,然后就跳到了 rest_framework/fields.py
中的 CharField類的run_validation()
-> Field類的run_validation()
-> Field類的 run_validators()
def run_validators(self, value):
errors = []
for validator in self.validators:
try:
if getattr(validator, 'requires_context', False):
validator(value, self)
else:
validator(value)
前面說過 Unique=True 會進行 UniqueValidator 驗證瓤介,UniqueValidator 中 requires_context=True
,
這里執(zhí)行validator(value, self)
實際就是執(zhí)行 UniqueValidator
類的__call__(self, value, serializer_field)
方法
def __call__(self, value, serializer_field):
field_name = serializer_field.source_attrs[-1]
instance = getattr(serializer_field.parent, 'instance', None)
queryset = self.queryset
queryset = self.filter_queryset(value, queryset, field_name)
queryset = self.exclude_current_instance(queryset, instance)
if qs_exists(queryset):
raise ValidationError(self.message, code='unique')
這里 serializer_field
就是UserSerializer的name
字段, serializer_field.parent
就是UserSerializer本身,它的instance
在批量操作的情況就是一個List類型赘那,而不是單一對象刑桑,這里可以 輸出類型很容易驗證
最后知道錯誤發(fā)送在 exclude_current_instance
方法,不就是 UniqueValidator 中的方法么募舟,
def exclude_current_instance(self, queryset, instance):
if instance is not None:
return queryset.exclude(pk=instance.pk)
return queryset
既然 instance
此時是List類型漾月,所以 instance.pk
必然報錯
所以需要修改這里的源碼為如下:
def exclude_current_instance(self, queryset, instance):
"""
If an instance is being updated, then do not include
that instance itself as a uniqueness conflict.
"""
if instance is not None:
if isinstance(instance, list):
for item in instance:
queryset.exclude(pk=instance.pk)
return queryset
# return queryset.exclude(pk=instance.pk)
return queryset
然后執(zhí)行批量更新,沒有報錯胃珍,也確實按照實際期望執(zhí)行了
ok梁肿,今天的源碼分析就到這里,比較冗長也比較細觅彰,就要是帶著大家都一遍吩蔑,了解如何去分析閱讀源碼。這樣以后出現(xiàn)問題的時候填抬,就知道怎么去分析解決問題了烛芬。
如果喜歡文章, 歡迎點贊飒责、關注赘娄、留言交流 1209755822
博客首頁: http://blog.colinspace.com
csdn首頁: https://blog.csdn.net/eagle5063
簡書首頁: http://www.reibang.com/u/6d793fbacc88
知乎首頁: https://www.zhihu.com/people/colin-31-49