I have a model that uses a generic foreign key using "content_type" field to store content type and "object_id" to store object ID. This model needs to be manipulated with a CRUD API and I am using DRF. I have a serializer for the model, but I hit a bit of a problem. If I just add content_type to the list of fields like this
class MySerializer(ModelSerializer): class Meta: fields = ('name', 'state', 'content_type', 'object_id')
The serializer sets the JSON representation to the ID of the ContentType model instance. The users of the API don't know these IDs and I do not want to expose ContentType model with yet another API. The API users do know the type of object they want to link though, so they can send a content type name. So, I have defined the serializer like this
class MySerializer(ModelSerializer): content_type = serializers.CharField(source="content_type.name") class Meta: fields = ('name', 'state', 'content_type', 'object_id')
Serializing the model works just fine. If I use the generic relationship to link a User instance, I get something like {'name': 'test', 'state': 'NY', 'content_type': 'user', 'object_id': 123}
. But when I submit a PUT or POST request with the JSON structure, DRF converts this to something like {'name': 'test', 'state': 'NY', 'content_type': {'name': {'name': 'user'}}, 'object_id': 123}
. I could write something like
def create(self, validated_data): ct_model = validated_data['content_type']['name']['name'] validated_data['content_type'] = ContentType.objects.get(model=ct_model) return MyModel.objects.create(**validated_data)
But it seems arbitrary and fragile. What is the right way to handle this situation?
Update #1: For the moment, I have solved the problem by overriding to_internal_value() with this code
def to_internal_value(self, data): content_type = data.get('content_type', None) validated_data = super().to_internal_value(data) try: validated_data['content_type'] = ContentType.objects.get(model=content_type) except ContentType.DoesNotExist: raise serializers.ValidationError("invalid content type %s" % content_type) return validated_data
It seems like a kind of an ugly hack to have to do to display a related object.