Django Rest Framework receive primary key value in POST and return model object as nested serializer
Solution 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",
...
}
}
Solution 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.
Solution 3
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
Solution 4
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.
Cristian Rojas
Web developer and photographer based in Bogota, Colombia
Updated on June 16, 2022Comments
-
Cristian Rojas almost 2 years
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 aParentArrival
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.
-
Cristian Rojas about 8 yearsI forgot to mention that I already know this solution (and I'm currently using it on this project), but I'm looking for a more "elegant" way to do this without having to put an extra read_only field for each nested.
-
Cristian Rojas about 8 yearsIt works like a charm! I had to do some other hacks because my API is Hyperlinked but your solution does the trick!
-
Anuj over 7 yearsCan we create a generic field like this, where we can pass serializer while initialization?
-
chukkwagon almost 7 yearsCool solution. It's worth noting, however, that this means you can only POST the primary key and not the rich object. This is frustrating in an API scenario because it means you can get a resource from your django app (angular:
$http.get('/my/resource');
, but then you can't save that object without changing the embedded object back into an int. -
Aaron McMillin almost 7 yearsWhen I did this my value was a PKOnlyObject and I had to do object = MyModel.get(pk=value.pk) to be able to instantiate the Serializer.
-
Aaron McMillin almost 7 yearsAlso this tanks when viewing the interactive API pages, because it can't use the dictionary response from
to_representation
when building the select widget. So you have to overrideget_choices()
-
Aaron McMillin almost 7 yearsI had to go with this solution. The "more clever" way with a custom Field class fell over in more ways than I can count.
-
ramwin over 6 yearsI think this is an elegant way and you won't have to make your own field. Since you don't add a new field like
foreignkey_info
, this may be more convenient for front-end user to encapsulate the object for this data structure. -
Little Brain about 5 yearsThank you! Clear and straightforward. I also had to add 'carpool_id' to the 'fields' list in class Meta, and I used serializers.UUIDField because my IDs are UUIDs not integers. It works and I like this answer because it works directly in the database and I haven't had to change any endponts.
-
Dequn about 5 years@LittleBrain Thanks , I add fileds in Meta class.
-
Genarito about 4 yearsThis should be the accepted answer! It's simpler, and is cleaner as It doesn't need to create a neew field and change API requests. Thank you so much!
-
Dennis Gathagu almost 4 yearsThis is the most sensible ways to do it, should be the accepted answer
-
Ondřej Kolín about 3 yearsI recommend scrolling a bit down, there is another great answer which makes it even more easier just with a custom to_representation function!