繁体   English   中英

Pydantic from_orm 使用相关列表字段加载 Django 模型

[英]Pydantic from_orm to load Django model with related list field

我有以下 Django 模型:

from django.db import models


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",
    )

和 Pydantic 模型(将orm_mode设置为True ):

from pydantic import BaseModel


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

Foo模型上的bars属性是一个ReverseManyToOneDescriptor ,它只为Bar模型返回一个RelatedManager 与 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 模型中内置的默认序列验证器。 你会得到同样的错误。

您需要给它一个实际的序列(例如listtuple )。 QuerySet不是一个序列,而是一个可迭代对象。 但是您可以使用它并将其变成一个序列,例如调用它的list


更通用的版本

您可以尝试概括该验证器并将其添加到您自己的 (Pydantic) 基础模型中。 这样的事情应该适用于您注释为list[Model]的任何字段,其中Modelpydantic.BaseModel的子类:

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

...

class CustomBaseModel(BaseModel):

    @validator("*", pre=True)
    def get_all_from_manager(cls, v: object, field: ModelField) -> object:
        if not (isinstance(field.type_, type) and issubclass(field.type_, BaseModel)):
            return v
        if field.shape is SHAPE_LIST and isinstance(v, BaseManager):
            return list(v.all())
        return v

我还没有彻底测试过这个,但我想你明白了。


边注

值得一提的是prefetch_related与问题无关。 无论您是否这样做,问题及其解决方案都是一样的。 不同之处在于,如果没有prefetch_related ,您将在调用from_orm时触发额外的数据库查询,从而执行使用.bars.all()查询集的验证器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM