Skip to content

外键

ForeignKey(to: Model, *, name: str = None, unique: bool = False, nullable: bool = True, related_name: str = None, virtual: bool = False, onupdate: Union[ReferentialAction, str] = None, ondelete: Union[ReferentialAction, str] = None, **kwargs: Any) 具有采用目标模型类所需的参数。

Sqlalchemy 列和类型自动从目标模型中获取。

  • Sqlalchemy 列:目标模型主键列的类
  • 类型(用于 pydantic):目标模型的类型

定义模型

要定义关系,请添加指向相关模型的外键字段。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from typing import Optional

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Department] = ormar.ForeignKey(Department)

反向关系

外键字段会自动注册关系的反面。

默认情况下,它是子(源)模型名称 + s,如下面代码片段中的课程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import asyncio
from typing import Optional

import databases
import ormar
import sqlalchemy
from examples import create_drop_database

DATABASE_URL = "sqlite:///test.db"

ormar_base_config = ormar.OrmarConfig(
    database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)


class Department(ormar.Model):
    ormar_config = ormar_base_config.copy(tablename="departments")

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)


class Course(ormar.Model):
    ormar_config = ormar_base_config.copy()

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Department] = ormar.ForeignKey(Department)


@create_drop_database(base_config=ormar_base_config)
async def verify():
    department = await Department(name="Science").save()
    course = Course(name="Math", completed=False, department=department)
    print(department.courses[0])
    # Will produce:
    # Course(id=None,
    #        name='Math',
    #        completed=False,
    #        department=Department(id=None, name='Science'))
    await course.save()


asyncio.run(verify())

反向关系公开 API 来从父端管理相关对象。

跳过反向关系

如果您确定不想要反向关系,可以使用外键的skip_reverse = True标志。

如果您在内部设置了skip_reverse标志,该字段仍然注册在关系的另一端,因此您可以:

  • 按反向模型中的相关模型字段进行过滤
  • order_by by 反向模型中的相关模型字段

但你不能:

  • 使用 related_name 从反向模型访问相关字段
  • 即使您从模型的反面选择相关,返回的模型也不会填充在反向实例中(不会阻止连接,因此您仍然可以对关系进行过滤和排序)
  • 该关系不会填充在 model_dump() 和 model_dump_json() 中
  • 从字典或 json 填充时(也通过 fastapi),您无法传递嵌套的相关对象。根据 pydantic 配置中的额外设置,它将被忽略或引发错误。

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Author(ormar.Model):
    ormar_config = base_ormar_config.copy()

    id: int = ormar.Integer(primary_key=True)
    first_name: str = ormar.String(max_length=80)
    last_name: str = ormar.String(max_length=80)


class Post(ormar.Model):
    ormar_config = base_ormar_config.copy()

    id: int = ormar.Integer(primary_key=True)
    title: str = ormar.String(max_length=200)
    author: Optional[Author] = ormar.ForeignKey(Author, skip_reverse=True)

# create sample data
author = Author(first_name="Test", last_name="Author")
post = Post(title="Test Post", author=author)

assert post.author == author  # ok
assert author.posts  # Attribute error!

# but still can use in order_by
authors = (
    await Author.objects.select_related("posts").order_by("posts__title").all()
)
assert authors[0].first_name == "Test"

# note that posts are not populated for author even if explicitly
# included in select_related - note no posts in model_dump()
assert author.model_dump(exclude={"id"}) == {"first_name": "Test", "last_name": "Author"}

# still can filter through fields of related model
authors = await Author.objects.filter(posts__title="Test Post").all()
assert authors[0].first_name == "Test"
assert len(authors) == 1

添加

从父端添加子模型会导致将相关模型添加到当前加载的父关系中,并设置子模型外键值并更新模型。

1
2
3
4
5
6
7
8
9
department = await Department(name="Science").save()
course = Course(name="Math", completed=False) # note - not saved

await department.courses.add(course)
assert course.pk is not None # child model was saved
# relation on child model is set and FK column saved in db
assert course.department == department
# relation on parent model is also set
assert department.courses[0] == course

!!!警告如果您想在相关模型上添加子模型,则父模型的主键值必须存在于数据库中。

1
2
3
4
Otherwise ormar will raise `RelationshipInstanceError` as it cannot set child's ForeignKey column value 
if parent model has no primary key value.

That means that in example above the department has to be saved before you can call `department.courses.add()`.

!!!警告此方法不适用于多对多关系 - 在这种情况下,在添加到关系之前必须保存关系的双方。

消除

相关型号一一删除。

在反向关系中,调用remove()不会删除子模型,而是将其ForeignKey值设为空。

1
2
3
4
5
6
7
8
9
# continuing from above
await department.courses.remove(course)
assert len(department.courses) == 0
# course still exists and was saved in remove
assert course.pk is not None
assert course.department is None

# to remove child from db
await course.delete()

但如果您想清除关系并同时删除子项,您可以发出:

1
2
3
# this will not only clear the relation 
# but also delete related course from db
await department.courses.remove(course, keep_reversed=False)

清除

一次调用删除所有相关模型。

与删除一样,默认情况下,clear() 会将子模型上的 ForeigKey 列清空(全部,无论它们是否加载)。

1
2
# nulls department column on all courses related to this department
await department.courses.clear()

如果要从数据库中完全删除子项,请设置 keep_reversed=False

1
2
# deletes from db all courses related to this department 
await department.courses.clear(keep_reversed=False)

查询集代理

