Django rest framework create-only serializer field
Question:
I’m have a Django model that serves as a request description. It is created to issue a request by a REST client, serves to record the tasks current status, and record historical requests received by clients.
This model has a few fields that are used to fine-tune and control the requested task (say, a target object and the type of action). Obviously, I’d like the client to control those fields on object creation but not afterwards (you can’t change the object once the task started running).
I was hoping for something similar to serializers.ReadOnlyField
, so I could have something similar to this:
class TaskSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
task_id = serializers.ReadOnlyField()
target_object = serializers.CreateOnlyField()
but couldn’t find it in the documentation or google.
Answers:
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
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
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')
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)
I’m have a Django model that serves as a request description. It is created to issue a request by a REST client, serves to record the tasks current status, and record historical requests received by clients.
This model has a few fields that are used to fine-tune and control the requested task (say, a target object and the type of action). Obviously, I’d like the client to control those fields on object creation but not afterwards (you can’t change the object once the task started running).
I was hoping for something similar to serializers.ReadOnlyField
, so I could have something similar to this:
class TaskSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
task_id = serializers.ReadOnlyField()
target_object = serializers.CreateOnlyField()
but couldn’t find it in the documentation or google.
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
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
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')
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)