[Solved]-Django rest framework create-only serializer field

22👍

Just to expand on Wim’s answer, this is a way to select a different serialiser based on the incoming request method:

class RequestViewSet(viewsets.ModelViewSet): 
    serializer_class = RequestModelSerializer 
    model = Request 

    def get_serializer_class(self): 
        serializer_class = self.serializer_class 
        if self.request.method == 'POST': 
            serializer_class = SerializerWithoutCertainFields 
        return serializer_class

2👍

The answer of @fabio.sussetto put me on the right track. I think my answer is slightly prettier; I don’t specify the serializer on the class directly but only in get_serializer_class(). Also, I do not switch it based on the HTTP type (i.e. POST) but rather on the action, update, which I think is more declarative.

class RequestViewSet(viewsets.ModelViewSet): 
    model = Request 

    def get_serializer_class(self): 
        if self.action == 'update': 
            return serializer_class = SerializerWithoutCertainFields 
        return RequestModelSerializer

1👍

This can be achieved with one serializer by using to_internal_value method

class TaskSerializer(serializers.ModelSerializer):
    # Field settings here

    def to_internal_value(self, data):
        data = super().to_internal_value(data)
        # Remove target_object if serializer is updating object
        if self.instance:
            data.pop('target_object', None)
        return data

    class Meta:
        model = Task
        fields = ('owner', 'task_id', 'target_object')
👤TCFDS

0👍

could also be done with a combination of required=False and dropping the field value when updating like in this example:

class SectionSerializer(serializers.ModelSerializer):

    # do not require field lesson when updating
    lesson = serializers.PrimaryKeyRelatedField(queryset=Lesson.objects.all(), required=False)

    # do not allow changing the lesson field
    def update(self, instance, validated_data):
        validated_data.pop("lesson", None)
        return super().update(instance, validated_data)

0👍

The most simple and reusable solution I could find for it was to create a validator that allows creating but not updating:

class CreateOnlyValidator:
    """
    Prevent a field from being updated.
    """
    requires_context = True

    def __call__(self, value, serializer_field):
        instance = serializer_field.parent.instance

        if instance is not None:
            if value != getattr(instance, serializer_field.source):
                msg = "This field cannot be updated."
                raise exceptions.ValidationError(msg, code="create-only")

            # if the value is the same, no need to update it
            raise fields.SkipField()

        return value


class TaskSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    task_id = serializers.ReadOnlyField()
    target_object = serializers.PrimaryKeyRelatedField(
        queryset=Request.objects.all()
        validators=[CreateOnlyValidator()],

    )

Adding the validation logic to a validator allows using it in any field type.

Leave a comment