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.