Pydantic from_orm для загрузки модели django prefetch_related list field

У меня есть модель django:

class Foo(models.Model):
    id: int
    name = models.TextField(null=False)

class Bar(models.Model):
    id: int
    foo = models.ForeignKey(
        Foo,
        on_delete=models.CASCADE,
        null=False,
        related_name="bars",
    )

и пидантическая модель как (orm_mode есть True):

class BarPy(BaseModel):
    id: int
    foo_id: int

class FooPy(BaseModel):
    id: int
    name: str
    bars: List[BarPy]

Теперь я хочу сделать запрос к модели Foo и загрузить ее в FooPy, поэтому я написал такой запрос:

foo_db = Foo.objects.prefetch_related("bars").all()
pydantic_model = FooPy.from_orm(foo_db)

Но это дает мне эту ошибку:

pydantic.error_wrappers.ValidationError: 1 validation error for FooPy
bars
  value is not a valid list (type=type_error.list)

У меня получается сделать это при явном использовании конструктора FooPy и присвоении значений вручную, но я хочу использовать from_orm.

Атрибут bars на вашей модели Foo - это ReverseManyToOneDescriptor, который просто возвращает RelatedManager для модели Bar. Как и для любого менеджера в Django, чтобы получить набор всех экземпляров, управляемых им, вам нужно вызвать метод all для него. Обычно вы делаете что-то вроде foo.bars.all().

Вы можете добавить собственный пользовательский валидатор к FooPy и заставить его pre=True захватывать все связанные Bar экземпляры и передавать их последовательность валидаторам по умолчанию:

from django.db.models.manager import BaseManager
from pydantic import BaseModel, validator

...

class FooPy(BaseModel):
    id: int
    name: str
    bars: List[BarPy]

    @validator("bars", pre=True)
    def get_all_from_manager(cls, v: object) -> object:
        if isinstance(v, BaseManager):
            return list(v.all())
        return v

Обратите внимание, что недостаточно просто сделать .all(), потому что это вернет кверисет, который не пройдет валидатор последовательности по умолчанию, встроенный в модели Pydantic. Вам нужно передать ему фактическую последовательность (например, list или tuple), которой QuerySet не является.

Вернуться на верх