Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework - How to nest several fields in a serializer?

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

  1. 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.

  2. 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.

like image 970
haki Avatar asked Jan 28 '15 08:01

haki


People also ask

What is nested serializer in Django REST Framework?

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.

How do you pass extra context data to Serializers in Django REST Framework?

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 Meta class in serializer?

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.


1 Answers

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.

like image 172
Tom Christie Avatar answered Sep 28 '22 02:09

Tom Christie