Reputation: 1380
I'm trying to handle POST requests in my API endpoint using a different serializer than the one derived from Model and used for GET or PUT requests. The format of the POSTed messages is different from the Model and from GET/PUT and must be pre-processed before storing to the database.
As a demo of my problem I made a very simple model and the corresponding API view and serializer:
class Message(models.Model):
message = models.CharField(max_length = 500)
class MessageSerializer(serializers.ModelSerializer):
class Meta:
model = Message
fields = ('message',)
class MessageViewSet(viewsets.ModelViewSet):
queryset = Message.objects.all().order_by('-pk')
serializer_class = MessageSerializer
That works well. Then I tried to override the MessageViewSet.create() to handle POST requests differently.
class MessageSerializer_FromTo(serializers.Serializer):
sender = serializers.EmailField()
recipient = serializers.EmailField()
def create(self, validated_data):
message = "Message from <{sender}> to <{recipient}>".format(**validated_data)
return Message(message)
class MessageViewSet(viewsets.ModelViewSet):
queryset = Message.objects.all().order_by('-pk')
serializer_class = MessageSerializer
# Handle POST requests differently
def create(self, request, format=None):
message = MessageSerializer_FromTo(data = request.data)
if message.is_valid():
message.save()
return Response(message.data, status=status.HTTP_201_CREATED)
return Response(message.errors, status=status.HTTP_400_BAD_REQUEST)
Essentially I want to pass this JSON to POST /api/messages/
{"sender": "[email protected]", "recipient": "[email protected]"}
GET /api/messages/1/ should return
{"message": "Message from <[email protected]> to <[email protected]>"}
However the POST fails with this message:
Internal Server Error: /api/messages/
Traceback (most recent call last):
File ".../rest_framework/fields.py", line 441, in get_attribute
return get_attribute(instance, self.source_attrs)
File ".../rest_framework/fields.py", line 100, in get_attribute
instance = getattr(instance, attr)
AttributeError: 'Message' object has no attribute 'sender'
During handling of the above exception, another exception occurred:
[...]
AttributeError: Got AttributeError when attempting to get a value for field `sender` on serializer `MessageSerializer_FromTo`.
The serializer field might be named incorrectly and not match any attribute or key on the `Message` instance.
Original exception text was: 'Message' object has no attribute 'sender'.
[26/Feb/2018 05:09:08] "POST /api/messages/ HTTP/1.1" 500 19059
This is just to demonstrate the problem obviously, I'm doing more complex things in my POST handler, but the error is like this.
Any idea how to achieve what I need? I.e. accept POST fields that are completely different from the Model fields?
Thanks!
UPDATE: The complete code is here: https://github.com/mludvig/drf-demo
Upvotes: 2
Views: 4049
Reputation: 546
To use different serializers depending on if the serializer will deserialize the data from the request or serialize it for the response, you can use drf-rw-serializers.
It has generic classes to help you build views like the Django REST Framework ones, but, instead of using only one serializer_class
, they use a read_serializer_class
and a write_serializer_class
.
You can read more instruction about installation and usage in the documentation.
Upvotes: 2
Reputation: 43096
You can get a different serializer depending on the method type by overriding the get_serializer_class
method of you viewset
class MessageViewSet(viewsets.ModelViewSet):
model = Message
def get_serializer_class(self):
serializers_class_map = {
'default': DefaultSerializer,
'create': CreateMessageSerializer,
}
return serializers_class_map.get(self.action, serializers_class_map['default'])
You can also overrides the to_representation
method of your CreateMessageSerializer
to get a different output
class CreateMessageSerializer(serializer.ModelSerializer):
class Meta:
model = Message
fields = (
'sender', 'recipient'
)
def to_representation(self, instance):
return {
'message': "Message from <{0.sender}> to <{0.recipient}>".format(instance)
}
Upvotes: 0
Reputation: 3827
problem is with in your serializer. You are just passing model class Message(message)
as the output of create function instead of Message object
class MessageSerializer_FromTo(serializers.Serializer):
sender = serializers.EmailField(write_only=True)
recipient = serializers.EmailField(write_only=True)
message = serializers.CharField(read_only=True, max_length = 500)
def create(self, validated_data):
message = "Message from <{sender}> to <{recipient}>".format(**validated_data)
return Message.objects.create(message=message)
Upvotes: 3