4
votes

I have a model that represents a house:

class House(models.Model):
    name = models.CharField(...)
    long = models.FloatField(...)
    lat = models.FloatField(...)

and a serializer to return a list of houses in their most basic representation:

class HouseSerializer(serializers.ModelSerializer):
    class Meta:
        model = House
        fields = ('id', 'name')    

and the view

class HouseList(generics.ListAPIView):
    queryset = House.objects.all()
    serializer_class = HouseSerializer

this works fine. I can visit /api/house/ and I see a json list of houses:

{ 
    'id': 1,
    'name': 'Big House'
},
{
    'id': 1
    'name': 'Small House',
}...

Now I want to create a second view/resource at /api/maps/markers/ that returns my houses as a list of Google-Map-Friendly markers of the format:

{ 
    'id': 1,
    'long': ...,
    'lat': ...,
    'houseInfo': {
        'title': "Big House",
    }
} ...

I can foresee two approaches:

  • perform this as a separate serializer (using the same view as before) and mapping out the alternative field layout.
  • perform this as a separate view (using the same serializer as before) and simply layout the fields before creating a Response

but in neither approach am I clear on how to go about it nor which approach is preferable?

2

2 Answers

6
votes

Answer 1

Looks to me like you need both - different view and serializer.

Simply because the view endpoint is not a sub-url of the first one, so they are not related - different view, even if they use the same model.

And different serializer - since you have a different field layout.

Not really sure how complicated is your case, but any code duplication can probably be solved by mixins anyway.

Answer 2

Depending on the use case:

  • if you also need to write data using the same struct, you need to define your own field class and handle the parsing correctly
  • if it's just reading data, you should be fine with this:

    class HouseGoogleSerializer(HouseSerializer):
        houseInfo = serializers.SerializerMethodField('get_house_info')
    
        class Meta:
            model = House
            fields = [...]
    
        def get_house_info(self, obj):
            return {'title': obj.name}
    

    where HouseSerializer is your base house serializer.

1
votes

this code come from a running project and offer somethig more that you ask but can easily adapted for your need if you want remove some features. The current implemetation allow you:

  • use only one url one serializer and one view
  • choose the output using query string param (?serializer=std)

how to use in your code:

Case 1 (one url with ability to choose the serializer via querystring)

class HouseSerializer(HouseSerializer):
    houseInfo = serializers.SerializerMethodField('get_house_info')

    class Meta:
        model = House

    def get_house_info(self, obj):
        return {'title': obj.name}


class HouseList(DynamicSerializerMixin, generics.ListAPIView):
    queryset = House.objects.all()
    serializer_class = HouseSerializer
    serializers_fieldsets = {'std': ('id', 'name'),
                              'google' : ('id', 'long', 'lat', 'houseInfo')}

Case 2 (different views)

class HouseList(DynamicSerializerMixin, generics.ListAPIView):
    queryset = House.objects.all()
    serializer_class = HouseSerializer
    serializers_fieldsets = {'std': ('id', 'name')}

class GoogleHouseList(DynamicSerializerMixin, generics.ListAPIView):
    queryset = House.objects.all()
    serializer_class = HouseSerializer
    serializers_fieldsets = {'std': ('id', 'long', 'lat', 'houseInfo')}

==============

def serializer_factory(model, base=BaseHyperlinkedModelSerializer,
                       fields=None, exclude=None):
    attrs = {'model': model}
    if fields is not None:
        attrs['fields'] = fields
    if exclude is not None:
        attrs['exclude'] = exclude

    parent = (object,)
    if hasattr(base, 'Meta'):
        parent = (base.Meta, object)
    Meta = type(str('Meta'), parent, attrs)
    if model:
        class_name = model.__name__ + 'Serializer'
    else:
        class_name = 'Serializer'
    return type(base)(class_name, (base,), {'Meta': Meta, })


class DynamicSerializerMixin(object):
    """
    Mixin that allow to limit the fields returned
    by the serializer.

    Es.
        class User(models.Model):
            country = models.ForeignKey(country)
            username = models.CharField(max_length=100)
            email = models.EmailField()

        class UserSerializer(BaseHyperlinkedModelSerializer):
            country = serializers.Field(source='country.name')


        class MyViewSet(DynamicSerializerViewSetMixin, BaseModelViewSet):
            model = User
            serializer_class = UserSerializer
            serializers_fieldsets = {'std': None,
                                      'brief' : ('username', 'email')
                                      }
    this allow calls like

        /api/v1/user/?serializer=brief

    """
    serializers_fieldsets = {'std': None}
    serializer_class = ModelSerializer

    def get_serializer_class(self):
        ser = self.request.QUERY_PARAMS.get('serializer', 'std')

        fields = self.serializers_fieldsets.get(ser, 'std')

        return serializer_factory(self.model,
                                  self.serializer_class,
                                  fields=fields)