Django Rest Framework receive primary key value in

2019-05-05 18:42发布

问题:

I'm not completely sure that the title of my question is as specific as I wanted it to be, but this is the case:

I have a HyperlinkedModelSerializer that looks like this:

class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
    carpool = SchoolBuildingCarpoolSerializer()

    class Meta:
        model = ParentArrival

As you can see the carpool is defined as a nested serializer object and what I want is to be able to make a POST request to create a ParentArrival in this way (data as application/json):

{
    ...
    "carpool": "http://localhost:8000/api/school-building-carpools/10/"
    ...
}

And receive the data in this way:

{
    "carpool": {
        "url": "http://localhost:8000/api/school-building-carpools/10/"
        "name": "Name of the carpool",
        ...
    }
}

Basically, I'm looking for a way to deal with nested serializers without having to send data as an object (but id or url in this case) in POST request, but receiving the object as nested in the serialized response.

回答1:

I have been happy with my previous solution, but decided to look again and I think I have another solution that does exactly what you want.

Basically, you need to create your own custom field, and just overwrite the to_representation method:

class CarpoolField(serializers.PrimaryKeyRelatedField):
    def to_representation(self, value):
        pk = super(CarpoolField, self).to_representation(value)
        try:
           item = ParentArrival.objects.get(pk=pk)
           serializer = CarpoolSerializer(item)
           return serializer.data
        except ParentArrival.DoesNotExist:
           return None

    def get_choices(self, cutoff=None):
        queryset = self.get_queryset()
        if queryset is None:
            return {}

        return OrderedDict([(item.id, str(item)) for item in queryset])

class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
    carpool = CarpoolField(queryset=Carpool.objects.all())

    class Meta:
        model = ParentArrival

This will allow you to post with

{
     "carpool": 10
}

and get:

{
    "carpool": {
        "url": "http://localhost:8000/api/school-building-carpools/10/"
        "name": "Name of the carpool",
        ...
    }
}


回答2:

It's simple. As you know, Django appends "_id" to the field name in the ModelClass, and you can achieve it in the SerializerClass, and the original filed can also be achieved. All you have to do is like this

class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
    # ...
    carpool_id = serializers.IntegerField(write_only=True)
    carpool = SchoolBuildingCarpoolSerializer(read_only=True)
    # ...
    class Meta:
        fields = ('carpool_id', 'carpool', ...)

And use carpool_id in POST request.



回答3:

One way to do it is to keep 'carpool' as the default you get from DRF, and then add a read-only field for the nested object.

Something like this (I don't have time to test the code, so consider this pseudo-code. If you cannot get it to work, let me know, and will spend more time):

class ParentArrivalSerializer(serializers.HyperlinkedModelSerializer):
    carpool_info = serializers.SerializerMethodField(read_only=True)

    class Meta:
        model = ParentArrival
        fields = ('id', 'carpool', 'carpool_info',)

    def get_carpool_info(self, obj):
         carpool = obj.carpool
         serializer = SchoolBuildingCarpoolSerializer(carpool)
         return serializer.data

If your only nested object is carpool, I would also suggest switching to the regular ModelSerializer so carpool only shows the ID (10) and the nested object then can show the URL.

 class ParentArrivalSerializer(serializers.ModelSerializer):
     ....

and then if it all works, you will be able to do a post with

{
     "carpool": 10
}

and your get:

{
    "carpool": 10
    "carpool_info": {
        "url": "http://localhost:8000/api/school-building-carpools/10/"
        "name": "Name of the carpool",
        ...
    }
}

I have never found another solution, so this is the trick I have used several times.



回答4:

How about overriding the to_representation method?

class YourSerializer(serializers.ModelSerializer):

    class Meta:
        model = ModelClass
        fields = ["id", "foreignkey"]

    def to_representation(self, instance):
        data = super(YourSerializer, self).to_representation(instance)
        data['foreignkey'] = YourNestedSerializer(instance.foreignkey).data
        return data