Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework return nested object using PrimaryKeyRelatedField

I am using DRF to expose some API endpoints.

# models.py

class Project(models.Model):
    ...
    assigned_to = models.ManyToManyField(
        User, default=None, blank=True, null=True
    )



# serializers.py

class ProjectSerializer(serializers.ModelSerializer):
    assigned_to = serializers.PrimaryKeyRelatedField(
        queryset=User.objects.all(), required=False, many=True)

    class Meta:
        model = Project
        fields = ('id', 'title', 'created_by', 'assigned_to')


# view.py

class ProjectList(generics.ListCreateAPIView):
    mode   = Project
    serializer_class = ProjectSerializer
    filter_fields = ('title',)

    def post(self, request, format=None):
        # get a list of user.id of assigned_to users
        assigned_to = [x.get('id') for x in request.DATA.get('assigned_to')]
        # create a new project serilaizer
        serializer = ProjectSerializer(data={
            "title": request.DATA.get('title'),
            "created_by": request.user.pk,
            "assigned_to": assigned_to,
        })
        if serializer.is_valid():
            serializer.save()
        else:
            return Response(serializer.errors,
                        status=status.HTTP_400_BAD_REQUEST)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

This all works fine, and I can POST a list of ids for the assigned to field. However, to make this function I had to use PrimaryKeyRelatedField instead of RelatedField. This means that when I do a GET then I only receive the primary keys of the user in the assigned_to field. Is there some way to maintain the current behavior for POST but return the serialized User details for the assigned_to field?

like image 872
Darwin Tech Avatar asked Nov 24 '13 21:11

Darwin Tech


2 Answers

I recently solved this with a subclassed PrimaryKeyRelatedField() which uses the id for input to set the value, but returns a nested value using serializers. Now this may not be 100% what was requested here. The POST, PUT, and PATCH responses will also include the nested representation whereas the question does specify that POST behave exactly as it does with a PrimaryKeyRelatedField.

https://gist.github.com/jmichalicek/f841110a9aa6dbb6f781

class PrimaryKeyInObjectOutRelatedField(PrimaryKeyRelatedField):
    """
    Django Rest Framework RelatedField which takes the primary key as input to allow setting relations,
    but takes an optional `output_serializer_class` parameter, which if specified, will be used to
    serialize the data in responses.

    Usage:
        class MyModelSerializer(serializers.ModelSerializer):
            related_model = PrimaryKeyInObjectOutRelatedField(
                queryset=MyOtherModel.objects.all(), output_serializer_class=MyOtherModelSerializer)

            class Meta:
                model = MyModel
                fields = ('related_model', 'id', 'foo', 'bar')

    """

    def __init__(self, **kwargs):
        self._output_serializer_class = kwargs.pop('output_serializer_class', None)
        super(PrimaryKeyInObjectOutRelatedField, self).__init__(**kwargs)

    def use_pk_only_optimization(self):
        return not bool(self._output_serializer_class)

    def to_representation(self, obj):
        if self._output_serializer_class:
            data = self._output_serializer_class(obj).data
        else:
            data = super(PrimaryKeyInObjectOutRelatedField, self).to_representation(obj)
        return data
like image 90
jmichalicek Avatar answered Oct 13 '22 08:10

jmichalicek


You'll need to use a different serializer for POST and GET in that case.

Take a look into overriding the get_serializer_class() method on the view, and switching the serializer that's returned depending on self.request.method.

like image 35
Tom Christie Avatar answered Oct 13 '22 08:10

Tom Christie