Динамически генерируемый сериализатор Django

#python #django #django-rest-framework #metaclass #django-serializer

#python #django #django-rest-framework #метакласс #django-сериализатор

Вопрос:

Есть ли способ динамической генерации сериализаторов Django rest Framework?

Учитывая это:

     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)
  

Мне интересно, возможно ли что-то вроде следующего примера:

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

Или есть какой-либо другой способ создания сериализаторов DRF?

Ответ №1:

Вот функция для построения a ModelSerializer для данного Model (протестирована с Django 3.2, DRF 3.12.4 и Python 3.8):

 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
  

Если вы уверены, что вызовете эту функцию только один раз для каждого сериализатора, вы можете опустить @lru_cache, чтобы сохранить некоторую память.

Пример использования:

 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)
  

Чтобы добавить сериализаторы для всех ваших моделей в пространство имен текущего модуля:

 from django.apps import apps

for model in apps.get_models():
    serializer_type = model_serializer(model)
    globals()[serializer_type.__name__] = serializer_type
  

Ответ №2:

Ваш подход будет работать, но для Django, чтобы узнать об этих сериализаторах, вы должны назначить их в пространство имен модуля.

В вашем коде вы просто вызываете type — класс serializer создается и немедленно «выбрасывается». Даже если базовый класс Django serializers.ModelSerializer хранит ссылку на него в реестре, вы не сможете импортировать свои сериализаторы и использовать их.

Все, что вам нужно сделать, это добавить их в словарь, возвращаемый globals() . Аналогично, пространство имен, которое вы создаете для класса, также должно быть словарем — поскольку вы вызываете «type», но фактически не присваиваете его имя как «Meta», вы создаете набор, а не словарь, и ваш вызов type завершится ошибкой, поскольку он ожидал словаря.

Итак, я не проверял, действительно ли код будет работать, но идея, основанная на вашей:

 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
  

Комментарии:

1. Спасибо, я попробую. Какая польза от del models, model, name ? Зачем ты это делаешь?

2. это не нужно — просто, поскольку эти переменные создаются в корне модуля как инструменты для создания классов, которые будут использоваться, они не имеют никакого смысла после войны. С del помощью инструкции они не будут видны тому, кто импортирует модуль, и пространство имен остается более чистым. Но это действительно косметический.