tortoise 关联关系(外键、多对多)相关问题

这里主要介绍在tortoise-orm中使用pydantic_model_creator创建Pydantic时,关联关系出现的一些问题。关联字段丢失、继承失败等

1. 使用全局初始化关联关系

全局初始化会使所有模型的外键、一对多、多对多等关系自动建立,这个适用于关联关系简单的数据库表。
优点:简单易用,不用手动简历关联关系
缺点:适用于简单关联关系表,自定义关系字段时比较麻烦,需要手动exclude排除多余的关联字段
实现方法如下:
先看setting.py中数据库配置:

DATABASE_CONFIG = {
    "connections": {
        "default": {
            "engine": "tortoise.backends.mysql",
            "credentials": {
                "host": os.getenv("DATABASE_HOST", PROJECT_CONFIG["database"]["host"]),
                # FS_DATABASE_HOST 数据库名称环境变量根据需求修改
                "port": int(os.getenv("DATABASE_PORT", PROJECT_CONFIG["database"]["port"])),
                "user": os.getenv("DATABASE_USER", PROJECT_CONFIG["database"]["user"]),
                "password": os.getenv("DATABASE_PASSWORD", PROJECT_CONFIG["database"]["password"]),
                "database": os.getenv("DATABASE_NAME", PROJECT_CONFIG["database"]["db_name"]),
                "minsize": PROJECT_CONFIG["database"]["minsize"],
                "maxsize": PROJECT_CONFIG["database"]["maxsize"],
                "charset": "utf8mb4",
                "pool_recycle": PROJECT_CONFIG["database"]["pool_recycle"],
            }
        },

    },
    "apps": {
        # 1.注意此处的app代表的并不与FastAPI的routers对应,为Tortoise中的app概念
        # 2.在Tortoise-orm使用外键时,需要用到该app名称来指执行模型,"app.model",所以同一个app中不要出现名称相同的两个模型类
        # 3.app的划分结合 规则2与实际情况进行划分即可
        "cp_model": {
            "models": [
                "src.faster.database.models",
            ],
            "default_connection": "default",
        },
    },
    "routers": ["src.my_tools.tortoise_tools.routers.WriteOrReadRouter"],
    "use_tz": True,  # 设置数据库总是存储utc时间
    # "timezone": DEFAULT_TIMEZONE,  # 设置时区转换,即从数据库取出utc时间后会被转换为timezone所指定的时区时间(待验证)
    "timezone": LOCAL_TIMEZONE,  # 设置时区转换,即从数据库取出utc时间后会被转换为timezone所指定的时区时间(待验证)
}

在routers的init.py中最上面写这行初始化代码:

from src.settings import DATABASE_CONFIG
from tortoise import Tortoise

# 在模型schema初始化前,调用外键关联
# 1.这将调用Tortoise子带的外键关联,如果想自定义外键关联的字段,需要自己写Pydantic
# 2.如果想偷懒只要能返回外键关联信息,则使用此方法

Tortoise.init_models(DATABASE_CONFIG["apps"]["cp_model"]["models"], "cp_model")
重要提示:若需要自动生成外键关联,则需要在模型schema初始化前,调用外键关联。也就是要在导入各个routers之前初始化关联关系,否则会无效。

2. 自定义关联关系字段

在创建Pydantic时,用户可以根据表字段的关联关系自定义。适合复杂关系表的Pydantic建立关联字段。
这个时候需要注释掉上面的自动关联关系的初始化代码。

先举例数据模型:

class Post(models.Model):
    """岗位表"""
    id = fields.CharField(max_length=50, pk=True)
    name = fields.CharField(max_length=32, null=False, default="")

class Department(AbstractDefaultColumn):
    """部门表"""
    id = fields.CharField(max_length=50, pk=True)
    name = fields.CharField(max_length=64, help_text='部门名')

class User(models.Model):
    """用户表"""
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=32, index=True)
    phone_no = fields.CharField(max_length=32, unique=True)
    email = fields.CharField(max_length=32, null=True)
    department = fields.ForeignKeyField("cp_model.Department", on_delete=fields.SET_NULL, null=True,
                                        related_name="depart_users", help_text='所属部门')
    post = fields.ForeignKeyField("cp_model.Post", on_delete=fields.SET_NULL, null=True, related_name="post_users",
                                  help_text='岗位')

