跳转至

关系模型增删改查

读取

通常的 get, filter 等查询函数,并不会同时返回相关联的模型,你可以通过在 filter 时添加 prefetch_related 选项,让其同时获取相关联的模型。

1
2
3
4
5
6
7
8
9
    )

    schools_with_students: list[School] = (
        await School.select_related().prefetch_related(School.students).all()
    )

    school = await School.get(name="school 3")
    await school.fetch_related(School.students)
    assert len(school.students) == 1

prefetch_related 接受若干个位置参数,用于指定要同时获取的字段,如果不传入参数,则是模型上的所有关系字段。

select_related 是模型类上的没有查询条件的 filter().prefetch_related 的简写,它的参数与 prefetch_related 相同。

你也可以在模型实例上调用 fetch_related,来让该模型实例获取与它相关联的模型,它的参数与 prefetch_related 相同。

1
2
3
    school = await School.get(name="school 3")
    await school.fetch_related(School.students)
    assert len(school.students) == 1

插入

insert

1
2
3
4
    school = School(name="school 1")
    await school.insert()

    await Student(name="student 1", school=school).insert()

关系字段

对于有相关关系的模型,insertinsert_many 只会将自身和关系模型实例之间建立关系,而不会将关系模型一起插入到数据库中,也就是说,你必须先将关系模型插入到数据库中,然后再赋值给模型实例的关系字段上。正如该例子,你需要先将 school 插入到数据库中,再将其赋值给 studentschool 字段上。

此外,insert 接受一个类型为 boolexclude_related 参数,用于指定是否要与关系模型实例建立关系,默认为 False

你可以使用模型实例的 insert_with_related 方法,将关系模型连同自身一起插入到数据库中:

    school2 = School(
        name="school 2",
        students=[
            Student(name="student 2"),
            Student(name="student 3"),
        ],
    )
    await school2.insert_with_related()

    student4 = Student(name="student 4", school=School(name="school 3"))
    await student4.insert_with_related()

这样就可以省去先插入关系模型的步骤了。

add

该方法仅适用于 ManyToMany 多对多关系上,它接受一个模型实例,将该模型实例添加到自己的多对多字段值上。

如果提供的模型是非多对多关系字段模型,则会抛出异常。

    tag1 = await Tag(content="tag 1").insert()
    tag2 = await Tag(content="tag 2").insert()

    post1 = await Post(title="post 1").insert()
    post2 = await Post(title="post 2").insert()

    await post1.add(tag1)
    await post1.add(tag2)

    await post2.add(tag1)

    assert post1.tags == [tag1, tag2]
    assert post2.tags == [tag1]

删除

对于一对多和多对多关系模型,在模型定义时有级联相关配置。

remove

对于多对多关系,可以调用 remove 来将模型实例从自己的字段上删除。

如果提供的模型是非多对多关系字段模型,则会抛出异常。

1
2
3
4
5
    await post1.remove(tag1)
    assert post1.tags == [tag2]

    await tag1.fetch_related(Tag.posts)
    assert len(tag1.posts) == 1

完整代码

一对多完整示例代码
from typing import Optional

import cherry

db = cherry.Database("sqlite+aiosqlite:///:memory:")


class Student(cherry.Model):
    id: cherry.AutoIntPK = None
    name: str
    school: cherry.ForeignKey[Optional["School"]] = None

    cherry_config = cherry.CherryConfig(tablename="student", database=db)


class School(cherry.Model):
    id: cherry.AutoIntPK = None
    name: str
    students: cherry.ReverseRelation[list[Student]] = []

    cherry_config = cherry.CherryConfig(tablename="school", database=db)


async def main():
    await db.init()

    school = School(name="school 1")
    await school.insert()

    await Student(name="student 1", school=school).insert()

    school2 = School(
        name="school 2",
        students=[
            Student(name="student 2"),
            Student(name="student 3"),
        ],
    )
    await school2.insert_with_related()

    student4 = Student(name="student 4", school=School(name="school 3"))
    await student4.insert_with_related()

    # Pythonic Style
    student: list[Student] = await Student.filter(School.name == "school 2").all()
    # Django Style
    student: list[Student] = await Student.filter(school_name="school 2").all()

    student_with_school: Student = (
        await Student.filter(Student.name == "student 1")
        .prefetch_related(Student.school)
        .get()
    )

    school_with_students: School = (
        await School.filter(School.name == "school 2")
        .prefetch_related(School.students)
        .get()
    )

    schools_with_students: list[School] = (
        await School.select_related().prefetch_related(School.students).all()
    )

    school = await School.get(name="school 3")
    await school.fetch_related(School.students)
    assert len(school.students) == 1


if __name__ == "__main__":
    import asyncio

    asyncio.run(main())
多对多完整示例代码
import cherry

db = cherry.Database("sqlite+aiosqlite:///:memory:")


class Tag(cherry.Model):
    id: cherry.AutoIntPK = None
    content: str
    posts: cherry.ManyToMany[list["Post"]] = []

    cherry_config = cherry.CherryConfig(tablename="tag", database=db)


class Post(cherry.Model):
    id: cherry.AutoIntPK = None
    title: str
    tags: cherry.ManyToMany[list[Tag]] = []

    cherry_config = cherry.CherryConfig(tablename="post", database=db)


async def main():
    await db.init()

    tag1 = await Tag(content="tag 1").insert()
    tag2 = await Tag(content="tag 2").insert()

    post1 = await Post(title="post 1").insert()
    post2 = await Post(title="post 2").insert()

    await post1.add(tag1)
    await post1.add(tag2)

    await post2.add(tag1)

    assert post1.tags == [tag1, tag2]
    assert post2.tags == [tag1]

    await tag1.fetch_related(Tag.posts)
    assert len(tag1.posts) == 2

    await post1.remove(tag1)
    assert post1.tags == [tag2]

    await tag1.fetch_related(Tag.posts)
    assert len(tag1.posts) == 1


if __name__ == "__main__":
    import asyncio

    asyncio.run(main())