Skip to content Skip to sidebar Skip to footer

Django Rest Framework : Nested Serializer Dynamic Model Fields

I have defined serializers like below. I'm using a mixin to change the display fields on the fly. class SerializerTwo(serializers.ModelSerializer): class Meta: model =

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"