Django Rest Framework : Nested Serializer Dynamic Model Fields
Solution 1:
After having the same problem, I found a solution, I hope this will be helpul for some people. I modified DynamicFieldsModelSerializer as defined here
def__init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
nested = kwargs.pop('nested', None)
# Instantiate the superclass normallysuper(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields isnotNone:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
if nested isnotNone:
for serializer in nested:
try:
nested_serializer = self.fields[serializer]
except:
logger.warning("Wrong nested serializer name")
continue
allowed = set(nested[serializer])
existing = set(nested_serializer.fields.keys())
for field_name in existing - allowed:
nested_serializer.fields.pop(field_name)
After that, You can use it like this:
SerializerOne(queryset, nested={"another_field": ["name"]})
You can modify my solution to use the double underscore instead of another kewyord with a dict, but I wanted to separate regular fields from nested serializer.
It can also be improved to be recursive, here I'm only dealing with a depth of one nested serializer
EDIT I modified my code to use the double underscore syntax after all:
def__init__(self, *args, **kwargs):
defparse_nested_fields(fields):
field_object = {"fields": []}
for f in fields:
obj = field_object
nested_fields = f.split("__")
for v in nested_fields:
if v notin obj["fields"]:
obj["fields"].append(v)
if nested_fields.index(v) < len(nested_fields) - 1:
obj[v] = obj.get(v, {"fields": []})
obj = obj[v]
return field_object
defselect_nested_fields(serializer, fields):
for k in fields:
if k == "fields":
fields_to_include(serializer, fields[k])
else:
select_nested_fields(serializer.fields[k], fields[k])
deffields_to_include(serializer, fields):
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(serializer.fields.keys())
for field_name in existing - allowed:
serializer.fields.pop(field_name)
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normallysuper(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields isnotNone:
fields = parse_nested_fields(fields)
# Drop any fields that are not specified in the `fields` argument.
select_nested_fields(self, fields)
You can then use it like this:
SerializerOne(instance, fields=["another_field__name"])
Solution 2:
@Lotram's answer doesn't work on fields that return multiple values (via many=True
).
The following code improves upon @Lotram's solution which works on fields that return multiple values:
classNestedDynamicFieldsModelSerializer(serializers.ModelSerializer):
def__init__(self, *args, **kwargs):
defparse_nested_fields(fields):
field_object = {"fields": []}
for f in fields:
obj = field_object
nested_fields = f.split("__")
for v in nested_fields:
if v notin obj["fields"]:
obj["fields"].append(v)
if nested_fields.index(v) < len(nested_fields) - 1:
obj[v] = obj.get(v, {"fields": []})
obj = obj[v]
return field_object
defselect_nested_fields(serializer, fields):
for k in fields:
if k == "fields":
fields_to_include(serializer, fields[k])
else:
select_nested_fields(serializer.fields[k], fields[k])
deffields_to_include(serializer, fields):
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
ifisinstance(serializer, serializers.ListSerializer):
existing = set(serializer.child.fields.keys())
for field_name in existing - allowed:
serializer.child.fields.pop(field_name)
else:
existing = set(serializer.fields.keys())
for field_name in existing - allowed:
serializer.fields.pop(field_name)
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normallysuper(NestedDynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if fields isnotNone:
# import pdb; pdb.set_trace()
fields = parse_nested_fields(fields)
# Drop any fields that are not specified in the `fields` argument.
select_nested_fields(self, fields)
Solution 3:
I use the following way to implement the so called Nested Serializer Dynamic Model Fields
.
classSerializerTwo(serializers.ModelSerializer):
fields_filter_key = 'two_fields'classMeta:
model = Two
fields = ('name', 'contact_number')
classSerializerOne(DynamicFieldsModelSerializer, serializers.ModelSerializer):
fields_filter_key = 'one_fields'
another_field = serializers.SerializerMethodField()
classMeta:
lookup_field = 'uuid'
model = One
fields = ('status', 'another_field',)
defget_another_field(self, obj):
another_filed_serializer = SerializerTwo(obj.another_field,
context=self.context)
return another_filed_serializer.data
and we make some modification to DynamicFieldsModelSerializer
classDynamicFieldsModelSerializer(serializers.ModelSerializer):
def__init__(self, *args, **kwargs):
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
if'request'notin self.context ornot self.fields_filter_key:
return
fields = self.context['request'].query_params.get(self.fields_filter_key)
if fields:
fields = fields.split(',')
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
so the last problem is how to organize the url, write the GET
url like this:
domain/something?one_fields=name,contact_number&two_fields=another_field
Post a Comment for "Django Rest Framework : Nested Serializer Dynamic Model Fields"