I have several a base model with several control fields. Among them a location fields compound from lat, lon, accuracy, provider and client time. Most of my writable models (and hence resources) are inheriting from this base model.
I'm trying to make DRF serialize the location related fields in a nested "location" field. For example,
{
"id": 1,
"name": "Some name",
"location": {
"lat": 35.234234,
"lon": 35.234234,
"provider": "network",
"accuracy": 9.4,
}
}
I'ts important to remember that these fields are regular (flat) fields on the base model.
I've investigated and found several options
Create a custom field and by overriding "get_attribute" create the nested representation. I don't like this solution because i lose some of the benefits of the model serializer such as validation.
Create a nested resource called Location. I guess i could make it work by adding a property by the same name on the model but again, no validations.
So my question is, What is the best way to nest ( or group ) several fields in a DRF serializer ?
DRF 3.0.0, Django 1.7
EDIT:
Building on top of @Tom Christie answer this is what i came up with (simplified)
# models.py
class BaseModel(models.Model):
id = models.AutoField(primary_key=True)
lat = models.FloatField(blank=True, null=True)
lon = models.FloatField(blank=True, null=True)
location_time = models.DateTimeField(blank=True, null=True)
location_accuracy = models.FloatField(blank=True, null=True)
location_provider = models.CharField(max_length=50, blank=True, null=True)
@property
def location(self):
return {
'lat': self.lat,
'lon': self.lon,
'location_time': self.location_time,
'location_accuracy': self.location_accuracy,
'location_provider': self.location_provider
}
class ChildModel(BaseModel):
name = models.CharField(max_lengtg=10)
# serializers.py
class LocationSerializer(serializers.Serializer):
lat = serializers.FloatField(allow_null=True, required=False)
lon = serializers.FloatField(allow_null=True, required=False)
location_time = serializers.DateTimeField(allow_null=True, required=False)
location_accuracy = serializers.FloatField(allow_null=True, required=False)
location_provider = serializers.CharField(max_length=50,allow_null=True, required=False)
class BaseSerializer(serializers.ModelSerializer):
def create(self,validated_data):
validated_data.update(validated_data.pop('location',{}))
return super(BaseSerializer,self).create(validated_data)
def update(self, instance, validated_data):
location = LocationSerializer(data=validated_data.pop('location',{}), partial=True)
if location.is_valid():
for attr,value in location.validated_data.iteritems():
setattr(instance,attr,value)
return super(BaseSerializer,self).update(instance, validated_data)
class ChildSerializer(BaseSerializer):
location = LocationSerializer()
class meta:
model = ChildModel
fields = ('name','location',)
I've tested with valid/invalid post/patch and it worked perfectly.
Thanks.
DRF provides a Serializer class that gives you a powerful, generic way to control the output of your responses, as well as a ModelSerializer class that provides a useful shortcut for creating serializers that deal with model instances and querysets.
In function-based views, we can pass extra context to serializer with “context” parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with “self. context”. From example, to get “exclude_email_list” we just used code 'exclude_email_list = self.
What is class Meta in Django serializer? It's a standard pattern to attach configuration (metadata) to a set of fields that make up the model, form, or in this case, serializer.03-Mar-2020.
I'd suggest simply using explicit serializer classes, and writing the fields explicitly. It's a bit more verbose, but it's simple, obvious and maintainable.
class LocationSerializer(serializers.Serializer):
lat = serializers.FloatField()
lon = serializers.FloatField()
provider = serializers.CharField(max_length=100)
accuracy = serializers.DecimalField(max_digits=3, decimal_places=1)
class FeatureSerializer(serializers.Serializer):
name = serializers.CharField(max_length=100)
location = LocationSerializer()
def create(self, validated_data):
return Feature.objects.create(
name=validated_data['name'],
lat=validated_data['location']['lat'],
lon=validated_data['location']['lat'],
provider=validated_data['location']['provider'],
accuracy=validated_data['location']['accuracy']
)
def update(self, instance, validated_data):
instance.name = validated_data['name']
instance.lat = validated_data['location']['lat']
instance.lon = validated_data['location']['lat']
instance.provider = validated_data['location']['provider']
instance.accuracy = validated_data['location']['accuracy']
instance.save()
return instance
There's a bunch of ways you could use a ModelSerializer
instead, or ways to keep the create
and update
methods a little shorter, but it's not clear that the extra indirection you'd be giving yourself is at all worth it.
We almost always use completely explicit serializer classes for APIs that we're building.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With