Django Rest Framework: allow a serializer field to be created, but not edited

Question:

Right now, DRF’s read_only argument on a Serializer constructor means you can neither create nor update the field, while the write_only argument on a Serializer constructor allows the field to be created OR updated, but prevents the field from being output when serializing the representation.

Is there any (elegant) way to have a Serializer field that can be created, exactly once, when the model in question is created (when the create() is called on the Serializer), but cannot that later be modified via update?

NB: Yes, I’ve seen this solution, but honestly I find it ugly and un-Pythonic. Is there a better way?

Asked By: ubadub

||

Answers:

I don’t think there’s any, you either specify it like that or make your own serializer, inheriting from DRF’s serializer.

Answered By: Sumanto Dinar

By “not elegant”, I’m assuming you only want one serializer for both creates and updates. You could perhaps consider overriding the update method of your serializer and remove the create_only_field from validated_data before saving:

class MySerializer(serializers.ModelSerializer):

    def update(self, instance, validated_data):
        validated_data.pop('create_only_field')
        return super().update(instance, validated_data)

    class Meta:
        model = MyModel
        fields = ('id', 'field_one', 'field_two', 'create_only_field')

You would, however, have to supply the old (or some) field value when updating your model.

Answered By: slider
class TodoModifySerializer(ModelSerializer):

    def to_internal_value(self, data):
        data = super(TodoModifySerializer, self).to_internal_value(data)
        if self.instance:
            # update
            for x in self.create_only_fields:
                data.pop(x)
        return data

    class Meta:
        model = Todo
        fields = ('id', 'category', 'title', 'content')
        create_only_fields = ('title',)

you can do it in to_internal_value method by remove this data when update

Answered By: Ykh

In order to make the field REQUIRED and read-only on update I’ve handled it on field validation.

class MyUserProfileSerializer(serializers.ModelSerializer):

    username = serializers.CharField(source='user.username', required=True)
    first_name = serializers.CharField(source='user.first_name')
    last_name = serializers.CharField(source='user.last_name')

    class Meta:
        model = UserProfile
        fields = ['user', 'username', 'first_name', 'last_name', 'phone']
    
        read_only_fields = []

    def validate_username(self, value):
        if not self.instance and not value:      ## Creation and value not provided
            raise serializers.ValidationError('The username is required on user profile creation.')
        elif value and self.instance != value:   ## Update and value differs from existing
            raise serializers.ValidationError('The username cannot be modified.') 
    return value

You can also make the field optional in case of edition, but you need to set the field as required=False and do validation in validate() method, since validate_username() wouldn’t be called in creation if not included in the payload.

Answered By: lembon