0

I'm have a dynamic dataset I am trying to use with the Django REST Framework by dynamically creating serializer, but it doesn't seem to process the dynamic attributes I add.

Here is the code:

-- views.py
class DynamicReadings(generics.ListAPIView):
    def get_serializer_class(self):
        site = self.kwargs['site']
        devices = Device.objects.filter(reader__site__slug=site).order_by('code')
        dyn_fields = ['x%s' % a.code for a in devices]
        return SerializerClassFactory(dyn_fields)

    def get_queryset(self):
        ...
        # the query is correct and returns data

-- serializers.py
def SerializerClassFactory(dyn_fields):
class DynamicSerializer(serializers.Serializer):
    read_at = serializers.DateTimeField()

    class Meta:
        fields = ('read_at')

    for f in dyn_fields:
        setattr(DynamicSerializer, f, serializers.FloatField())
    DynamicSerializer.Meta.fields += ','.join("'%s'" % f for f in dyn_fields)

    return DynamicSerializer

When I execute the code, only the attribute (read_at) specified in the class definition is serialized. None of the dynamic attributes appear to be working.

Example:

[{"read_at":"2017-05-07T00:12:29Z"},{"read_at":"2017-05-08T00:12:30Z"}]

Here is the output of the class after creation in shell:

>>> serializer = SerializerClassFactory(['x01', 'x02', 'x03'])
>>> serializer.__dict__
mappingproxy({'__module__': 'xxx.api.serializers', 
'Meta': <class 'xxx.api.serializers.SerializerClassFactory.<locals>.DynamicSerializer.Meta'>, 
'x02': FloatField(), '_declared_fields': OrderedDict([('read_at', DateTimeField())]), 
'__doc__': None, 'x01': FloatField(), 'x03': FloatField()})

Additionally, I've tried:

setattr(DynamicSerializer, f, property(serializers.FloatField()))
setattr(DynamicSerializer, f, type(serializers.FloatField()))

I'm not sure what I am doing wrong.

bah
  • 168
  • 1
  • 6

1 Answers1

0

Here, only the fields are dynamic, the base serializer is the same. In order to make it work, we will change only the fields on the DynamicSerializer

in serializers.py you would have:

class DynamicSerializer(serializers.Serializer):
    read_at = serializers.DateTimeField()

    class Meta:
        fields = ('read_at')

    def __init__(*args, dyn_fields=None, **kwargs):
        dyn_fields = dyn_fields or []
        for field in dyn_fields:
            self.fields[field] = serializers.FloatField()

        super(DynamicSerializer, self).__init__(*args, **kwargs)

and in the views.py on the get_serializer_class method you would return DynamicSerializer(dyn_fields=dyn_fields)

for more info visit http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields

foobarna
  • 854
  • 5
  • 16
  • I had to change the signature of the __init__ method: def __init__(self, dyn_fields=None, *args, **kwargs). Now I am getting the error 'DynamicSerializer' object is not callable. – bah May 10 '17 at 21:53
  • You are getting this issue because not overriding `get_serializer_class` is what you need. Take a look here https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py#L105-L130 `get_serializer_class` expects to return a class (a callable), not an instance. You should override `get_serializer()`. – foobarna May 11 '17 at 08:48
  • Thanks for your help and pointing me in the right direction. In this case, you can't use positional arguments as it causes issues. See here [link](https://github.com/encode/django-rest-framework/issues/4397) – bah May 15 '17 at 20:46
  • `class DynamicSerializer(serializers.Serializer): read_at = serializers.DateTimeField() def __init__(self, *args, **kwargs): dyn_fields = kwargs.pop('dyn_fields') for field in dyn_fields: self.fields[field] = serializers.FloatField() super(DynamicSerializer, self).__init__(*args, **kwargs)` – bah May 16 '17 at 15:43
  • `def get_serializer(self, *args, **kwargs): site = self.kwargs['site'] device = Device.objects.filter(reader__site__slug=site).order_by('code') dyn_fields = ['x%s' % a.code for a in devices] kwargs['dyn_fields'] = dyn_fields return DynamicSerializer(*args, **kwargs)` – bah May 16 '17 at 15:43