外键
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
|
添加
从父端添加子模型会导致将相关模型添加到当前加载的父关系中,并设置子模型外键值并更新模型。
| 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
|
!!!警告如果您想在相关模型上添加子模型,则父模型的主键值必须存在于数据库中。
| 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值设为空。
| # 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()
|
但如果您想清除关系并同时删除子项,您可以发出:
| # this will not only clear the relation
# but also delete related course from db
await department.courses.remove(course, keep_reversed=False)
|
清除
一次调用删除所有相关模型。
与删除一样,默认情况下,clear() 会将子模型上的 ForeigKey 列清空(全部,无论它们是否加载)。
| # nulls department column on all courses related to this department
await department.courses.clear()
|
如果要从数据库中完全删除子项,请设置 keep_reversed=False
| # 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 情况下,相关模型的主键值必须存在于数据库中。
| Otherwise an IntegrityError will be raised by your database driver library.
|