djangodjango-rest-frameworkdjango-contenttypesdjango-generic-relations

Serialize Generic relationships with Django Rest Framework, with write support


I have the following model:

class TaggedItem(models.Model):
    tag = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

I am trying to serialize this model in a way that I can assign the content object via an API endpoint

So far I have done this:

class TaggedItemSerializer(serializers.ModelSerializer):
    content_object = serializers.RelatedField(read_only=True)

    class Meta:
        model = TaggedItem

However this is readonly. If I remove the read_only parameter, I must specify the queryset for the field. However, I have many different model types for this generic relationship. It seems like I am duplicating code if I specify all the possible model types both within the serializer and elsewhere in the model.

I could also set the content object through the object_id and content_type fields, but when I do this I get an error.

For example:

{
    ...
    object_id: 1,
    content_type: 'auth.User'
}

returns a 400 response with "detail": "JSON parse error - Expected object or value"

How can I make this content_object writable via the DRF api?


Solution

  • Override the .to_internal_value , .validate and .create methods like this:

    from django.apps import apps
    
    class TaggedItemSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = TaggedItem
            read_only_fields = ('content_type', 'object_id', 'content_object')
    
        def to_internal_value(self, data):
            object_id = data.pop('object_id')
            content_type = data.pop('content_type')
    
            ret = super(ConfigCalcSerializer, self).to_internal_value(data)
    
            ret['object_id'] = object_id
            ret['content_type'] = content_type
    
            return ret
    
        def validate(self, data):
            object_id = data.pop('object_id')
            content_type = data.pop('content_type')
    
            Model = apps.get_model(content_type)
    
            try:
                content_object = Model.objects.get(id=object_id)
            except Model.DoesNotExist:
                raise serializers.ValidationError('Not found')
            else:
                data['content_object'] = content_object
    
            return data
    
        def create(self, validate_data):
            return TaggedItem.objects.create(**validate_data)