反向关系公开了 QuerysetProxy API,它允许您像发出普通查询一样查询相关模型。

要了解 QuerySet 的哪些方法可用,请阅读下面的 querysetproxy

相关名称

您可以通过提供 related_name 参数来覆盖相关模型字段名称,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from typing import Optional

import databases
import ormar
import sqlalchemy

DATABASE_URL = "sqlite:///test.db"

ormar_base_config = ormar.OrmarConfig(
    database=databases.Database(DATABASE_URL), metadata=sqlalchemy.MetaData()
)


class Department(ormar.Model):
    ormar_config = ormar_base_config.copy()

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)


class Course(ormar.Model):
    ormar_config = ormar_base_config.copy()

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Department] = ormar.ForeignKey(
        Department, related_name="my_courses"
    )


department = Department(name="Science")
course = Course(name="Math", completed=False, department=department)

print(department.my_courses[0])
# Will produce:
# Course(id=None,
#        name='Math',
#        completed=False,
#        department=Department(id=None, name='Science'))

!!!tip 访问时的反向关系返回 wekref.proxy 列表以避免循环引用。

!!!警告当您向同一模型提供多个关系时,否则将无法再为您自动生成 related_name。因此,在这种情况下,您必须为除一个字段(一个可以是默认值并生成)之外的所有字段或所有相关字段提供 related_name。

参考行动

当ForeignKey引用的对象发生更改(删除或更新)时,ormar将设置由ondelete和onupdate参数指定的SQL约束。

ondelete 和 onupdate 的可能值可在 ormar.ReferentialAction 中找到:

!!!note 您可以直接将字符串值传递给这两个参数,而不是 ormar.ReferentialAction,但不建议这样做,因为这会破坏完整性。

级联

每当删除(或更新)父(引用)表中的行时,子(引用)表中具有匹配外键列的相应行也将被删除(或更新)。这称为级联删除(或更新)。

限制

当引用表或子表中存在引用被引用表中的值的行时,无法更新或删除该值。

同样,只要引用表或子表中有对行的引用,就无法删除该行。

设置_NULL

将ForeignKey设置为None;仅当 nullable 为 True 时,这才有可能。

设置默认值

将ForeignKey设置为其默认值;必须设置ForeignKey 的server_default。

!!!note 请注意,默认值是不允许的,您必须通过 server_default 来执行此操作,您可以在本节中阅读有关内容。

不做任何事

不采取任何行动;无行动和限制非常相似。 NO ACTION 和 RESTRICT 之间的主要区别在于,对于 NO ACTION,引用完整性检查是在尝试更改表后完成的。 RESTRICT 在尝试执行 UPDATE 或 DELETE 语句之前进行检查。如果参照完整性检查失败,两个参照操作的行为相同:UPDATE 或 DELETE 语句将导致错误。

关系设置

您可以通过多种方式建立关系连接。

模型实例

最明显的一个是将相关的 Model 实例传递给构造函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from typing import Dict, Optional, Union

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)


department = Department(name="Science")

# set up a relation with actual Model instance
course = Course(name="Math", completed=False, department=department)

# set up  relation with only related model pk value
course2 = Course(name="Math II", completed=False, department=department.pk)

# set up a relation with dictionary corresponding to related model
course3 = Course(name="Math III", completed=False, department=department.model_dump())

# explicitly set up None
course4 = Course(name="Math III", completed=False, department=None)

主键值

您还可以仅使用相关模型的 pk 列值来设置关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from typing import Dict, Optional, Union

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)


department = Department(name="Science")

# set up a relation with actual Model instance
course = Course(name="Math", completed=False, department=department)

# set up  relation with only related model pk value
course2 = Course(name="Math II", completed=False, department=department.pk)

# set up a relation with dictionary corresponding to related model
course3 = Course(name="Math III", completed=False, department=department.model_dump())

# explicitly set up None
course4 = Course(name="Math III", completed=False, department=None)

字典

下一个选项是使用相关模型的键值字典。

您可以自己构建字典或使用 model_dump() 方法从现有模型中获取它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from typing import Dict, Optional, Union

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)


department = Department(name="Science")

# set up a relation with actual Model instance
course = Course(name="Math", completed=False, department=department)

# set up  relation with only related model pk value
course2 = Course(name="Math II", completed=False, department=department.pk)

# set up a relation with dictionary corresponding to related model
course3 = Course(name="Math III", completed=False, department=department.model_dump())

# explicitly set up None
course4 = Course(name="Math III", completed=False, department=None)

没有任何

最后,您可以显式地将其设置为 None(如果未传递值,则为默认行为)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from typing import Dict, Optional, Union

import databases
import ormar
import sqlalchemy

database = databases.Database("sqlite:///db.sqlite")
metadata = sqlalchemy.MetaData()


class Department(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)


class Course(ormar.Model):
    ormar_config = ormar.OrmarConfig(
        database=database,
        metadata=metadata,
    )

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=100)
    completed: bool = ormar.Boolean(default=False)
    department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department)


department = Department(name="Science")

# set up a relation with actual Model instance
course = Course(name="Math", completed=False, department=department)

# set up  relation with only related model pk value
course2 = Course(name="Math II", completed=False, department=department.pk)

# set up a relation with dictionary corresponding to related model
course3 = Course(name="Math III", completed=False, department=department.model_dump())

# explicitly set up None
course4 = Course(name="Math III", completed=False, department=None)

!!!警告 在所有非 None 情况下,相关模型的主键值必须存在于数据库中。

1
Otherwise an IntegrityError will be raised by your database driver library.