简体   繁体   中英

'Dynamic' fields in DRF serializers

My aim is to build endpoint which will surve to create objects of model with GenericForeignKey. Since model also includes ContentType, the actual type of model which we will reference is not known before object creation.

I will provide an example:

I have a 'Like' model which can reference a set of other models like 'Book', 'Author'.

class Like(models.Model):
    created = models.DateTimeField()
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

Serializer may look like this:

class LikeSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.Like
        fields = ('id', 'created', )

What I want to achieve is to determine type of Like based on keys passed in request. The problem is that DRF do not pass those keys from request if they were not expilictly specified in Serializer fields. For example, POST request body contains:

{
    "book":2
}

I want to do next

def restore_object(self, attrs, instance=None)
    if attrs.get('book', None) is not None:
        # create Like instance with Book contenttype
    elif attrs.get('author', None) is not None:
        # create Like instance with Author contenttype

In this case first if clause will be executed. As you can see, The type determined based on key passed in request, without specifying special Field.

Is there any way to achieve this?

Thanks

You might try instantiating your serializer whenever your view is called by wrapping it in a function (you make a serializer factory):

def like_serializer_factory(type_of_like):
    if type_of_like == 'book':
        class LikeSerializer(serializers.ModelSerializer):
            class Meta:
                model = models.Like
                fields = ('id', 'created', )
            def restore_object(self, attrs, instance=None):
                # create Like instance with Book contenttype
    elif type_of_like == 'author':
        class LikeSerializer(serializers.ModelSerializer):
            class Meta:
                model = models.Like
                fields = ('id', 'created', )
            def restore_object(self, attrs, instance=None):
                # create Like instance with Author contenttype
    return LikeSerializer

Then override this method in your view:

def get_serializer_class(self):
    return like_serializer_factory(type_of_like)

Solution 1

Basically there is a method you can add on GenericAPIView class called get_context_serializer By default your view, request and format class are passed to your serializer DRF code for get_context_serializer

def get_serializer_context(self):
    """
    Extra context provided to the serializer class.
    """
    return {
        'request': self.request,
        'format': self.format_kwarg,
        'view': self
    }

you can override that on your view like this

def get_serializer_context(self):
    data = super().get_serializer_context()
    # Get the book from post and add to context
    data['book'] = self.request.POST.get('book')
    return data

And use this on your serializer class

def restore_object(self, attrs, instance=None):
    # Get book from context to use
    book = self.context.get('book', None)
    author = attrs.get('author', None)
    if book is not None:
        # create Like instance with Book contenttype
        pass
    elif author is not None:
        # create Like instance with Author contenttype
        pass

Solution 2

Add a field on your serializer

class LikeSerializer(serializers.ModelSerializer):
    # New field and should be write only, else it will be
    # return as a serializer data
    book = serializers.IntegerField(write_only=True)
    class Meta:
        model = models.Like
        fields = ('id', 'created', )
    
    def save(self, **kwargs):
        # Remove book from validated data, so the serializer does
        # not try to save it
        self.validated_data.pop('book', None)
        
        # Call model serializer save method
        return super().save(**kwargs)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM