You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 17:14:53