【问题标题】:This QueryDict instance is immutable这个 QueryDict 实例是不可变的
【发布时间】:2017-11-26 19:09:22
【问题描述】:

我有一个带有外键帐户的分支模型(分支的所有者):

class Branch(SafeDeleteModel):
    _safedelete_policy = SOFT_DELETE_CASCADE
    name = models.CharField(max_length=100)
    account = models.ForeignKey(Account, null=True, on_delete=models.CASCADE)
    location = models.TextField()
    phone = models.CharField(max_length=20, blank=True,
                         null=True, default=None)
    create_at = models.DateTimeField(auto_now_add=True, null=True)
    update_at = models.DateTimeField(auto_now=True, null=True)

    def __str__(self):
        return self.name

    class Meta:
        unique_together = (('name','account'),)

    ...

我有一个带有用户外键的 Account 模型(一对一字段):

class Account(models.Model):
    _safedelete_policy = SOFT_DELETE_CASCADE
    name = models.CharField(max_length=100)
    user = models.OneToOneField(User)
    create_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name + ' - ' + self.create_at.strftime('%Y-%m-%d %H:%M:%S')

我为 Branch 创建了一个 ModelViewSet,它显示了登录用户拥有的分支:

class BranchViewSet(viewsets.ModelViewSet):
    serializer_class = BranchSerializer
    permission_classes = (permissions.IsAuthenticated,)


    def get_queryset(self):
        queryset = Branch.objects.all().filter(account=self.request.user.account)
        return queryset

现在要创建一个新分支,我想用 request.user.account 保存帐户字段,而不是从其余客户端发送的数据(为了提高安全性)。例如:

def create(self, request, *args, **kwargs):
    if request.user.user_type == User.ADMIN:
        request.data['account'] = request.user.account
        return super(BranchViewSet, self).create(request, *args, **kwargs)

def perform_create(self, serializer):
    '''
        Associate branch with account
    '''
    serializer.save(account=self.request.user.account)

在分支序列化器中

class BranchSerializer(serializers.ModelSerializer):
    account = serializers.CharField(source='account.id', read_only=True)

    class Meta:
        model = Branch
        fields = ('id', 'name', 'branch_alias',
              'location', 'phone', 'account')
        validators = [
            UniqueTogetherValidator(
                queryset=Branch.objects.all(),
                fields=('name', 'account')
            )
        ]

但我得到了这个错误: 此 QueryDict 实例是不可变的。 (意味着 request.data 是一个不可变的 QueryDict 并且不能更改)

您知道在使用 django rest 框架创建对象时添加额外字段的更好方法吗?

【问题讨论】:

  • 覆盖 perform_create 是这里要做的事情,并且完全按照您的意愿行事,所以我不知道您为什么需要更改 create 或根本修改 QueryDict。
  • 你能举个例子吗?
  • 我不需要展示示例,您已经有了正确的代码。只需摆脱 create 方法,因为您在 perform_create 中编写的内容正是您想要的。
  • 但是调用API时传递的数据呢?
  • 我不明白你的意思。序列化程序已经有了 post 数据。

标签: django django-models django-rest-framework


【解决方案1】:

您知道在使用 django rest 框架创建对象时添加额外字段的更好方法吗?

在创建/更新对象时提供额外数据的官方方法是将它们传递给serializer.save(),如图所示here

【讨论】:

  • 这在需要属性进行验证时没有帮助。 is_valid().save() 之前被调用
  • 如果验证需要它们而不是由客户提供,您应该考虑添加一个额外的步骤,即“业务验证”或类似的东西。在序列化器中放入太多业务逻辑不是一个好主意。
【解决方案2】:

https://docs.djangoproject.com/en/2.0/ref/request-response/#querydict-objects

在正常的请求/响应周期中访问时,request.POST 和 request.GET 中的 QueryDicts 将是不可变的。要获得可变版本,您需要使用 QueryDict.copy()。

【讨论】:

    【解决方案3】:

    正如您在Django documentation 中看到的:

    request.POSTrequest.GET 中的 QueryDicts正常请求/响应周期中访问时将是不可变的。

    因此您可以使用同一文档中的建议:

    要获得可变版本,您需要使用 QueryDict.copy()

    或者...使用一个小技巧,例如,如果您出于某种原因需要保留对对象的引用或保持对象不变:

    # remember old state
    _mutable = data._mutable
    
    # set to mutable
    data._mutable = True
    
    # сhange the values you want
    data['param_name'] = 'new value'
    
    # set mutable flag back
    data._mutable = _mutable
    

    您的 QueryDicts

    数据在哪里

    【讨论】:

      【解决方案4】:

      做简单的事:

      #views.py
      from rest_framework import generics
      
      
      class Login(generics.CreateAPIView):
          serializer_class = MySerializerClass
          def create(self, request, *args, **kwargs):
              request.data._mutable = True
              request.data['username'] = "example@mail.com"
              request.data._mutable = False
      
      #serializes.py
      from rest_framework import serializers
      
      
      class MySerializerClass(serializers.Serializer):
          username = serializers.CharField(required=False)
          password = serializers.CharField(required=False)
          class Meta:
              fields = ('username', 'password')
      

      【讨论】:

        【解决方案5】:

        我个人认为这样写代码会更优雅。

        def create(self, request, *args, **kwargs):
            data = OrderedDict()
            data.update(request.data)
            data['account'] = request.user.account
            serializer = self.get_serializer(data)
            serializer.is_valid(raise_exception=True)
            self.perform_create(serializer)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
        

        【讨论】:

          【解决方案6】:
          request.data._mutable=True
          

          使可变为 true 以启用在 querydict 或请求中的编辑。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-03-10
            • 1970-01-01
            相关资源
            最近更新 更多