Q:

How to create dynamic rest ModelSerializer?

I'm currently working on a big code base and i need to send emails from any potential module, that conduct in Circular Dependencies issues in python
so i tried to use apps.get_model() from django.apps but when serializers are declared the models are not ready.
So i'm trying to create a factory function who build the class at runtime instead of launch time
from rest_framework.serializers import ModelSerializer

def make_serializer(model: str, fields: tuple, options = None, **nested_fields) -> ModelSerializer:
    """Generate a new serializer "On the fly", so the model does not have to be imported at launch time.
    """
    model_object = apps.get_model(model)
    input_fields = fields

    if options is None:
        options = {}

    class Serializer(ModelSerializer):
        class Meta:
            model = model_object
            fields = input_fields

            def create(self, validated_data):
                # we won't permit to create data from thoses serializers.
                raise NotImplementedError

    # configure nested serializers.
    for nested_field in nested_fields.values():
        for key, nested_serializer_class in nested_field.items():
            serializer_instance = nested_serializer_class(**options.get(key, {}))
            print(model, key, serializer_instance)
            setattr(Serializer, key, serializer_instance)

    return Serializer

my tests models looks like
class Band(Model):
    name = Charfield(max_length=255)

class Influencer(Model):
    entity = Charfield(max_length=255)

class Submission(Model):
    influencer = ForeignKey(Influencer, ...)

class Campaign(Model):
    band = ForeignKey('band.Band', ...)
    submissions = ManyToMany(Submission)

and my testing function is:
def test():
    serializer = make_serializer(
        model='submission.Campaign',
        fields=['pk', 'submissions', 'band'],
        options={'submissions': {'many': True}},
        nested_fields={
            'submissions': make_serializer(
                model='submission.Submission',
                fields=('influencer',),
                nested_fields={
                    'influencer': make_serializer('influencer.Influencer', ('entity',))
                },
            ),
            'band': make_serializer('band.Band', ('name',))
        }
    )
    return serializer

instead of having my fields correly with test()(Campaign.objects.last()).data i only got "pks" and my serialiser looks like:
Serializer():
    pk = IntegerField(label='ID', read_only=True)
    submissions = PrimaryKeyRelatedField(many=True, queryset=Submission.objects.all())
    band = PrimaryKeyRelatedField(allow_null=True, queryset=Band.objects.all(), required=False)

i except and output like:
{
    "pk": 1,
    "band": {
         "name": "BobMarley",
    },
    "submissions": [
        {
            "influencer": {"entity": "The influencer's name"}
        }
    ]
}

but i got a ReturnDict containing:
{
    "pk": 1,
    "band": 523,
    "submissions": [6, 7, 8]
}

thanks for your time

A:

well after many headcaches i've found out that i CAN'T setattr on a class after it's declaration, so i use a trick based on a dict
def make_serializer(model: str, fields: tuple, options = None, **nested_fields) -> ModelSerializer:
    """Generate a new serializer "On the fly", so the model does not have to be imported at launch time.
    """
        name = f'Serializer_{model}'
    model_object = apps.get_model(model)
    input_fields = fields

    if options is None:
        options = {}

    def create(self, validated_data):
            # we won't permit to create data from thoses serializers.
            raise NotImplementedError

    class Meta:
        model = model_object
        fields = input_fields

    attrs = {"Meta": Meta}
    # configure nested serializers.
    for key, nested_serializer_class in nested_fields.items():
        attrs[key] = nested_serializer_class(**options.get(key, {}))

    attrs['create'] = create
    return type(ModelDictSerializer)(name, (ModelDictSerializer,), attrs)

the syntax is something like:
campaign_serializer = make_serializer(
        model='submission.Campaign',
        fields=['pk', 'submissions', 'band'],
        options={'submissions': {'many': True}},
        submissions=make_serializer(
            model='submission.Submission',
            fields=('influencer',),
            influencer=make_serializer('influencer.Influencer', ('entity',))
        ),
        band=make_serializer('band.Band', ('name',))
    )

and it's working like a charm:
Serializer_submission.Campaign(<Campaign: Campaign object (9665)>):
    pk = IntegerField(label='ID', read_only=True)
    submissions = Serializer_submission.Submission(many=True):
        influencer = Serializer_influencer.Influencer():
            entity = CharField(allow_blank=True, max_length=255, required=False)
    band = Serializer_band.Band():
        name = CharField(max_length=255)

i hope this will help someone else

