Django REST Framework and generic relations

2019-03-25 01:44发布

问题:

Problem

I have a model with the following standard generic foreign key fields:

content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
event_object = generic.GenericForeignKey('content_type', 'object_id')

According to REST framework's docs, I can do the following to serialise this correctly:

class WhateverSerializer(serializers.ModelSerializer):
    event_object = serializers.RelatedField(source='event_object')

This works fine, however in two other related situations, I can't get things working:

  1. I would like to use HyperlinkedRelatedField. This field requires the view_name argument, something I can't declare since the view name varies with the related model. I solved this by using SerializerMethodField, instantiating a HyperlinkedIdentityField at runtime and returning its field_to_native method (see snippet below). This does not feel very elegant.
  2. I would like to nest the related object directly in the serialisation by saying event_object = SoAndSoSerializer(source='event_object'). The only solution I can see is to walk every *Serializer I have defined and check which has the correct model, then use that. Again, this does not feel very elegant.

Questions

is HyperlinkRelatedField meant to work across a generic relationship? Am I just making a mistake? Is there an obvious solution to picking the right *Serializer that I'm missing?

Code Snippet

The inelegant solution mentioned in bullet point 1 above:

class WhateverSerializer(DefaultSerializer):

    event_object_url = serializers.SerializerMethodField('get_related_object_url')
    # ...

    def get_related_object_url(self, obj):
        obj = obj.event_object
        default_view_name = '%(model_name)s-detail'
        format_kwargs = {
            'app_label': obj._meta.app_label,
            'model_name': obj._meta.object_name.lower()
        }
        view_name = default_view_name % format_kwargs
        s = serializers.HyperlinkedIdentityField(source=obj, view_name=view_name)
        s.initialize(self, None)
        return s.field_to_native(obj, None)

回答1:

Your right, REST framework doesn't support those use cases, and its not obvious to me what the design would look like if it did. You'd probably need an implicit registry of model->view (for the hyperlinked case) and model-> serializer (for the nested case) which I don't think I'd be very keen on.

The simplest way to do what you need is probably to subclass ManyRelatedField and create a custom field type, overriding to_native(self, obj) to serialize each object in the set exactly the way you want it.