class ForumArticle(AbstractDefaultColumn):
    """文章"""
    id = fields.IntField(pk=True)
    user = fields.ForeignKeyField("cp_model.User", on_delete=fields.CASCADE)
    title = fields.CharField(max_length=32, default='', null=True)  # 文章标题
    content = fields.TextField(default='', null=True, blank=True)  # 文章正文
    tags = fields.ManyToManyField('cp_model.ArticleTag', related_name='tags_article',
                                  through="cp_forum_article_tags",
                                  forward_key="cp_tag_id",
                                  backward_key="cp_forum_article_id"
                                  )  # 标签

    posters = fields.ManyToManyField("cp_model.CPImage", related_name="posters_article",
                                     through="cp_forum_article_posters",
                                     forward_key="cp_image_id",
                                     backward_key="cp_forum_article_id"
                                     )

class ArticleComment(AbstractDefaultColumn):
    """评论"""
    id = fields.IntField(pk=True)
    user = fields.ForeignKeyField("cp_model.User", related_name="user_comments", on_delete=fields.CASCADE)
    to_user_id = fields.IntField(default=0)  # 回复用户的id
    to_user_name = fields.CharField(max_length=32, default="", null=True, blank=True)  # 回复用户的name
    article = fields.ForeignKeyField("cp_model.ForumArticle", on_delete=fields.SET_NULL, null=True,
                                     related_name="article_comments")

class ArticleUpvoteRecord(AbstractDefaultColumn):
    """用户论坛文章的点赞记录"""
    id = fields.IntField(pk=True)
    user = fields.ForeignKeyField("cp_model.User", related_name="user_article_upvotes", on_delete=fields.CASCADE)
    article = fields.ForeignKeyField("cp_model.ForumArticle", related_name="article_upvotes", on_delete=fields.CASCADE)
  

这里拿一个论坛项目的几张表做栗子

2.1 外键关系
tip:如果模型字段比较多,但是序列化给前端只要几个字段,可以通过include添加字段,只会序列化include里面的字段和Pydantic里面用户自己添加的字段。如果模型字段比较少,就可以通过exclude来排除不需要的字段,会序列化模型所有字段除去exclude里面的字段。exclude和include都不写就序列化模型所有的字段。

用户Pyandtic:

class PostSimpleSchema(pydantic_model_creator(Post, name="PostSimpleSchema", exclude=())):
    """职位"""
    pass

class UserSimpleSchema(
    pydantic_model_creator(
        User, name="UserSimpleSchema", include=("id", "name", "image", "post")
   )
):
    """简单展示用户信息,通过include只会序列化这四个字段"""
    id: int
    name: str
    image: str = None
    post: PostSimpleSchema = None   # 外键岗位字段关系序列化
    pass
2.1 外键反向查询(多对一)和m2m(多对多)

多对一关系,可以直接根据关联关系名,related_name,来指定,比如下面评论表的帖子信息
article: CommentArticleSchema = None, 多对多可以用List[]来指定,
比如下面 posters: List[ImageSimpleSchema] = None

class CommentArticleSchema(
    pydantic_model_creator(ForumArticle,
                           name="CommentArticleSchema",
                           exclude=("article_comments", ), exclude_readonly=True)
):
    """帖子"""
    user: UserSimpleSchema = Field(description="发帖人")
    posters: List[ImageSimpleSchema] = None
    id: int
    pass

class CommentBaseSchema(
    pydantic_model_creator(ArticleComment,
                           name="CommentSimpleSchema",
                           exclude=("write_date", "upvote_score",))
):
    """评论"""
    user: UserSimpleSchema = Field(description="评论人")
    posters: List[ImageSimpleSchema] = None

class CommentListSchema(CommentBaseSchema):
    """用于帖子列表里面的评论"""
    user: UserSimpleSchema = Field(description="评论人")
    posters: List[ImageSimpleSchema] = None
    parent_comment: CommentBaseSchema = None  # 父评论
    article: CommentArticleSchema = None
    anonymous_user: AnonymousUserSimpleSchema = None

tip:Pydantic继承会有一些问题,关联关系会继承失败,所以需要在子类重新声明一下关联关系字段

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容