5
votes

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.

1
Did you try solution from the docs django-rest-framework.org/api-guide/relations/… ? - mateuszb
The solution seems to be to define a custom related field class. I will look into that, but that seems convoluted. - Mad Wombat
If you have answered your own question, post it as an answer and not as update. - Ivan Anishchuk
This is not an answer I am looking for. I came up with an ugly hack that seems to resolve the immediate issue I am having. It is fragile and not efficient. I am still looking for a better way of doing this. - Mad Wombat

1 Answers

0
votes

You'll probably have to go custom, providing your own field class with to_representation and to_internal_value methods to serialize such relations. It's not that complicated, give it a try.

Consider using uri notations /api/resource/object_id as your serialization format.

Converting content types to resource uris and back can be a bit challenging (if you have more than a few possible types) but I'm sure there must be some easy way to do it.