This QueryDict instance is immutable
Asked Answered
M

7

48

I have a Branch model with a foreign key to account (the owner of the branch):

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'),)

    ...

I have a Account model with a foreign key to user (one to one field):

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')

I've created a ModelViewSet for Branch which shows the branch owned by the logged in user:

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

Now to create a new branch, I want to save account field with request.user.account, not with data sent from the rest client (for more security). for example:

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)

In branch serializer

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')
            )
        ]

but I got this error: This QueryDict instance is immutable. (means request.data is a immutable QueryDict and can't be changed)

Do you know any better way to add additional fields when creating an object with django rest framework?

Mccabe answered 23/6, 2017 at 9:7 Comment(9)
Overriding perform_create is the thing to do here, and does exactly what you want, so I don't know why you need to change create or modify the QueryDict at all.Stillwell
can you show some example ?Mccabe
I don't need to show an example, you already have the right code. Just get rid of that create method because what you've written in perform_create does exactly what you want.Stillwell
but how about the data passed when calling the API ?Mccabe
I don't understand what you mean. The serializer already has the post data.Stillwell
TypeError at /api/branch/ 'type' object is not iterable it returns that error messageMccabe
Show the full traceback please.Stillwell
I fix it alreadyMccabe
But it still returns that account is a required field. Eventhough I added this code. serializer.save(account=self.request.user.account)Mccabe
H
96

As you can see in the Django documentation:

The QueryDicts at request.POST and request.GET will be immutable when accessed in a normal request/response cycle.

so you can use the recommendation from the same documentation:

To get a mutable version you need to use QueryDict.copy()

or ... use a little trick, for example, if you need to keep a reference to an object for some reason or leave the object the same:

# 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

where data it is your QueryDicts

Horsehair answered 5/7, 2018 at 17:23 Comment(0)
T
22

Do Simple:

#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'] = "[email protected]"
        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')
Therapeutic answered 8/5, 2020 at 6:54 Comment(3)
request.data._mutable = True did it for meHeadwaters
I got an error 'dict' object has no attribute '_mutable' Betthezel
@mecdeality, This should be happening when the request method is NOT GET or POST I believeDoughy
W
10
request.data._mutable=True

Make mutable true to enable editing in querydict or the request.

Waterresistant answered 6/1, 2022 at 17:26 Comment(0)
K
4

You can use request=request.copy() at the first line of your function.

Kimes answered 28/4, 2022 at 18:11 Comment(2)
maybe you meant request=request.data.copy() - @ShreyashSrivsatavaLapointe
Yes, right, silly me :/ Or simply, data=request.data.copy()Kimes
R
3

Do you know any better way to add additional fields when creating an object with django rest framework?

The official way to provide extra data when creating/updating an object is to pass them to the serializer.save() as shown here

Rubdown answered 23/6, 2017 at 9:51 Comment(2)
this is not helpful when attributes are needed for validation. is_valid() is called before .save()Trengganu
If they are needed by validation and not provided by the client you should consider adding an extra step which would be "business validation" or something similar. Not a good idea to put too much business logic in the serializer.Rubdown
L
3

I personally think it would be more elegant to write code like this.

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)
Lingle answered 3/8, 2020 at 4:23 Comment(2)
Great answer. Any special reason why you chose to select OrderedDict instead of regular dict?Vivanvivarium
Because the default implementation of request._data in drf is of type QueryDict, dict is fine in this example, but bugs may occur in other cases. Request._dataLingle
I
1

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

The QueryDicts at request.POST and request.GET will be immutable when accessed in a normal request/response cycle. To get a mutable version you need to use QueryDict.copy().

Izzy answered 1/6, 2018 at 2:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.