#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
помощью инструкции они не будут видны тому, кто импортирует модуль, и пространство имен остается более чистым. Но это действительно косметический.