Csaba Toth
Csaba Toth

Reputation: 10699

DRF expose model field in two separate read-write fields on the REST API

I'm using Python 2.7, DRF 3.1.3, Django 1.4.21 (I know it's old but it's a large codebase, one day we'll migrate). The real-world business case is more complicated than this, here is the simplified version.

I have a model:

class Person(models.Model):
    foo = models.CharField(max_length=2, blank=True, null=True)

On the REST API I want to expose two fields, both of them is derived from foo. Both fields should be writable, and both fields would set the foo field in a way when I write into it. Try 1: bar is unfortunately read-only

class PersonSerializer(serializers.ModelSerializer):
    bar = serializers.SerializerMethodField()

    class Meta:
        model = Person
        fields = ('id', 'foo', 'bar')

    def get_bar(self, obj):
        return get_bar(obj.foo) # not relevant now what get_bar does

Try 2: trying to fake a field somehow

class PlaceholderCharField(serializers.CharField):
    def to_representation(self, obj):
        pass

    def to_internal_value(self, data):
        pass

class PersonSerializer(serializers.ModelSerializer):
    bar = PlaceholderCharField()

    class Meta:
        model = Person
        fields = ('id', 'foo', 'bar')

    def to_representation(self, instance):
        data = super(PersonSerializer, self).to_representation(instance)
        data['bar'] = get_bar(instance.foo)
        return data

    def to_internal_value(self, data):
        instance = super(PersonSerializer, self).to_internal_value(data)
        if data.has_key('bar') and not data.has_key('foo'):
            instance.foo = get_foo(data['bar']) if data['bar'] else None
        return instance

This latter one errors out obviously complaining that the Person model doesn't have a bar field. Which is true. How to solve this problem? I can handle setting/getting the foo and bar in the serializer's to_representation and to_internal_value. I just want a way in DRF to specify a field, which only exists on the REST API side, and doesn't have a field associated on the model end. I can take care of the transformations. And that field should be read-write, otherwise I could just solve it with a SerializerMethodField.

Upvotes: 2

Views: 1838

Answers (2)

Glyn Jackson
Glyn Jackson

Reputation: 8354

I'm unsure of your usecase, it seems a little odd, but why don't you just add a property on your model. For example,

   @property
    def bar(self):
        """
        Used for DRF only.
        """
        return None # or self.foo

Then add it to your fields in the serializer (no need to use PlaceholderCharField)

fields = ('id', 'foo', 'bar') 

The rest will work as you have it.

Upvotes: 2

Ross Rogers
Ross Rogers

Reputation: 24230

Tweak your first attempt a little and you can alias that field, no problem:

class PersonSerializer(serializers.ModelSerializer):
    #                 vvvvvvvvv
    bar = serializers.CharField(
       source='foo', required=False
    )# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    class Meta:
        model = Person
        fields = ('id', 'foo', 'bar')

I'm still using DRF 2.4.4 for the better nested, multi-object nested serializer support, but I use that method for aliasing a URL and its associate object all the time to deal with the way Angular JS compares objects in some of its controls. e.g.

class SensorSerializer(serializers.HyperlinkedModelSerializer):
    location_obj = SensorLocationSerializer(source='location',read_only=True,required=False)
    class Meta:
        model = Sensor
        fields = ('url', 'id', 'name', 'serial_number', 'location', 
            'location_obj', 'active')

Upvotes: 3

Related Questions