I'm trying to add fields dynamically to a serializer of Django Rest Framework, by overwriting the __init__
method. The approach is similar to the one described here: http://masnun.com/2015/10/21/django-rest-framework-dynamic-fields-in-serializers.html
The reason I'm doing this is, because I want to change the field type dynamically to a one, determined by the property type_
of the instance to be serialized. This works pretty well, if I serialize one instance at a time:
from rest_framework import serializers
from rest_framework.fields import empty
class VariableDetails:
def __init__(self, name, type_, value):
self.name = name
self.type_ = type_
self.value = value
class VariableDetailSerializer(serializers.Serializer):
TYPE_FIELD_MAP = {
'string': serializers.CharField,
'integer': serializers.IntegerField,
'float': serializers.FloatField,
}
name = serializers.CharField()
type_ = serializers.CharField()
def __init__(self, instance=None, data=empty, **kwargs):
# this is where the magic happens
super().__init__(instance, data, **kwargs)
if instance is not None:
field_type = self.TYPE_FIELD_MAP[instance.type_]
self.fields['value'] = field_type()
string_details = VariableDetails('character value', 'string', 'hello world')
integer_details = VariableDetails('integer value', 'integer', 123)
print(VariableDetailSerializer(string_details).data)
# {'name': 'character value', 'type_': 'string', 'value': 'hello world'}
print(VariableDetailSerializer(integer_details).data)
# {'name': 'integer value', 'type_': 'integer', 'value': 123}
If I want to serialize multiple instances of VariableDetails
that are related to a parent instance (calling it Parent
for the example), the value field is missing:
class Parent:
def __init__(self, variable_details):
self.variable_details = variable_details
class ParentSerializer(serializers.Serializer):
variable_details = VariableDetailSerializer(many=True)
parent = Parent(variable_details=[string_details, integer_details])
print(ParentSerializer(parent).data)
# {
# 'variable_details': [
# {
# 'name': 'character_value',
# 'type_': 'string'
# # value is missing
# },
# {
# 'name': 'integer_value',
# 'type_': 'integer'
# },
# ]
# }
Apparently VariableDetailSerializer.__init__
is only called during the creation of the ParentSerializer
and once when a new instance of the ParentSerializer
is initialized. In both cases instance
is None
. Hence, it is not called for each of the VariableDetails
.
Does anybody know how to add fields dynamically to a Serializer
that is also serializing list instances?
To facilitate running the code, I created a gist: https://gist.github.com/larsrinn/861f8d50bf5bb0626d73321b546d8cb3 The code should be copy&pastable into a python repl, if Django Rest Framework is installed. However, you don't need to create a Django Project
Because VariableDetailSerializer
is nested in ParentSerializer
it behaves a like a field itself. In this scenario you can override the to_representation()
method to do the dynamic type switching.
def to_representation(self, instance):
field_type = self.TYPE_FIELD_MAP[instance.type_]
self.fields['value'] = field_type()
try:
del self._readable_fields # Clear the cache
except AttributeError:
pass
return super().to_representation(instance)
The main catch here is that the serializer caches the fields because it does not expect them to change between instances. Therefore it is necessary to clear the cache with the del self._readable_fields
.
When I add the above to VariableDetailSerializer
and run your example, I get:
{
'variable_details': [
OrderedDict([('name', u'character value'),
('type_', u'string'),
('value', u'hello world')]),
OrderedDict([('name', u'integer value'),
('type_', u'integer'),
('value', 123)])
]
}
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