Search code examples
pythondjangodjango-rest-frameworkgeneric-relationship

set contenttype by name in generic relation in django rest framework


class Foo(models.Model):
    bar = models.CharField(max_length=300)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')


class FooSerializer(serializers.ModelSerializer):
    class Meta:
       model = Foo

class FooViewSet(viewsets.ModelViewSet):
    model = Foo
    serializer_class = FooSerializer

I can now post data to the viewset that looks like this:

{
    bar: 'content',
    content_type: 1
    object_id: 5
}

The only thing that's bugging me is that the frontend would have to be aware of the contenttype id's

Instead I want to be able to post the content_types name like 'User' as content_type and have the backend determine the id.


Solution

  • You could customize WritableField to map contenttype id to 'app_label.model' string:

    class ContentTypeField(serializers.WritableField):
        def field_from_native(self, data, files, field_name, into):
            into[field_name] = self.from_native(data[field_name])
    
        def from_native(self, data):
            app_label, model = data.split('.')
            return ContentType.objects.get(app_label=app_label, model=model)
    
        # If content_type is write_only, there is no need to have field_to_native here.
        def field_to_native(self, obj, field_name):
            if self.write_only:
                return None
            if obj is None:
                return self.empty
            ct = getattr(obj, field_name)
            return '.'.join(ct.natural_key())
    
    
    class FooSerializer(serializers.ModelSerializer):
        content_type = ContentTypeField()
        # ...
    

    You may want to do a second mapping to limit choices of contenttype and to avoid unveiling of your app/model names:

    CONTENT_TYPES = {
      'exposed-contenttype': 'app_label.model'
    }
    
    class ContentTypeField(...):
        def from_native(self, data):
            if data not in CONTENT_TYPES:
                raise serializers.ValidationError(...)
            app_label, model = CONTENT_TYPES[data].split('.')
            # ...