Reputation: 699
I have the following django model:
class Person(models.Model):
name = models.CharField()
location = models.PointField()
And I want to create a serialiser/deserialiser for this model. However the JSON object hat I receive is the following:
{
"userList":[
{
"username": "Foo",
"lat":40.875736,
"lon":8.94382834,
},
{
"username": "Bar",
"lat":40.875736,
"lon":8.94382834,
},
]
}
Serialiser
class PersonListSerializer(serializers.PersonSerializer):
username = serializers.CharField()
lat = serializers.FloatField()
lon = serializers.FloatField()
class PersonSerializer(serializers.ModelSerializer):
personList = PersonListSerializer
class Meta:
model = Person
Is it possible to create a custom serializer/deserialiser to deal with this structure without having to create an additional model (PersonList)?
Thanks in advance.
Upvotes: 2
Views: 1820
Reputation: 15408
Well, this took a while and it was definitely a nice learning experience.
Your problem can be split into two separate ones:
to_internal_value()
ListSerializer
that does not accept and return a list, but a dictionary with a single nested field that contains the actual list.You can do it without touching the model at all.
class ListDictSerializer(serializers.ListSerializer):
def get_field_name(self):
if not hasattr(self.Meta, 'field_name'):
raise ValueError('ListDictSerializer requires defining Meta.field_name overriding get_field_name()')
return self.Meta.field_name
def to_internal_value(self, data):
field_name = self.get_field_name()
return super(ListDictSerializer, self).to_internal_value(data[field_name])
def to_representation(self, data):
field_name = self.get_field_name()
return ReturnDict({
field_name: super(ListDictSerializer, self).to_representation(data)
}, serializer=self
)
@property
def data(self):
# skip over the ListSerializer to get the real data without the
# ReturnList
ret = super(serializers.ListSerializer, self).data
return ReturnDict(ret, serializer=self)
class PersonListSerializer(ListDictSerializer):
class Meta:
field_name = 'userList'
class PersonSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = PersonListSerializer
model = Person
fields = ('username', 'lat', 'lon')
username = serializers.CharField(source='name')
lat = serializers.SerializerMethodField(method_name='get_latitude')
lon = serializers.SerializerMethodField(method_name='get_longitude')
def get_latitude(self, instance):
return instance.location.coords[1]
def get_longitude(self, instance):
return instance.location.coords[0]
def to_internal_value(self, data):
return ReturnDict({
'name': data.get('username'),
'location': Point(data['lat'], data['lon']),
}, serializer=self)
Note that DRF allows you to export arbitrary model properties (not only fields) read/write. That is, we could get away with defining @property
lat
and lon
on the model with appropriate getters and setters. However, geos objects like Point
are immutable after creation, so you cannot selectively set a single coordinate on an existing object.
Upvotes: 5