Django DRF add request.user to modelserializer
Asked Answered
K

3

7

I am using django rest framework, and I have an object being created via a modelviewset, and a modelserializer. This view is only accessible by authenticated users, and the object should set its 'uploaded_by' field, to be that user.

I've read the docs, and come to the conclusion that this should work

viewset:

class FooViewset(viewsets.ModelViewSet):
    permission_classes = [permissions.IsAdminUser]
    queryset = Foo.objects.all()
    serializer_class = FooSerializer

    def get_serializer_context(self):
        return {"request": self.request}

serializer:

class FooSerializer(serializers.ModelSerializer):
    uploaded_by = serializers.PrimaryKeyRelatedField(
        read_only=True, default=serializers.CurrentUserDefault()
    )

    class Meta:
        model = Foo
        fields = "__all__"

However, this results in the following error:

django.db.utils.IntegrityError: NOT NULL constraint failed: bar_foo.uploaded_by_id

Which suggests that "uploaded_by" is not being filled by the serializer.

Based on my understanding of the docs, this should have added the field to the validated data from the serializer, as part of the create method.

Clearly I've misunderstood something!

Kitty answered 6/4, 2019 at 14:10 Comment(0)
S
13

The problem lies in the read_only attribute on your uploaded_by field:

Read-only fields are included in the API output, but should not be included in the input during create or update operations. Any 'read_only' fields that are incorrectly included in the serializer input will be ignored.

Set this to True to ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization.

Source

Basically it's used for showing representation of an object, but is excluded in any update and create-process.

Instead, you can override the create function to store the desired user by manually assigning it.

class FooSerializer(serializers.ModelSerializer):

    uploaded_by = serializers.PrimaryKeyRelatedField(read_only=True)

    def create(self, validated_data):
        foo = Foo.objects.create(
            uploaded_by=self.context['request'].user,
            **validated_data
        )
        return foo
Scrivings answered 6/4, 2019 at 14:14 Comment(9)
Ah, that makes sense, but it doesn't work without read_only: AssertionError: Relational field must provide a queryset argument, override get_queryset, or set read_only=True.Kitty
The HiddenField seems to have worked in terms of creation, but the serializer doesn't show the uploaded_by field when showing already-existing objectsKitty
Well, essentially a serializer is used to serialize the input data, and if you want to populate certain fields you could do so in the perform_create function. I've updated my answer and removed the HiddenField part as I didn't get that you wanted to present the uploaded_by field as wellScrivings
The docs say that perform_create should be in the viewset, but I've tried it in both, and it's never calledKitty
I see, have you tried to add source='user.id' (or just source='user') as an attribute to the original PrimaryKeyRelatedField ? Without using any of my proposed solutions in my current answer.Scrivings
I'm not manually defining the fields - just fields = "all" on the modelserializerKitty
No, I mean adding source as an attribute to uploaded_by = serializers.PrimaryKeyRelatedField(...). I've added an optional solution that I've used in one of my own projects as well.Scrivings
that doesn't work either. It looks like the create override will work, but I'm confused as to why the perform_create doesn't work, since the docs seem to recommend itKitty
It appears that the perform_create function is used for the CreateModelMixin, which I guess you'll need to use in the view instead. However, I've (once again) updated my answer to hopefully do the desired behaviour.Scrivings
G
2

DRF tutorial recommend to override perform_create method in this case and then edit serializer so, that it reflect to new field

from rest_framework import generics, serializers
from .models import Post


class PostSerializer(serializers.HyperlinkedModelSerializer):
    author = serializers.ReadOnlyField(source='author.username')

    class Meta:
        model = models.Post
        fields = ['title', 'content', 'author']


class ListPost(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    def perform_create(self, serializer):
        return serializer.save(author=self.request.user)
Guile answered 26/9, 2020 at 15:28 Comment(1)
What about checking serializer is valid like serializer.is_valid() before saving with author fieldLampblack
N
0

Cleaner way:

class PostCreateAPIView(CreateAPIView, GenericAPIView):
    queryset = Post.objects.all()
    serializer_class = PostCreationSerializer

    def perform_create(self, serializer):
        return serializer.save(author=self.request.user)

class PostCreationSerializer(serializers.ModelSerializer):
    author = serializers.PrimaryKeyRelatedField(read_only=True)

    class Meta:
        model = Post
        fields = ("content", "author")
Nievelt answered 2/4, 2021 at 10:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.