Django Rest Framework : Nested Serializer Dynamic Model Fields

django rest framework nested serializer post
django rest framework serializer multiple models
django rest framework combine serializers
nested serializer django
django rest framework serializer dynamic fields
django rest framework many to-many
django rest framework serializer queryset
primarykeyrelatedfield

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)

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"])

DRF: Advanced serializer usage, Dynamic modifying fields can make a Django rest framework API act like graphQL endpoi. and ModelSerializer to make the API for our Book model. we do to Dynamically modify fields for nested serializers like the Foreign  Serializers – Django REST Framework Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

@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)

Serializers, This property should be the serializer field class, that is used for a serializer field that maps to a standard model field. The default implementation dynamically creates a nested serializer class  The serializers in REST framework work very similarly to Django's Form and ModelForm classes. We provide a Serializer class which gives you a powerful, generic way to control the output of your responses, as well as a ModelSerializer class which provides a useful shortcut for creating serializers that deal with model instances and querysets.

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

Serializer relations, By default nested serializers are read-only. ModelSerializer): class Meta: model = Track fields = ['order', 'title', To provide a dynamic queryset based on the context , you can also override  FlexFields (DRF-FF) for Django REST Frameworkis a package designed to provide a common baseline of functionality for dynamically setting fields and nested models within DRF serializers. This package is designed for simplicity, with minimal magic and entanglement with DRF's foundational classes.

Creating dynamic fields for django models that work with django rest , Dynamic fields in Django Rest Framwork serializers (method) will be treated as if it is a field of a model by Django and Django Rest Framework serializer. We will create our models. This should not be too difficult to understand. Key is understanding where to place the foreign keys. Make your code look like this django-rest framework has this amazing…

Dynamically include/exclude fields to Django Rest Framwork , When using serializers with django rest framework , retrieving all fields of the Let us say we have a nested serializer called 'UserDataSerializer` with 5 fields each of class Meta: model = UserDataModel fields = ('profile', 'address', 'card',  The Django Rest Framework (DRF) is one of the effectively written frameworks around Django and helps build REST APIs for an application back-end. I was using it in one of my personal projects and stumbled upon this challenge of “serializing a model which is referencing another model via OneToOne field.”

DRF Dynamic Fields Serializer, With Django Rest Framework's serializers you can choose which fields are as if you exclude a nested related field from the serializer fields then the SQL query Using an example of a blog that has a simple model structure: The django-rest-framework-gis package provides geographic addons for django rest framework like a GeometryField field and a GeoJSON serializer. django-rest-framework-hstore The django-rest-framework-hstore package provides an HStoreField to support django-hstore DictionaryField model field.

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