4

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 = Two
        fields = ('name', 'contact_number')

class SerializerOne(DynamicFieldsModelSerializer, serializers.ModelSerializer):
    another_field = SerializerTwo()

    class Meta:
        lookup_field = 'uuid'
        model = One
        fields = ('status', 'another_field',)

Now what I want to do is, dynamically pass(on the fly) what all fields will be used from SerializerTwo, as I'm doing for SerializerOne.

The way I'm doing it for SerializerOne is:

# where fields=('status')
SerializerOne(queryset, fields=fields)

Is there a way, using which I can add fields from SerializerTwo to the above Serializer initialization.

# where fields=('status', 'name') name from SerializerTwo
# the double underscore notation does not work here for fields, so another_field__name cannot be used as well
SerializerOne(queryset, fields=fields)

3 Answers 3

2

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 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.keys())
    for field_name in existing - allowed:
        self.fields.pop(field_name)

if nested is not None:
    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):

    def parse_nested_fields(fields):
        field_object = {"fields": []}
        for f in fields:
            obj = field_object
            nested_fields = f.split("__")
            for v in nested_fields:
                if v not in 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

    def select_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])

    def fields_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 normally
    super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

    if fields is not None:
        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"])
Sign up to request clarification or add additional context in comments.

Comments

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:

class NestedDynamicFieldsModelSerializer(serializers.ModelSerializer):

def __init__(self, *args, **kwargs):

    def parse_nested_fields(fields):
        field_object = {"fields": []}
        for f in fields:
            obj = field_object
            nested_fields = f.split("__")
            for v in nested_fields:
                if v not in 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

    def select_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])

    def fields_to_include(serializer, fields):
        # Drop any fields that are not specified in the `fields` argument.
        allowed = set(fields)
        if isinstance(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 normally
    super(NestedDynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

    if fields is not None:
        # 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)

2 Comments

i replaced fields = kwargs.pop('fields', None) with kwargs.get('context').get('request').query_params.get('fields') if isinstance(kwargs.get('context'), dict) else None and fields = parse_nested_fields(fields) with fields = parse_nested_fields(fields.split(',')) after the if fields is not None: block
hello can you please give the full code of NestedDynamicFieldsModelSerializer class .. and example usage .
0

I use the following way to implement the so called Nested Serializer Dynamic Model Fields.

class SerializerTwo(serializers.ModelSerializer):
    fields_filter_key = 'two_fields'
    class Meta:
        model = Two
        fields = ('name', 'contact_number')

class SerializerOne(DynamicFieldsModelSerializer, serializers.ModelSerializer):
    fields_filter_key = 'one_fields'
    another_field = serializers.SerializerMethodField()

    class Meta:
        lookup_field = 'uuid'
        model = One
        fields = ('status', 'another_field',)

    def get_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

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

        if 'request' not in self.context or not 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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.