1

Is there a way how to dynamically generate a Django rest framework serializers?

Considering this:

    class BlogSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = models.Blog
            fields = get_all_model_fields(models.Blog)
    
    class PostSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = models.Post
            fields = get_all_model_fields(models.Post)
     
    
    class UserSerializer(serializers.ModelSerializer):
    
        class Meta:
            model = models.User
            fields = get_all_model_fields(models.User)

I am wondering if something like following example could be possible:

from django.apps import apps

models = [model for model in apps.get_models()]

for model in models:
    type(model.__name__+'Serializer',(serializers.ModelSerializer,),{
       type("Meta",(),{
           "model":model,
           "fields": get_all_model_fields(model)
       })
    })

Or is there any other way how to generate DRF serializers?

Patrik
  • 499
  • 1
  • 7
  • 24

2 Answers2

2

Here's a function to build a ModelSerializer for a given Model (tested with Django 3.2, DRF 3.12.4 and Python 3.8):

import types
from functools import lru_cache
from typing import Type

from django.db import models
from rest_framework import serializers

@lru_cache(maxsize=0)
def model_serializer(model: Type[models.Model]) -> Type[serializers.ModelSerializer]:
    meta_class = types.new_class("Meta")
    setattr(meta_class, "model", model)
    setattr(meta_class, "fields", "__all__")
    result = types.new_class(
        model.__name__ + "Serializer", (serializers.ModelSerializer,), {}
    )
    setattr(result, "Meta", meta_class)
    return result

If you are certain that you will call this function only once for each serializer, you can omit the @lru_cache to preserve some memory.

Example usage:

class MyModel(models.Model):
    some = models.CharField(max_length=123)
    other = models.IntegerField()

MyModelSerializer = model_serializer(MyModel)
my_serializer = MyModelSerializer({"some": "abc", "other": 1234})
my_serializer.is_valid(True)

To add the serializers for all your models to the current module's namespace:

from django.apps import apps

for model in apps.get_models():
    serializer_type = model_serializer(model)
    globals()[serializer_type.__name__] = serializer_type
roskakori
  • 3,139
  • 1
  • 30
  • 29
1

Your approach will work - but for Django to find about these serializers you have to assign them to the module namespace.

In your code, you just call type - the serializer class is created and "thrown away" immediately. Even if the base Django class serializers.ModelSerializer keep a reference to it in a registry, you would not be able to import your serializers and make use of them.

All you have to do is to add them to the dictionary returned by globals(). Likewise, the namespace you create for the class also has to be a dictionary -since you are calling "type" but not actually assigning its name as "Meta", you create a set, not a dictionary, and your call to type will fail as it was expecting a dictionary.

So, I did not check if the code actually will work, but the idea, based on yours is:

from django.apps import apps

models = [model for model in apps.get_models()]

for model in models:
    name = f"{model.__name__}Serializer"
    globals()[name] =  type(name,(serializers.ModelSerializer,),{
       "Meta": type("Meta",(),{
           "model":model,
           "fields": get_all_model_fields(model)
       })
    })
del models, model, name
Ronnie
  • 992
  • 1
  • 9
  • 25
jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • Thank you, I will try it. What's the use of `del models, model, name` ? Why are you doing that? – Patrik Nov 13 '20 at 10:11
  • 1
    it is not needed - it is just that as these variables are created at the module root as tools to create the classes that will be used, they have no use afterwars. With the `del` statement they won't be visible to one importing the module - and the namespace stays cleaner. But it is really cosmetic. – jsbueno Nov 13 '20 at 13:36