模型方法
!!!tip 与数据库的主要交互是通过每个模型上作为 Model.objects 公开的 QuerySet 对象公开的,类似于 django orm。
| To read more about **quering, joining tables, excluding fields etc. visit [queries][queries] section.**
|
每个模型实例都有一组方法来保存、更新或加载自身。
可用的方法如下所述。
迂腐方法
请注意,每个 ormar.Model 也是一个 pydantic.BaseModel,因此所有 pydantic 方法也可在模型上使用,尤其是 model_dump() 和 model_dump_json() 方法,它们还可以接受排除、包含和其他参数。
要阅读更多信息,请查看 pydantic 文档
model_construct()
model_construct 是用于构造新实例的 __init__ 方法的原始等效项。
不同之处在于 model_construct 会跳过验证,因此当您知道数据正确且可信时应该使用它。使用构造的好处是由于跳过验证而提高了执行速度。
!!!note 请注意,与 pydantic.model_construct 方法相反 - ormar 等效方法也将处理嵌套的相关模型。
警告 请记住,由于跳过了验证,构造方法不会执行任何转换、检查等。因此,您有责任提供有效且可供数据库使用的数据。
| The only two things that construct still performs are:
* Providing a `default` value for not set fields
* Initialize nested ormar models if you pass a dictionary or a primary key value
|
模型转储()
model_dump 是从 pydantic 继承的方法,但 ormar 添加了自己的参数,并且在使用默认值时有一些细微差别,因此为了清楚起见,在此处列出了它。
model_dump 顾名思义将数据从模型树导出到字典。
model_dump参数说明:
包括(或修改)
include: Union[Set, Dict] = None
要包含在返回的字典中的字段名称集或字典。
请注意,pydantic 有一种不常见的模式,即通过索引包含/排除列表中的字段(嵌套模型也是如此)。如果您想排除所有子项中的字段,您需要将 __all__ 键传递给字典。
您不能在 pydantic 中排除 Sets 中的嵌套模型,但可以在 ormar 中(通过在关系名称上添加双下划线,即排除您可以使用的书籍的类别名称) exclude={"book__category__name"}
)
ormar 不支持索引排除/包含,并接受简化且更用户友好的表示法。
要检查如何包含/排除字段(包括嵌套字段),请查看包含解释和大量示例的字段部分。
!!!注意事实上,在ormar中,您可以排除集合中的嵌套模型,您可以在fastapi中的response_model_exclude和response_model_include中从整个模型树中排除!
排除(或修改)
exclude: Union[Set, Dict] = None
要在返回的字典中排除的字段名称的集合或字典。
请注意,pydantic 有一种不常见的模式,即通过索引包含/排除列表中的字段(嵌套模型也是如此)。如果您想排除所有子项中的字段,您需要将 __all__ 键传递给字典。
您不能在 pydantic 中排除 Sets 中的嵌套模型,但可以在 ormar 中(通过在关系名称上添加双下划线,即排除您所使用的书籍的类别名称) exclude={"book__category__name"}
)
ormar 不支持索引排除/包含,并接受简化且更用户友好的表示法。
要检查如何包含/排除字段(包括嵌套字段),请查看包含解释和大量示例的字段部分。
!!!注意事实上,在ormar中,您可以排除集合中的嵌套模型,您可以在fastapi中的response_model_exclude和response_model_include中从整个模型树中排除!
排除_取消设置
排除未设置:布尔 = False
Flag 指示创建模型时未显式设置的字段是否应从返回的字典中排除。
!!!警告 请注意,将数据保存到数据库后,每个字段都有自己的值 ->,可以由您提供、默认值或无。
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 | That means that when you load the data from database, **all** fields are set, and this flag basically stop working!
class Category(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="categories")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, default="Test")
visibility: bool = ormar.Boolean(default=True)
class Item(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
price: float = ormar.Float(default=9.99)
categories: List[Category] = ormar.ManyToMany(Category)
category = Category(name="Test 2")
assert category.model_dump() == {'id': None, 'items': [], 'name': 'Test 2',
'visibility': True}
assert category.model_dump(exclude_unset=True) == {'items': [], 'name': 'Test 2'}
await category.save()
category2 = await Category.objects.get()
assert category2.model_dump() == {'id': 1, 'items': [], 'name': 'Test 2',
'visibility': True}
# NOTE how after loading from db all fields are set explicitly
# as this is what happens when you populate a model from db
assert category2.model_dump(exclude_unset=True) == {'id': 1, 'items': [],
'name': 'Test 2', 'visibility': True}
|
排除默认值
exclude_defaults: bool = False
标志指示等于其默认值(无论是否设置)应从返回的字典中排除
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 | class Category(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="categories")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, default="Test")
visibility: bool = ormar.Boolean(default=True)
class Item(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
price: float = ormar.Float(default=9.99)
categories: List[Category] = ormar.ManyToMany(Category)
category = Category()
# note that Integer pk is by default autoincrement so optional
assert category.model_dump() == {'id': None, 'items': [], 'name': 'Test', 'visibility': True}
assert category.model_dump(exclude_defaults=True) == {'items': []}
# save and reload the data
await category.save()
category2 = await Category.objects.get()
assert category2.model_dump() == {'id': 1, 'items': [], 'name': 'Test', 'visibility': True}
assert category2.model_dump(exclude_defaults=True) == {'id': 1, 'items': []}
|
排除_无
排除_无:布尔 = False
Flag 指示是否应从返回的字典中排除等于 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 | class Category(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="categories")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, default="Test", nullable=True)
visibility: bool = ormar.Boolean(default=True)
class Item(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
price: float = ormar.Float(default=9.99)
categories: List[Category] = ormar.ManyToMany(Category)
category = Category(name=None)
assert category.model_dump() == {'id': None, 'items': [], 'name': None,
'visibility': True}
# note the id is not set yet so None and excluded
assert category.model_dump(exclude_none=True) == {'items': [], 'visibility': True}
await category.save()
category2 = await Category.objects.get()
assert category2.model_dump() == {'id': 1, 'items': [], 'name': None,
'visibility': True}
assert category2.model_dump(exclude_none=True) == {'id': 1, 'items': [],
'visibility': True}
|
except_primary_keys(仅限 ormar)
exclude_primary_keys: bool = False
将标志设置为 True 将排除树中的所有主键列,包括嵌套模型。
| class Item(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
item1 = Item(id=1, name="Test Item")
assert item1.model_dump() == {"id": 1, "name": "Test Item"}
assert item1.model_dump(exclude_primary_keys=True) == {"name": "Test Item"}
|
except_through_models(仅限 ormar)
exclude_through_models: bool = False
为每个多对多关系自动添加直通模型,并且它们在链接模型/表上保存附加参数。
设置 except_through_models=True 将排除所有直通模型,包括子模型的直通模型。
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 | class Category(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="categories")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
class Item(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
categories: List[Category] = ormar.ManyToMany(Category)
# tree defining the models
item_dict = {
"name": "test",
"categories": [{"name": "test cat"}, {"name": "test cat2"}],
}
# save whole tree
await Item(**item_dict).save_related(follow=True, save_all=True)
# get the saved values
item = await Item.objects.select_related("categories").get()
# by default you can see the through models (itemcategory)
assert item.model_dump() == {'id': 1, 'name': 'test',
'categories': [
{'id': 1, 'name': 'test cat',
'itemcategory': {'id': 1, 'category': None, 'item': None}},
{'id': 2, 'name': 'test cat2',
'itemcategory': {'id': 2, 'category': None, 'item': None}}
]}
# you can exclude those fields/ models
assert item.model_dump(exclude_through_models=True) == {
'id': 1, 'name': 'test',
'categories': [
{'id': 1, 'name': 'test cat'},
{'id': 2, 'name': 'test cat2'}
]}
|
model_dump_json()
model_dump_json() 与 model_dump() 具有完全相同的参数,因此请检查上面的内容。
当然,最终结果是一个 json 表示的字符串,而不是字典。
获取_pydantic()
get_pydantic(include: Union[Set, Dict] = None, exclude: Union[Set, Dict] = None)
此方法允许您从 ormar 模型生成 pydantic 模型,而无需重新输入所有字段。
请注意,如果您有嵌套模型,它将为您生成整个 pydantic 模型树!但以防止循环引用问题的方式。
此外,您可以传递排除和/或包含参数以仅保留您想要的字段,包括在嵌套模型中。
这意味着您可以轻松地为 fastapi 中的请求和响应创建 pydantic 模型。
!!!注意要阅读有关可能的排除/包含以及如何构建排除字典或设置的更多信息,请访问文档的字段部分
给定示例 ormar 模型如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | base_ormar_config = ormar.OrmarConfig(
metadata=sqlalchemy.MetaData(),
database=databases.Database(DATABASE_URL, force_rollback=True),
)
class Category(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="categories")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
class Item(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, default="test")
category: Optional[Category] = ormar.ForeignKey(Category, nullable=True)
|
您可以通过一个简单的调用来生成 pydantic 模型。
| PydanticCategory = Category.get_pydantic(include={"id", "name"})
|
这将生成相当于以下内容的模型:
| class Category(BaseModel):
id: Optional[int]
name: Optional[str] = "test"
|
!!!警告注意,在一个模块中拥有多个同名的类并不是一个好习惯,而且它会破坏 fastapi 文档。这就是为什么 ormar 在类名中添加随机 3 个大写字母。在上面的示例中,这意味着实际上类将被命名为 Category_XIP(BaseModel)。
要排除或包含嵌套字段,您可以使用字典或双下划线。
| # both calls are equivalent
PydanticCategory = Category.get_pydantic(include={"id", "items__id"})
PydanticCategory = Category.get_pydantic(include={"id": ..., "items": {"id"}})
|
生成的结构如下:
| class Item(BaseModel):
id: Optional[int]
class Category(BaseModel):
id: Optional[int]
items: Optional[List[Item]]
|
当然,您也可以使用深层嵌套结构,ormar 将为您生成它的 pydantic 等效项(以排除循环的方式)。
请注意上面的 Item 模型如何没有对 Category 的引用,尽管在 ormar 中关系是双向的(并且 ormar.Item 有categories 字段)。
警告注意,生成的 pydantic 模型将从原始 ormar 模型继承所有字段验证器,其中包括 ormar 选择验证器以及使用 pydantic.validator 装饰器定义的验证器。
| But, at the same time all root validators present on `ormar` models will **NOT** be copied to the generated pydantic model. Since root validator can operate on all fields and a user can exclude some fields during generation of pydantic model it's not safe to copy those validators.
If required, you need to redefine/ manually copy them to generated pydantic model.
|
加载()
默认情况下,当您查询表而不预取相关模型时,ormar 仍将构造您的相关模型,但仅使用 pk 值填充它们。您可以通过调用load()方法加载相关模型。
load() 还可用于从数据库刷新模型(如果它被其他进程更改)。
| track = await Track.objects.get(name='The Bird')
track.album.pk # will return malibu album pk (1)
track.album.name # will return None
# you need to actually load the data first
await track.album.load()
track.album.name # will return 'Malibu'
|
加载全部()
load_all(follow: bool = False, exclude: Union[List, str, Set, Dict] = None) -> Model
方法的工作方式与 load() 类似,但也会遍历调用该方法的模型的所有关系,并从数据库重新加载它们。
默认情况下,load_all 方法仅加载与调用该方法的模型直接相关(一步之遥)的模型。
但是您可以指定 follow=True 参数来遍历嵌套模型并将它们全部加载到关系树中。
警告 为了避免使用 follow=True 设置进行循环更新,load_all 保留一组已访问过的模型,并且不会对已访问过的模型执行嵌套加载。
| So if you have a diamond or circular relations types you need to perform the loads in a manual way.
```python
# in example like this the second Street (coming from City) won't be load_all, so ZipCode won't be reloaded
Street -> District -> City -> Street -> ZipCode
```
|
方法还接受可选的排除参数,其工作方式与 QuerySet 中的排除字段方法完全相同。这样您就可以从正在刷新的相关模型中删除字段或跳过整个相关模型。
方法执行一次数据库查询,因此比相关模型上对 load() 和 all() 的嵌套调用更有效。
!!!tip 要了解有关排除的更多信息,请阅读 except_fields
!!!警告所有关系都在 load_all() 上清除,因此如果排除某些嵌套模型,它们在调用后将为空。
节省()
save() -> 自我
您可以使用 QuerySet.create() 方法或将模型初始化为普通 pydantic 模型然后调用 save() 方法来创建新模型。
save() 还可用于保存对模型所做的更改,但前提是未设置主键或数据库中不存在模型。
save() 方法不会检查模型是否存在于数据库中,因此如果存在,如果尝试使用已存在的主键保存模型,您将从所选的数据库后端收到完整性错误。
| track = Track(name='The Bird')
await track.save() # will persist the model in database
track = await Track.objects.get(name='The Bird')
await track.save() # will raise integrity error as pk is populated
|
更新()
update(_columns: List[str] = None, **kwargs) -> self
您可以使用 QuerySet.update() 方法或更新模型属性(字段)并调用 update() 方法来更新模型。
如果您尝试在没有设置主键的情况下更新模型,则会引发 ModelPersistenceError 异常。
要持久保存新创建的模型,请使用 save() 或 upsert(**kwargs) 方法。
| track = await Track.objects.get(name='The Bird')
await track.update(name='The Bird Strikes Again')
|
要仅将模型中选定的列更新到数据库中,请提供应更新为 _columns 参数的列列表。
在示例中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | class Movie(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100, nullable=False, name="title")
year: int = ormar.Integer()
profit: float = ormar.Float()
terminator = await Movie(name='Terminator', year=1984, profit=0.078).save()
terminator.name = "Terminator 2"
terminator.year = 1991
terminator.profit = 0.520
# update only name
await terminator.update(_columns=["name"])
# note that terminator instance was not reloaded so
assert terminator.year == 1991
# but once you load the data from db you see it was not updated
await terminator.load()
assert terminator.year == 1984
|
!!!警告请注意,update() 不会刷新模型的实例,因此,如果您更改的列数多于传入 _columns 列表中的列数,您的模型实例将具有与数据库不同的值!
更新插入()
upsert(**kwargs) -> 自我
它是上述 save() 或 update(**kwargs) 方法的代理。
如果主键设置为->,则将调用更新方法。
如果未设置 pk,将调用 save() 方法。
| track = Track(name='The Bird')
await track.upsert() # will call save as the pk is empty
track = await Track.objects.get(name='The Bird')
await track.upsert(name='The Bird Strikes Again') # will call update as pk is already populated
|
删除()
您可以使用 QuerySet.delete() 方法或使用您的模型并调用 delete() 方法来删除模型。
| track = await Track.objects.get(name='The Bird')
await track.delete() # will delete the model from database
|
!!!tip 请注意,该跟踪对象保持不变,只有数据库中的记录被删除。
保存相关()
save_related(follow: bool = False, save_all: bool = False, exclude=Optional[Union[Set, Dict]]) -> None
方法会遍历调用该方法的模型的所有关系,并在每个未保存的模型上调用 upsert() 方法。
要了解模型何时保存,请查看上面的保存状态部分。
默认情况下,save_lated 方法仅保存与调用该方法的模型直接相关(一步之遥)的模型。
但您可以指定 follow=True 参数来遍历嵌套模型并将它们全部保存在关系树中。
默认情况下,save_lated 仅保存未保存状态的模型,这意味着它们在当前范围内进行了修改。
如果要强制保存所有相关方法,请使用 save_all=True 标志,这将更新插入所有相关模型,无论其保存状态如何。
如果您想跳过保存某些关系,您可以传递排除参数。
Exclude 可以是一组自己的模型关系,也可以是也可以包含嵌套项的字典。
!!!note 请注意,save_lated 中的排除参数仅接受关系字段名称,因此如果您传递任何其他字段,它们无论如何都会被保存
!!!note 要了解有关传递给排除的可能值的结构的更多信息,请检查 Queryset.fields 方法文档。
警告 为了避免使用 follow=True 设置进行循环更新,save_lated 在关系树的每个分支上保留一组已访问过的模型,并且不会对已访问过的模型执行嵌套 save_lated。
| So if you have circular relations types you need to perform the updates in a manual way.
|
请注意,使用 save_all=True 和 follow=True 您可以使用 save_lated() 一次保存整个关系树。
例子:
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59 | class Department(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
department_name: str = ormar.String(max_length=100)
class Course(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
course_name: str = ormar.String(max_length=100)
completed: bool = ormar.Boolean()
department: Optional[Department] = ormar.ForeignKey(Department)
class Student(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
courses = ormar.ManyToMany(Course)
to_save = {
"department_name": "Ormar",
"courses": [
{"course_name": "basic1",
"completed": True,
"students": [
{"name": "Jack"},
{"name": "Abi"}
]},
{"course_name": "basic2",
"completed": True,
"students": [
{"name": "Kate"},
{"name": "Miranda"}
]
},
],
}
# initialize whole tree
department = Department(**to_save)
# save all at once (one after another)
await department.save_related(follow=True, save_all=True)
department_check = await Department.objects.select_all(follow=True).get()
to_exclude = {
"id": ...,
"courses": {
"id": ...,
"students": {"id", "studentcourse"}
}
}
# after excluding ids and through models you get exact same payload used to
# construct whole tree
assert department_check.model_dump(exclude=to_exclude) == to_save
|
警告 save_lated() 会迭代所有关系和所有模型,并一一更新它们,因此它将保存所有模型,但在数据库查询数量方面可能不是最佳的。