模型和字段
Model
类, Field
实例和模型实例都映射到数据库概念:
事情 |
对应于… |
---|---|
模型类 |
数据库表 |
字段实例 |
表上的列 |
模型实例 |
数据库表中的行 |
下面的代码显示了定义数据库连接和模型类的典型方法。
import datetime
from peewee import *
db = SqliteDatabase('my_app.db')
class BaseModel(Model):
class Meta:
database = db
class User(BaseModel):
username = CharField(unique=True)
class Tweet(BaseModel):
user = ForeignKeyField(User, backref='tweets')
message = TextField()
created_date = DateTimeField(default=datetime.datetime.now)
is_published = BooleanField(default=True)
创建的实例
Database
.db = SqliteDatabase('my_app.db')
这个
db
对象将用于管理到sqlite数据库的连接。在这个例子中,我们使用SqliteDatabase
但是你也可以用其中一个 database engines .创建一个指定数据库的基础模型类。
class BaseModel(Model): class Meta: database = db
定义一个建立数据库连接的基础模型类是一个很好的实践。这会使代码变干,因为您不必为后续模型指定数据库。
模型配置保留在名为
Meta
. 这个会议是从姜戈借来的。 Meta 配置被传递到子类,因此我们的项目的模型将所有子类 基本模型. 有 many different attributes 您可以使用配置 元模型.定义模型类。
class User(BaseModel): username = CharField(unique=True)
模型定义使用在其他流行的窗体(如sqlacalchemy或django)中看到的声明性样式。请注意,我们正在扩展 BaseModel 类如此 User 模型将继承数据库连接。
我们已经明确定义了一个 username 具有唯一约束的列。因为我们没有指定主键,Peewee将自动添加一个名为 id.
备注
如果要开始对现有数据库使用peewee,可以使用 Pwiz,模型发生器 自动生成模型定义。
领域
这个 Field
类用于描述 Model
数据库列的属性。每个字段类型都有一个对应的SQL存储类(即varchar、int),并且可以透明地处理python数据类型和底层存储之间的转换。
创建一个 Model
类,字段定义为类属性。对于django框架的用户来说,这应该是熟悉的。下面是一个例子:
class User(Model):
username = CharField()
join_date = DateTimeField()
about_me = TextField()
在上面的示例中,因为没有字段是用初始化的 primary_key=True
,将自动创建一个自动递增的主键并命名为“id”。Peewee用途 AutoField
表示一个自动递增的整数主键,这意味着 primary_key=True
.
有一种特殊类型的字段, ForeignKeyField
,它允许您以直观的方式表示模型之间的外键关系:
class Message(Model):
user = ForeignKeyField(User, backref='messages')
body = TextField()
send_date = DateTimeField(default=datetime.datetime.now)
这允许您编写如下代码:
>>> print(some_message.user.username)
Some User
>>> for message in some_user.messages:
... print(message.body)
some message
another message
yet another message
备注
参考 关系和连接 用于深入讨论外键、联接和模型之间关系的文档。
有关字段的完整文档,请参见 Fields API notes
字段类型表
字段类型 |
SQLite |
波斯特雷斯尔 |
MySQL |
---|---|---|---|
|
整数 |
序列号 |
整数 |
|
整数 |
大系列 |
大整数 |
|
整数 |
整数 |
整数 |
|
整数 |
大整数 |
大整数 |
|
整数 |
短整型 |
短整型 |
|
不支持 |
因特恒等式 |
不支持 |
|
真实的 |
真实的 |
真实的 |
|
真实的 |
双精度 |
双精度 |
|
十进制的 |
数字的 |
数字的 |
|
瓦卡尔 |
瓦卡尔 |
瓦卡尔 |
|
烧焦 |
烧焦 |
烧焦 |
|
文本 |
文本 |
文本 |
|
斑点 |
二进制数据 |
斑点 |
|
整数 |
大整数 |
大整数 |
|
斑点 |
二进制数据 |
斑点 |
|
文本 |
UUID |
VARCHAR(40) |
|
斑点 |
二进制数据 |
ValBoice(16) |
|
日期时间 |
时间戳 |
日期时间 |
|
日期 |
日期 |
日期 |
|
时间 |
时间 |
时间 |
|
整数 |
整数 |
整数 |
|
整数 |
大整数 |
大整数 |
|
整数 |
布尔 |
布尔 |
|
非类型化的 |
不支持 |
不支持 |
|
整数 |
整数 |
整数 |
字段初始化参数
所有字段类型接受的参数及其默认值:
null = False
--允许空值index = False
--在此列上创建索引unique = False
--在此列上创建唯一索引。也见 adding composite indexes .column_name = None
--在数据库中显式指定列名。default = None
--任何值或可作为未初始化模型的默认值使用的调用primary_key = False
--表的主键constraints = None
- one or more constraints, e.g.[Check('price > 0')]
sequence = None
--序列名(如果后端支持)collation = None
--用于排序字段/索引的排序规则unindexed = False
--指示应取消对虚拟表上的字段的索引(sqlite only*)choices = None
-- optional iterable containing 2-tuples ofvalue
,display
help_text = None
--表示此字段的任何有用文本的字符串verbose_name = None
--表示此字段的“用户友好”名称的字符串index_type = None
--指定自定义索引类型,例如,对于Postgres,可以指定'BRIN'
或'GIN'
索引。
某些字段采用特殊参数…
字段类型 |
特殊参数 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
备注
两个 default
和 choices
可以在数据库级别实现为 DEFAULT 和 CHECK CONSTRAINT 但任何应用程序更改都需要模式更改。因为这个, default
仅在python和 choices
未验证,但仅用于元数据目的。
要添加数据库(服务器端)约束,请使用 constraints
参数。
默认字段值
创建对象时,peewee可以为字段提供默认值。例如, IntegerField
默认为零而不是 NULL
,可以使用默认值声明该字段:
class Message(Model):
context = TextField()
read_count = IntegerField(default=0)
在某些情况下,默认值可能是动态的。一个常见的场景是使用当前日期和时间。Peewee允许您在这些情况下指定一个函数,其返回值将在创建对象时使用。注意,我们只提供函数,实际上不提供 call 它:
class Message(Model):
context = TextField()
timestamp = DateTimeField(default=datetime.datetime.now)
备注
如果使用的字段接受可变类型(list, dict, 等),并且希望提供默认值,最好将默认值包装在一个简单函数中,这样多个模型实例就不会共享对同一基础对象的引用:
def house_defaults():
return {'beds': 0, 'baths': 0}
class House(Model):
number = TextField()
street = TextField()
attributes = JSONField(default=house_defaults)
数据库还可以提供字段的默认值。虽然peewee没有显式地提供用于设置服务器端默认值的API,但是可以使用 constraints
用于指定服务器默认值的参数:
class Message(Model):
context = TextField()
timestamp = DateTimeField(constraints=[SQL('DEFAULT CURRENT_TIMESTAMP')])
ForeignKeyField
ForeignKeyField
是允许一个模型引用另一个模型的特殊字段类型。通常,外键将包含与其相关的模型的主键(但可以通过指定 field
)
外键允许数据 normalized . 在我们的示例模型中,有一个外键来自 Tweet
到 User
. 这意味着所有用户都存储在自己的表中,tweet也是如此,而从tweet到user的外键允许每个tweet point 到特定的用户对象。
备注
参考 关系和连接 用于深入讨论外键、联接和模型之间关系的文档。
在peewee中,访问 ForeignKeyField
将返回整个相关对象,例如:
tweets = (Tweet
.select(Tweet, User)
.join(User)
.order_by(Tweet.created_date.desc()))
for tweet in tweets:
print(tweet.user.username, tweet.message)
备注
在上面的示例中, User
数据被选作查询的一部分。有关此技术的更多示例,请参见 Avoiding N+1 文件。
如果我们没有选择 User
尽管如此,然后 附加查询 将发出以获取关联的 User
数据:
tweets = Tweet.select().order_by(Tweet.created_date.desc())
for tweet in tweets:
# WARNING: an additional query will be issued for EACH tweet
# to fetch the associated User data.
print(tweet.user.username, tweet.message)
有时,您只需要外键列中关联的主键值。在这种情况下,Peewee遵循Django建立的惯例,允许您通过附加 "_id"
到外键字段的名称:
tweets = Tweet.select()
for tweet in tweets:
# Instead of "tweet.user", we will just get the raw ID value stored
# in the column.
print(tweet.user_id, tweet.message)
为了防止意外解析外键并触发其他查询, ForeignKeyField
支持初始化参数 lazy_load
当被禁用时,其行为类似于 "_id"
属性。例如:
class Tweet(Model):
# ... same fields, except we declare the user FK to have
# lazy-load disabled:
user = ForeignKeyField(User, backref='tweets', lazy_load=False)
for tweet in Tweet.select():
print(tweet.user, tweet.message)
# With lazy-load disabled, accessing tweet.user will not perform an extra
# query and the user ID value is returned instead.
# e.g.:
# 1 tweet from user1
# 1 another from user1
# 2 tweet from user2
# However, if we eagerly load the related user object, then the user
# foreign key will behave like usual:
for tweet in Tweet.select(Tweet, User).join(User):
print(tweet.user.username, tweet.message)
# user1 tweet from user1
# user1 another from user1
# user2 tweet from user1
ForeignKeyField返回引用
ForeignKeyField
允许将反向引用属性绑定到目标模型。隐式地,此属性将被命名为 classname_set
在哪里 classname
是类的小写名称,但可以使用参数重写 backref
:
class Message(Model):
from_user = ForeignKeyField(User, backref='outbox')
to_user = ForeignKeyField(User, backref='inbox')
text = TextField()
for message in some_user.outbox:
# We are iterating over all Messages whose from_user is some_user.
print(message)
for message in some_user.inbox:
# We are iterating over all Messages whose to_user is some_user
print(message)
日期时间字段、日期字段和时间字段
用于处理日期和时间的三个字段具有特殊属性,允许访问年、月、小时等内容。
DateField
具有的属性:
year
month
day
TimeField
具有的属性:
hour
minute
second
DateTimeField
上面都有。
这些属性可以像任何其他表达式一样使用。假设我们有一个事件日历,并希望突出显示当前月份中已附加事件的所有日期:
# Get the current time.
now = datetime.datetime.now()
# Get days that have events for the current month.
Event.select(Event.event_date.day.alias('day')).where(
(Event.event_date.year == now.year) &
(Event.event_date.month == now.month))
备注
sqlite没有本机日期类型,因此日期存储在格式化的文本列中。为了确保比较工作正常,需要对日期进行格式化,以便按字典顺序进行排序。这就是它们默认存储为 YYYY-MM-DD HH:MM:SS
.
Bitfield和BigBitfield
这个 BitField
和 BigBitField
从3.0.0开始是新的。前者提供了 IntegerField
这适用于将功能切换存储为整数位掩码。后者适用于存储大型数据集的位图,例如表示成员资格或位图类型数据。
作为使用的示例 BitField
,假设我们有一个 Post 我们希望存储一些关于如何发布的真/假标志。我们可以将所有这些功能切换存储在它们自己的 BooleanField
对象,或者我们可以使用 BitField
而是:
class Post(Model):
content = TextField()
flags = BitField()
is_favorite = flags.flag(1)
is_sticky = flags.flag(2)
is_minimized = flags.flag(4)
is_deleted = flags.flag(8)
使用这些标志非常简单:
>>> p = Post()
>>> p.is_sticky = True
>>> p.is_minimized = True
>>> print(p.flags) # Prints 4 | 2 --> "6"
6
>>> p.is_favorite
False
>>> p.is_sticky
True
我们还可以使用post类上的标志在查询中构建表达式:
# Generates a WHERE clause that looks like:
# WHERE (post.flags & 1 != 0)
favorites = Post.select().where(Post.is_favorite)
# Query for sticky + favorite posts:
sticky_faves = Post.select().where(Post.is_sticky & Post.is_favorite)
自从 BitField
存储在整数中,最多可以表示64个标志(64位是整数列的公用大小)。对于存储任意大的位图,您可以使用 BigBitField
,它使用自动管理的字节缓冲区,存储在 BlobField
.
当批量更新 BitField
,可以使用位运算符设置或清除一个或多个位:
# Set the 4th bit on all Post objects.
Post.update(flags=Post.flags | 8).execute()
# Clear the 1st and 3rd bits on all Post objects.
Post.update(flags=Post.flags & ~(1 | 4)).execute()
对于简单的操作,这些标志提供了方便 set()
和 clear()
设置或清除单个位的方法:
# Set the "is_deleted" bit on all posts.
Post.update(flags=Post.is_deleted.set()).execute()
# Clear the "is_deleted" bit on all posts.
Post.update(flags=Post.is_deleted.clear()).execute()
示例用法:
class Bitmap(Model):
data = BigBitField()
bitmap = Bitmap()
# Sets the ith bit, e.g. the 1st bit, the 11th bit, the 63rd, etc.
bits_to_set = (1, 11, 63, 31, 55, 48, 100, 99)
for bit_idx in bits_to_set:
bitmap.data.set_bit(bit_idx)
# We can test whether a bit is set using "is_set":
assert bitmap.data.is_set(11)
assert not bitmap.data.is_set(12)
# We can clear a bit:
bitmap.data.clear_bit(11)
assert not bitmap.data.is_set(11)
# We can also "toggle" a bit. Recall that the 63rd bit was set earlier.
assert bitmap.data.toggle_bit(63) is False
assert bitmap.data.toggle_bit(63) is True
assert bitmap.data.is_set(63)
BareField
这个 BareField
类仅用于sqlite。由于sqlite使用动态类型,并且不强制执行数据类型,因此声明字段时最好不要使用 any 数据类型。在这种情况下,你可以使用 BareField
. 对于sqlite虚拟表,使用元列或非类型化列也是很常见的,因此对于这些情况,您也可能希望使用非类型化字段(尽管对于全文搜索,您应该使用 SearchField
相反!).
BareField
接受特殊参数 adapt
.此参数是一个函数,它获取来自数据库的值并将其转换为适当的python类型。例如,如果有一个虚拟表有一个未类型化的列,但您知道它将返回 int
对象,可以指定 adapt=int
.
例子:
db = SqliteDatabase(':memory:')
class Junk(Model):
anything = BareField()
class Meta:
database = db
# Store multiple data-types in the Junk.anything column:
Junk.create(anything='a string')
Junk.create(anything=12345)
Junk.create(anything=3.14159)
创建自定义字段
在Peewee中,很容易添加对自定义字段类型的支持。在这个例子中,我们将为PostgreSQL创建一个UUID字段(它有一个本机UUID列类型)。
要添加自定义字段类型,首先需要确定字段数据将存储在哪种类型的列中。如果您只想将python行为添加到一个十进制字段(例如,创建一个货币字段)上,那么您只需子类化 DecimalField
. 另一方面,如果数据库提供自定义列类型,则需要让Peewee知道。这是由 Field.field_type
属性。
备注
在船上撒尿 UUIDField
,以下代码仅用作示例。
让我们从定义UUID字段开始:
class UUIDField(Field):
field_type = 'uuid'
我们将把UUID存储在本机UUID列中。由于psycopg2默认将数据视为字符串,因此我们将向字段添加两种方法来处理:
来自数据库的数据将在我们的应用程序中使用
从我们的python应用程序进入数据库的数据
import uuid
class UUIDField(Field):
field_type = 'uuid'
def db_value(self, value):
return value.hex # convert UUID to hex string.
def python_value(self, value):
return uuid.UUID(value) # convert hex string to UUID
**此步骤是可选的。**默认情况下, field_type
值将用于数据库架构中的列数据类型。如果您需要支持多个数据库,这些数据库为您的字段数据使用不同的数据类型,那么我们需要让数据库知道如何映射这个数据类型。 uuid 标记为实际值 uuid 数据库中的列类型。在中指定替代 Database
构造函数:
# Postgres, we use UUID data-type. db = PostgresqlDatabase('my_db', field_types={'uuid': 'uuid'}) # Sqlite doesn't have a UUID type, so we use text type. db = SqliteDatabase('my_db', field_types={'uuid': 'text'})
就是这样!有些字段可能支持异域操作,例如PostgreSQL hstore字段的作用类似于键/值存储,并且具有自定义的运算符 contains 和 更新. 您可以指定 custom operations 也。例如代码,请检查 HStoreField
在 playhouse.postgres_ext
.
字段命名冲突
Model
例如,类实现了许多类和实例方法 Model.save()
或 Model.create()
. 如果声明一个名称与模型方法一致的字段,则可能会导致问题。考虑:
class LogEntry(Model):
event = TextField()
create = TimestampField() # Uh-oh.
update = TimestampField() # Uh-oh.
为避免在数据库架构中仍使用所需的列名时出现此问题,请显式指定 column_name
为字段属性提供可选名称时:
class LogEntry(Model):
event = TextField()
create_ = TimestampField(column_name='create')
update_ = TimestampField(column_name='update')
创建模型表
为了开始使用我们的模型,必须先打开到数据库的连接并创建表。Peewee将运行必要的 CREATE TABLE 查询,另外创建任何约束和索引。
# Connect to our database.
db.connect()
# Create the tables.
db.create_tables([User, Tweet])
备注
严格来说,没有必要调用 connect()
但明确表示是一种好的做法。这样,如果发生了错误,错误会发生在连接步骤,而不是稍后的某个任意时间。
备注
默认情况下,peewee包括 IF NOT EXISTS
创建表时使用子句。如果要禁用此功能,请指定 safe=False
.
创建表后,如果选择修改数据库架构(通过添加、删除或更改列),则需要:
删除表并重新创建。
运行一个或多个 ALTER TABLE 查询。Peewee附带了一个模式迁移工具,可以大大简化这一过程。检查 schema migrations 有关详细信息的文档。
模型选项和表元数据
为了不污染模型名称空间,模型特定的配置被放置在一个名为 Meta (一项借鉴了Django框架的公约):
from peewee import *
contacts_db = SqliteDatabase('contacts.db')
class Person(Model):
name = CharField()
class Meta:
database = contacts_db
这将指示Peewee每当在上执行查询时 Person 使用联系人数据库。
备注
看一看 the sample models -您会注意到我们创建了一个 BaseModel
定义了数据库,然后扩展。这是定义数据库和创建模型的首选方法。
一旦定义了类,就不应该访问 ModelClass.Meta
,而是使用 ModelClass._meta
:
>>> Person.Meta
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Person' has no attribute 'Meta'
>>> Person._meta
<peewee.ModelOptions object at 0x7f51a2f03790>
这个 ModelOptions
类实现了几种可能用于检索模型元数据的方法(例如字段列表、外键关系等)。
>>> Person._meta.fields
{'id': <peewee.AutoField object at 0x7f51a2e92750>,
'name': <peewee.CharField object at 0x7f51a2f0a510>}
>>> Person._meta.primary_key
<peewee.AutoField object at 0x7f51a2e92750>
>>> Person._meta.database
<peewee.SqliteDatabase object at 0x7f519bff6dd0>
有几个选项可以指定为 Meta
属性。虽然大多数选项都是可继承的,但有些选项是特定于表的,不会被子类继承。
期权 |
意义 |
Inheritable? |
---|---|---|
|
模型数据库 |
对 |
|
要存储数据的表的名称 |
不 |
|
动态生成表名的函数 |
对 |
|
要索引的字段列表 |
对 |
|
一 |
对 |
|
表约束列表 |
对 |
|
模型的数据库架构 |
对 |
|
调用model.save()时,只保存脏字段 |
对 |
|
用于创建表扩展名的选项字典 |
对 |
|
将字符串设置在右括号后的列表 |
对 |
|
表示临时表 |
对 |
|
使用旧表名生成(默认情况下启用) |
对 |
|
指示此表依赖另一个表进行创建 |
不 |
|
指示表不应具有rowid(仅限于sqlite) |
不 |
|
指示严格的数据类型(仅限SQLite,3.37+) |
对 |
下面是一个显示可继承属性与不可继承属性的示例:
>>> db = SqliteDatabase(':memory:')
>>> class ModelOne(Model):
... class Meta:
... database = db
... table_name = 'model_one_tbl'
...
>>> class ModelTwo(ModelOne):
... pass
...
>>> ModelOne._meta.database is ModelTwo._meta.database
True
>>> ModelOne._meta.table_name == ModelTwo._meta.table_name
False
Meta.primary_key
这个 Meta.primary_key
属性用于指定 CompositeKey
或者表示模型 no 主键。这里将更详细地讨论复合主键: 复合主键 .
若要指示模型不应具有主键,请设置 primary_key = False
.
实例:
class BlogToTag(Model):
"""A simple "through" table for many-to-many relationship."""
blog = ForeignKeyField(Blog)
tag = ForeignKeyField(Tag)
class Meta:
primary_key = CompositeKey('blog', 'tag')
class NoPrimaryKey(Model):
data = IntegerField()
class Meta:
primary_key = False
表名
默认情况下,peewee将根据模型类的名称自动生成表名。表名的生成方式取决于 Meta.legacy_table_names
. 默认情况下, legacy_table_names=True
以避免破坏向后兼容性。但是,如果希望使用新的和改进的表名生成,可以指定 legacy_table_names=False
.
此表显示了如何将模型名称转换为SQL表名称的差异,具体取决于 legacy_table_names
:
型号名称 |
legacy_table_names=True |
旧表名=假(新) |
---|---|---|
用户 |
用户 |
用户 |
UserProfile |
用户配置文件 |
user_profile |
APIResponse |
蜜蜂响应 |
api_response |
WebHTTPRequest |
网页浏览器 |
web_http_request |
混合凸轮箱 |
混合凸轮箱 |
mixed_camel_case |
姓名2号码3XYZ |
姓名2号码3XYZ |
name2_numbers3_xyz |
注意
为了保持向后兼容性,当前版本(peewee 3.x)指定 legacy_table_names=True
默认情况下。
在下一个主要版本(Peewee 4.0)中, legacy_table_names
将具有默认值 False
.
要显式指定模型类的表名,请使用 table_name
元选项。此功能可用于处理可能使用了笨拙命名约定的现有数据库架构:
class UserProfile(Model):
class Meta:
table_name = 'user_profile_tbl'
如果希望实现自己的命名约定,可以指定 table_function
元选项。此函数将与模型类一起调用,并应以字符串形式返回所需的表名。假设我们公司指定表名的大小写应为小写,并以“_tbl”结尾,我们可以将其作为表函数来实现:
def make_table_name(model_class):
model_name = model_class.__name__
return model_name.lower() + '_tbl'
class BaseModel(Model):
class Meta:
table_function = make_table_name
class User(BaseModel):
# table_name will be "user_tbl".
class UserProfile(BaseModel):
# table_name will be "userprofile_tbl".
索引和约束
Peewee可以在单个或多个列上创建索引,也可以选择包括 UNIQUE 约束。Peewee还支持对模型和字段的用户定义约束。
单列索引和约束
使用字段初始化参数定义单列索引。下面的示例在 username 字段,以及 email 领域:
class User(Model):
username = CharField(unique=True)
email = CharField(index=True)
要在列上添加用户定义的约束,可以使用 constraints
参数。您可能希望指定默认值作为架构的一部分,或者添加 CHECK
约束,例如:
class Product(Model):
name = CharField(unique=True)
price = DecimalField(constraints=[Check('price < 10000')])
created = DateTimeField(
constraints=[SQL("DEFAULT (datetime('now'))")])
多列索引
多列索引可以定义为 Meta 使用嵌套元组的属性。每个数据库索引都是一个2元组,第一部分是字段名称的元组,第二部分是指示索引是否应唯一的布尔值。
class Transaction(Model):
from_acct = CharField()
to_acct = CharField()
amount = DecimalField()
date = DateTimeField()
class Meta:
indexes = (
# create a unique on from/to/date
(('from_acct', 'to_acct', 'date'), True),
# create a non-unique on from/to
(('from_acct', 'to_acct'), False),
)
备注
记住添加 trailing comma 如果索引元组只包含一个项:
class Meta:
indexes = (
(('first_name', 'last_name'), True), # Note the trailing comma!
)
高级索引创建
Peewee支持更结构化的API,用于在使用 Model.add_index()
方法或直接使用 ModelIndex
助手类。
实例:
class Article(Model):
name = TextField()
timestamp = TimestampField()
status = IntegerField()
flags = IntegerField()
# Add an index on "name" and "timestamp" columns.
Article.add_index(Article.name, Article.timestamp)
# Add a partial index on name and timestamp where status = 1.
Article.add_index(Article.name, Article.timestamp,
where=(Article.status == 1))
# Create a unique index on timestamp desc, status & 4.
idx = Article.index(
Article.timestamp.desc(),
Article.flags.bin_and(4),
unique=True)
Article.add_index(idx)
警告
sqlite不支持参数化 CREATE INDEX
查询。这意味着,当使用sqlite创建涉及表达式或标量值的索引时,需要使用 SQL
帮手:
# SQLite does not support parameterized CREATE INDEX queries, so
# we declare it manually.
Article.add_index(SQL('CREATE INDEX ...'))
见 add_index()
有关详细信息。
有关详细信息,请参阅:
表约束
Peewee允许您向 Model
,这将是创建架构时表定义的一部分。
例如,假设您有 people 表的复合主键为两列,分别是人员的名字和姓氏。您希望另一张与 people 表,为此,需要定义一个外键约束:
class Person(Model):
first = CharField()
last = CharField()
class Meta:
primary_key = CompositeKey('first', 'last')
class Pet(Model):
owner_first = CharField()
owner_last = CharField()
pet_name = CharField()
class Meta:
constraints = [SQL('FOREIGN KEY(owner_first, owner_last) '
'REFERENCES person(first, last)')]
您还可以实现 CHECK
表级别的约束:
class Product(Model):
name = CharField(unique=True)
price = DecimalField()
class Meta:
constraints = [Check('price < 10000')]
主键、复合键和其他技巧
这个 AutoField
用于标识自动递增的整数主键。如果不指定主键,peewee将自动创建一个名为“id”的自动递增主键。
要使用其他字段名指定自动递增的ID,可以写入:
class Event(Model):
event_id = AutoField() # Event.event_id will be auto-incrementing PK.
name = CharField()
timestamp = DateTimeField(default=datetime.datetime.now)
metadata = BlobField()
您可以将其他字段标识为主键,在这种情况下,不会创建“id”列。在本例中,我们将使用某人的电子邮件地址作为主键:
class Person(Model):
email = CharField(primary_key=True)
name = TextField()
dob = DateField()
警告
我经常看到人们写下以下内容,期望得到一个自动递增的整数主键:
class MyModel(Model):
id = IntegerField(primary_key=True)
Peewee将上述模型声明理解为具有整数主键的模型,但该ID的值由应用程序确定。要创建一个自动递增的整数主键,您应该编写:
class MyModel(Model):
id = AutoField() # primary_key=True is implied.
复合主键可以使用 CompositeKey
. 请注意,这样做可能会导致 ForeignKeyField
因为Peewee不支持“复合外键”的概念。因此,我发现只有在少数情况下使用复合主键才是明智的,例如琐碎的多对多连接表:
class Image(Model):
filename = TextField()
mimetype = CharField()
class Tag(Model):
label = CharField()
class ImageTag(Model): # Many-to-many relationship.
image = ForeignKeyField(Image)
tag = ForeignKeyField(Tag)
class Meta:
primary_key = CompositeKey('image', 'tag')
在极为罕见的情况下,您希望用 no 主键,可以指定 primary_key = False
在模型中 Meta
选项。
非整数主键
如果要使用非整数主键(我通常不建议使用),可以指定 primary_key=True
创建字段时。当您希望使用非自动增量主键为模型创建一个新实例时,您需要确保 save()
指定 force_insert=True
.
from peewee import *
class UUIDModel(Model):
id = UUIDField(primary_key=True)
正如他们的名字所说,当您向数据库中插入新行时,会自动为您生成自动递增的ID。当你调用 save()
,Peewee确定是否执行 INSERT 与一个 UPDATE 基于主键值的存在。由于使用UUID示例,数据库驱动程序不会生成新的ID,因此我们需要手动指定它。当我们第一次调用save()时,传入 force_insert = True
:
# This works because .create() will specify `force_insert=True`.
obj1 = UUIDModel.create(id=uuid.uuid4())
# This will not work, however. Peewee will attempt to do an update:
obj2 = UUIDModel(id=uuid.uuid4())
obj2.save() # WRONG
obj2.save(force_insert=True) # CORRECT
# Once the object has been created, you can call save() normally.
obj2.save()
备注
具有非整数主键的模型的任何外键都将具有 ForeignKeyField
使用与其相关的主键相同的基础存储类型。
复合主键
Peewee对复合键有非常基本的支持。要使用组合键,必须设置 primary_key
模型选项的属性 CompositeKey
实例:
class BlogToTag(Model):
"""A simple "through" table for many-to-many relationship."""
blog = ForeignKeyField(Blog)
tag = ForeignKeyField(Tag)
class Meta:
primary_key = CompositeKey('blog', 'tag')
警告
Peewee不支持定义 CompositeKey
主键。如果要向具有复合主键的模型添加外键,请复制相关模型上的列并添加自定义访问器(例如属性)。
手动指定主键
有时,您不希望数据库自动为主键生成值,例如在批量加载关系数据时。在一个 one-off 基础上,你可以简单地告诉Peewee关掉 auto_increment
导入过程中:
data = load_user_csv() # load up a bunch of data
User._meta.auto_increment = False # turn off auto incrementing IDs
with db.atomic():
for row in data:
u = User(id=row[0], username=row[1])
u.save(force_insert=True) # <-- force peewee to insert row
User._meta.auto_increment = True
尽管在不使用黑客的情况下,实现上述目标的更好方法是使用 Model.insert_many()
应用程序编程接口:
data = load_user_csv()
fields = [User.id, User.username]
with db.atomic():
User.insert_many(data, fields=fields).execute()
如果你 总是 想要控制主键,只需不使用 AutoField
字段类型,但使用普通 IntegerField
(或其他列类型):
class User(BaseModel):
id = IntegerField(primary_key=True)
username = CharField()
>>> u = User.create(id=999, username='somebody')
>>> u.id
999
>>> User.get(User.username == 'somebody').id
999
没有主键的模型
如果要创建没有主键的模型,可以指定 primary_key = False
在内心深处 Meta
班级:
class MyData(BaseModel):
timestamp = DateTimeField()
value = IntegerField()
class Meta:
primary_key = False
这将产生以下DDL:
CREATE TABLE "mydata" (
"timestamp" DATETIME NOT NULL,
"value" INTEGER NOT NULL
)
警告
例如,对于没有主键的模型,某些模型API可能无法正常工作。 save()
和 delete_instance()
(您可以使用 insert()
, update()
和 delete()
)
自引用外键
创建层次结构时,需要创建一个自引用外键,将子对象链接到其父对象。由于在实例化自引用外键时未定义模型类,因此请使用特殊字符串 'self'
要指示自引用外键,请执行以下操作:
class Category(Model):
name = CharField()
parent = ForeignKeyField('self', null=True, backref='children')
正如你所看到的,外国的关键点 upward 到父对象,而后面的引用命名为 儿童.
注意
自引用外键应始终为 null=True
.
当查询包含自引用外键的模型时,有时可能需要执行自联接。在这种情况下,你可以使用 Model.alias()
创建表引用。以下是使用自联接查询类别和父模型的方法:
Parent = Category.alias()
GrandParent = Category.alias()
query = (Category
.select(Category, Parent)
.join(Parent, on=(Category.parent == Parent.id))
.join(GrandParent, on=(Parent.parent == GrandParent.id))
.where(GrandParent.name == 'some category')
.order_by(Category.name))
循环外键依赖项
有时会在两个表之间创建循环依赖关系。
备注
我个人的观点是循环外键是一种代码味道,应该进行重构(例如,通过添加一个中间表)。
用peewee添加循环外键有点困难,因为在定义其中一个外键时,它指向的模型还没有定义,导致 NameError
.
class User(Model):
username = CharField()
favorite_tweet = ForeignKeyField(Tweet, null=True) # NameError!!
class Tweet(Model):
message = TextField()
user = ForeignKeyField(User, backref='tweets')
一种选择是简单地使用 IntegerField
要存储原始ID:
class User(Model):
username = CharField()
favorite_tweet_id = IntegerField(null=True)
通过使用 DeferredForeignKey
我们可以绕过这个问题,仍然使用一个外键字段:
class User(Model):
username = CharField()
# Tweet has not been defined yet so use the deferred reference.
favorite_tweet = DeferredForeignKey('Tweet', null=True)
class Tweet(Model):
message = TextField()
user = ForeignKeyField(User, backref='tweets')
# Now that Tweet is defined, "favorite_tweet" has been converted into
# a ForeignKeyField.
print(User.favorite_tweet)
# <ForeignKeyField: "user"."favorite_tweet">
不过,还有一个怪癖需要注意。当你调用 create_table
我们将再次遇到同样的问题。因此,Peewee不会自动为任何 deferred 外键。
创建表格 and 外键约束,可以使用 SchemaManager.create_foreign_key()
创建表后创建约束的方法:
# Will create the User and Tweet tables, but does *not* create a
# foreign-key constraint on User.favorite_tweet.
db.create_tables([User, Tweet])
# Create the foreign-key constraint:
User._schema.create_foreign_key(User.favorite_tweet)
备注
由于SQLite对更改表的支持有限,因此在创建表之后,不能将外键约束添加到表中。