SilentDev
SilentDev

Reputation: 22747

DjangoRestFramework - Serializer validation raising a KeyError even when it is provided by the perform_create() method

This is my ViewSet:

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)

    def perform_create(self, serializer):
        # Passing location to the serializer.
        serializer.save(owner=self.request.user, location=self.request.user.userextended.location)

And this is my serializer:

class PostSerializer(serializers.ModelSerializer):

    class Meta:
        model = Post
        fields = ('id', 'owner', 'post', 'page', 'location')
        read_only_fields = ('id', 'owner', 'location')

    def create(self, validated_data):
        post = Post(
                owner =  validated_data['owner'],
                post = validated_data['post'],
                location = validated_data['location'],
        )

        if validated_data.get('page', None):
            post.page = validated_data['page']

        post.save()

        return post

    def validate(self, data):
        if not data.get('page', None):
            return data
        # The KeyError being raised points to the line below.
        if data['location'] == data['page']:
            return data
        raise serializers.ValidationError('Error.')

The problem is, even though I am passing locaiton to the serializer from my perform_create() method, I get an error raised when I try to create a post. The error is:

if data['location'] == data['page']:
    KeyError: 'location'

I'm assuming that DRF does the serializer validation check before executing the perform_create() method? If yes, how do I accomplish what I need to? (i.e. how do I check if data['location'] == data['page'] when location is passed from my view to the serializer and page is from my serializer?).

Upvotes: 2

Views: 1896

Answers (1)

levi
levi

Reputation: 22697

According to DRF docs.

Sometimes you'll want your view code to be able to inject additional data at the point of saving the instance. This additional data might include information like the current user, the current time, or anything else that is not part of the request data.

You can do so by including additional keyword arguments when calling .save()

serializer.save(owner=request.user)

Any additional keyword arguments will be included in the validated_data argument when .create() or .update() are called.

So, you can access to extra keyword arguments only within create() or update() method, not in validate()

Said that, how can you pass extra data to your serializer ? use context serializer

Override get_serializer_context method in your view

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly,)

    get_serializer_context(self):
         return {'location':self.request.user.userextended.location}

Then in your validate serializer method

def validate(self, data):
    if not data.get('page', None):
        return data
    location = self.context.get('location')
    if location == data['page']:
        return data
    raise serializers.ValidationError('Error.')

Upvotes: 3

Related Questions