Django QueryDict不可变错误:Customer序列化器POST请求失败
Django DRF 序列化器修改initial_data触发Immutable QueryDict错误
向/users/customer端点发起POST请求时,CustomerSerializer抛出AttributeError: This QueryDict instance is immutable错误,但MerchantSerializer无此异常。业务逻辑为通过JWT获取用户ID,重写序列化器的is_valid方法自动赋值customer_id/merchant_id,而非从请求中接收。
报错栈
Internal Server Error: /users/customer Traceback (most recent call last): File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\django\core\handlers\exception.py", line 55, in inner response = get_response(request) File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\django\views\decorators\csrf.py", line 56, in wrapper_view return view_func(*args, **kwargs) File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\django\views\generic\base.py", line 104, in view return self.dispatch(request, *args, **kwargs) File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\rest_framework\views.py", line 509, in dispatch response = self.handle_exception(exc) File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\rest_framework\views.py", line 469, in handle_exception self.raise_uncaught_exception(exc) File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception raise exc File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\rest_framework\views.py", line 506, in dispatch response = handler(request, *args, **kwargs) File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\rest_framework\decorators.py", line 50, in handler return func(*args, **kwargs) File "C:\Users\Abdullah\Desktop\Cashless\Users\api\views.py", line 64, in customer_register if serializer.is_valid(): File "C:\Users\Abdullah\Desktop\Cashless\Users\api\serializers.py", line 71, in is_valid self.initial_data['customer_id'] = request.user.id File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\django\http\request.py", line 555, in __setitem__ self._assert_mutable() File "C:\Users\Abdullah\Envs\cashless\lib\site-packages\django\http\request.py", line 552, in _assert_mutable raise AttributeError("This QueryDict instance is immutable") AttributeError: This QueryDict instance is immutable
相关代码
Serializer.py
class CustomerSerializer(serializers.ModelSerializer): customer= CustomUserSerializer(read_only=True) customer_id=serializers.CharField(write_only=True) class Meta: model=Customer fields='__all__' def is_valid(self, *, raise_exception=False): request=self.context['request'] self.initial_data['customer_id'] = request.user.id valid=super().is_valid(raise_exception=raise_exception) return valid class MerchantSerializer(serializers.ModelSerializer): merchant= CustomUserSerializer(read_only=True) merchant_id=serializers.CharField(write_only=True) class Meta: model=Merchant fields='__all__' def is_valid(self, *, raise_exception=False): request=self.context['request'] self.initial_data['merchant_id'] = request.user.id valid=super().is_valid(raise_exception=raise_exception) return valid
Models.py
class Merchant(models.Model): merchant=models.OneToOneField(CustomUser, related_name='owner',primary_key=True,on_delete=models.CASCADE) photo=models.ImageField() CNIC= models.CharField(max_length=13,validators=[MinLengthValidator(13)],unique=True,null=False,blank=False) NTN=models.CharField(max_length=13,null=False,blank=False) address=models.TextField(blank=True,null=True) account_title=models.CharField(max_length=30,null=False,blank=False) IBFT=models.CharField(max_length=20,null=False,blank=False) account_no=models.CharField(max_length=24,null=False,blank=False) bankname= models.ForeignKey(Bank, on_delete=models.CASCADE) is_verified=models.BooleanField(default=False) def __str__(self) -> str: return f'{self.merchant}' class CardType(models.Model): cardtype=models.CharField(max_length=20,null=False,blank=False) class Customer(models.Model): customer=models.OneToOneField(CustomUser,verbose_name=_('Customer Name'),on_delete=models.CASCADE,primary_key=True) card_number=models.CharField(_('Card Number'),max_length=16,null=False,blank=False) card_type=models.ForeignKey(CardType,on_delete=models.CASCADE) expiry_date= CardExpiryField(_('expiration date'),null=False,blank=False) name_card=models.CharField(_('Card Name'),max_length=30,null=False,blank=False) cvc=models.CharField(_('CVC'),max_length=3,null=False,blank=False) bankname= models.ForeignKey(Bank,on_delete=models.CASCADE) account_no=models.CharField(_('Account Number'),max_length=24,null=False,blank=False) def __str__(self): return f'{self.customer}'
Views.py
@api_view(['GET','POST']) def merchant_register(request): if request.method == 'POST': serializer = MerchantSerializer(data=request.data,context={'request': request}) if serializer.is_valid(): data={} user=serializer.save() user=CustomUser.objects.get(id=user.merchant_id) refresh = RefreshToken.for_user(user) data['refresh_token']= str(refresh) data['access_token']=str(refresh.access_token) data['user_id']=user.id data['merchant_type'] = user.is_merchant data['customer_type'] = user.is_customer return Response(data,status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED) merchant=Merchant.objects.all() serializer=MerchantSerializer(merchant,many=True) return Response(serializer.data, status=status.HTTP_200_OK) @api_view(['GET','POST']) def customer_register(request): if request.method == 'POST': serializer = CustomerSerializer(data=request.data,context={'request':request}) if serializer.is_valid(): data={} user=serializer.save() user=CustomUser.objects.get(id=user.customer_id) refresh = RefreshToken.for_user(user) data['refresh_token']= str(refresh) data['access_token']=str(refresh.access_token) data['user_id']=user.id data['merchant_type'] = user.is_merchant data['customer_type'] = user.is_customer return Response(status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED) customer=Customer.objects.all() serializer=CustomerSerializer(customer,many=True) return Response(serializer.data, status=status.HTTP_200_OK)
问题原因及解决方案
原因
request.data在DRF中,若请求为表单提交格式,会返回不可变的QueryDict,直接修改self.initial_data(即request.data的引用)会触发Immutable错误。MerchantSerializer未报错,大概率是因为对应接口的请求为JSON格式,此时request.data是普通可变字典。
解决方案
推荐采用DRF规范写法,重写序列化器的create方法,直接从上下文获取用户ID赋值,避免修改不可变的QueryDict:
修改Serializer.py:
class CustomerSerializer(serializers.ModelSerializer): customer= CustomUserSerializer(read_only=True) class Meta: model=Customer fields=['customer', 'card_number', 'card_type', 'expiry_date', 'name_card', 'cvc', 'bankname', 'account_no'] def create(self, validated_data): request = self.context.get('request') validated_data['customer'] = request.user return super().create(validated_data) class MerchantSerializer(serializers.ModelSerializer): merchant= CustomUserSerializer(read_only=True) class Meta: model=Merchant fields=['merchant', 'photo', 'CNIC', 'NTN', 'address', 'account_title', 'IBFT', 'account_no', 'bankname', 'is_verified'] def create(self, validated_data): request = self.context.get('request') validated_data['merchant'] = request.user return super().create(validated_data)
若一定要修改initial_data,可先将其转换为可变字典:
def is_valid(self, *, raise_exception=False): request = self.context['request'] self.initial_data = dict(self.initial_data) self.initial_data['customer_id'] = request.user.id valid = super().is_valid(raise_exception=raise_exception) return valid
但此方法不符合DRF设计规范,仅作临时替代方案。
内容的提问来源于stack exchange,提问作者Abdullah Moudood
相关产品推荐
相关产品推荐

