27

I have a serializer like this:

class PersonSerializer(serializers.ModelSerializer):
    gender = serializers.SerializerMethodField()
    bio = BioSerializer()

    class Meta:
        model = Person
        fields = UserSerializer.Meta.fields + ('gender', 'bio',)

    def get_gender(self, obj):
        return obj.get_gender_display()

I used this to display "Male" and "Female"(insted of "M" of "F") while performing GET request.

This works fine.

But now I am writing an patch method for the model and SerializerMethodField() has read_only=True. So I am not getting value passed for gender field in serializer.validated_data(). How to overcome this issue?

Kishan Mehta
  • 2,598
  • 5
  • 39
  • 61

2 Answers2

32

So if I understand you correctly, you want to send {'gender': 'Male'} in your PATCH request.

Therefor, you have to tell your serializer how to convert your representation i.e. 'Male' into the internal value.

As you can see in source, SerializerMethodField only covers the conversion from internal value to the representation.

You can implement a custom SerializerField that performs the necessary conversions. A naive implementation could something like this:

class GenderSerializerField(serializers.Field):

    VALUE_MAP = {
        'M': 'Male',
        'F': 'Female'
    }

    def to_representation(self, obj):
        return self.VALUE_MAP[obj]            

    def to_internal_value(self, data):
        return {k:v for v,k in self.VALUE_MAP.items()}[data]

class PersonSerializer(serializers.ModelSerializer):
    gender = GenderSerializerField()
    ...

Note that this untested and lacks any validation, check out the DRF docs on custom fields.

Andy Baker
  • 21,158
  • 12
  • 58
  • 71
Daniel Hepper
  • 28,981
  • 10
  • 72
  • 75
  • I did as you said but its not working. serializer is not detecting any key named "gender" in this case also. btw serializers.Serializer in class params as it said serailizers have no field like SerializerField – Kishan Mehta May 27 '16 at 09:54
  • Sorry, the base class was wrong (I assume that's what you mean with "class params"). It should be `serializers.Field` instead of `serializers.SerializerField` – Daniel Hepper May 27 '16 at 09:57
  • 1
    @kishan glad I could help! If my answer solved the issue for you, feel free to accept it by clicking the checkmark. Thanks! – Daniel Hepper May 30 '16 at 11:54
  • 2
    why do you create a copy of the dict before using it? woudn't `return VALUE_MAP[data]` also work? – Jens Timmerman Jun 15 '16 at 14:42
  • @JensTimmerman my intention was to invert the dict, but the code was wrong. It is now fixed (k,v instead of v,k). Good catch, thanks! – Daniel Hepper Jun 15 '16 at 14:45
4

Aside from accepted answer, there can be other simpler hooks. If 'create' and 'update' worked as you wanted before modifiying gender field, then you can do as follow to get everything to default for create and update requests.

  • Do not user SerializerMethodField. Instead override serializer representation.
class PersonSerializer(serializers.ModelSerializer):
    bio = BioSerializer()
    
    class Meta:
        model = Person
        fields = UserSerializer.Meta.fields + ('bio',)


    def to_representation(self, obj):
        ret = super().to_representation(obj)
        ret['gender'] = obj.get_gender_display()
        return ret

  • Override the __init__ method . .
class PersonSerializer(serializers.ModelSerializer):
    bio = BioSerializer()
    
     def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        try:
            if self.context['request'].method in ['GET']:
                self.fields['gender'] = serializers.SerializerMethodField()
        except KeyError:
            pass

    class Meta:
        model = Person
        fields = UserSerializer.Meta.fields + ('bio',)

    def get_gender(self, obj):
        return obj.get_gender_display()   

Sagar Adhikari
  • 1,312
  • 1
  • 10
  • 17
  • can you please guide , why did you use "UserSerializer" in the fields attribute? – Mueez Khan Sep 07 '22 at 12:38
  • @MueezKhan It does seem kind of unusual; I'd speculate because the properties of their `PersonSerializer` are mostly the same as the properties of their `UserSerializer`(?) – saschwarz Nov 21 '22 at 00:29