I'm trying to write a Serializer that would take dynamic fields and add them to the restricted number of fields specified in Meta
, but it seems that there's no method to "add back" a field to the serializer once it's been created.
Dynamic Fields per Django documentation
class DynamicFieldsModelSerializer(ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
class BldgLocationAndFilters(DynamicFieldsModelSerializer):
latitude = fields.FloatField(source='lat_016_decimal')
longitude = fields.FloatField(source='long_017_decimal')
class Meta:
model = Bldg
fields = ('latitude', 'longitude')
I'd like to do something that would modify the DynamicFieldsModelSerializer such that fields can be appended to the set that has already been filtered down, but it looks like the Meta fields override everything such that nothing can be added back (fields can only be removed
Pseudocode of desired behavior:
class DynamicFieldsUnionModelSerializer(ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields is not None:
new_fields = set(fields)
existing = set(self.fields)
unique_new = new_fields.union(existing) - existing
for field_name in unique_new:
self.fields.update(field_name)
If BldgLocationAndFilters
was called as serializer = BldLocationAndFilters(fields=['type'])
, I'd expect the resulting returns to have fields = ('latitude', 'longitude', 'type')
DynamicFieldsModelSerializer
only works on removing existing fields, because the implementation depends on fields already built in __init__
. You can add fields after the __init__
, but you have to build them again somehow (not just add names).
But one way to support this is to override the serializer's get_field_names
method which works with unbuilt field names:
class BldgLocationAndFilters(ModelSerializer):
latitude = fields.FloatField(source='lat_016_decimal')
longitude = fields.FloatField(source='long_017_decimal')
class Meta:
model = Bldg
fields = ('latitude', 'longitude')
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
self._fields_to_add = kwargs.pop('fields', None)
super().__init__(*args, **kwargs)
def get_field_names(self, *args, **kwargs):
original_fields = super().get_field_names(*args, **kwargs)
if self._fields_to_add:
return set(list(original_fields) + list(self._fields_to_add))
return original_fields
# Should use latitude, longitude, and type
BldgLocationAndFilters(instance, fields=('type',)).data
Note that this is using just ModelSerializer
.
Or just define your serializer with __all__
(while still using DynamicFieldsModelSerializer
) and set the fields on a per-need basis:
class BldgLocationAndFilters(DynamicFieldsModelSerializer):
latitude = fields.FloatField(source='lat_016_decimal')
longitude = fields.FloatField(source='long_017_decimal')
class Meta:
model = Bldg
fields = '__all__'
BldgLocationAndFilters(instance, fields=('latitude', 'longitude', 'type')).data