Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRF: Remove field on model serializer after validation but before creation (on CreateAPIView)

I have a contact form on a site that is posting to a CreateAPIView to create a new instance of a model (that is eventually emailed to the admin). On my serializer I have a honeypot field to help reject spam.

The model:

class Message(models.Model):
    name = ...
    message = ...

and serializer:

class MessageSerializer(serializers.ModelSerializer):

    # Honeypot field
    url = serializers.CharField(allow_blank=True, required=False)

    class Meta:
        model = Message
        fields = '__all__'

    def validate_url(self, value):
        if value and len(value) > 0:
            raise serializers.ValidationError('Spam')
        return value

and view:

class MessageView(generics.CreateAPIView):
    ''' Create a new contact form message. '''
    serializer_class = MessageSerializer

My problem is that as it stands, when I post to this view, I get the error:

TypeError: Got a TypeError when calling Message.objects.create(). This may be because you have a writable field on the serializer class that is not a valid argument to Message.objects.create(). You may need to make the field read-only, or override the MessageSerializer.create() method to handle this correctly.

so obviously the seriazlier is attempting to save the url field to the model in CreateApiView.perform_create()

I tried adding read_only to the serializer field, but this means that the url_validate method is skipped altogether.

How can I keep the field on the serializer until validation has occurred, removing it before the serializer.save() is called in perform_create()?

like image 695
Timmy O'Mahony Avatar asked Dec 02 '15 18:12

Timmy O'Mahony


2 Answers

you can do this overriding the create method like:

class MessageSerializer(serializers.ModelSerializer):

    # Honeypot field
    url = serializers.CharField(allow_blank=True, required=False)

    class Meta:
        model = Message
        fields = '__all__'

    def validate_url(self, value):
        if value and len(value) > 0:
            raise serializers.ValidationError('Spam')
        return value

    def create(self, validated_data):
        data = validated_data.pop('url')
        return Message.objects.create(**data)
like image 61
Anush Devendra Avatar answered Sep 27 '22 16:09

Anush Devendra


OK, I didn't read the error correctly. As it clearly says:

override the MessageSerializer.create() method to handle this correctly.

I was looking at overwriting the CreateAPIView.create() method which didn't make sense.

This works:

class MessageSerializer(serializers.ModelSerializer):

    # Honeypot field
    url = serializers.CharField(allow_blank=True, required=False)

    class Meta:
        model = Message
        fields = '__all__'

    def validate_url(self, value):

        if value and len(value) > 0:
            raise serializers.ValidationError('Error')
        return value

    def create(self, validated_data):
        if "url" in validated_data:
            del validated_data["url"]
        return Message.objects.create(**validated_data)
like image 30
Timmy O'Mahony Avatar answered Sep 27 '22 16:09

Timmy O'Mahony