roob
roob

Reputation: 2539

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?

Upvotes: 2

Views: 1952

Answers (1)

Hugo Brilhante
Hugo Brilhante

Reputation: 606

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)

Upvotes: 3

Related Questions