模型和字段

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)
  1. 创建的实例 Database .

    db = SqliteDatabase('my_app.db')
    

    这个 db 对象将用于管理到sqlite数据库的连接。在这个例子中,我们使用 SqliteDatabase 但是你也可以用其中一个 database engines .

  2. 创建一个指定数据库的基础模型类。

    class BaseModel(Model):
        class Meta:
            database = db
    

    定义一个建立数据库连接的基础模型类是一个很好的实践。这会使代码变干,因为您不必为后续模型指定数据库。

    模型配置保留在名为 Meta . 这个会议是从姜戈借来的。 Meta 配置被传递到子类,因此我们的项目的模型将所有子类 基本模型. 有 many different attributes 您可以使用配置 元模型.

  3. 定义模型类。

    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

AutoField

整数

序列号

整数

BigAutoField

整数

大系列

大整数

IntegerField

整数

整数

整数

BigIntegerField

整数

大整数

大整数

SmallIntegerField

整数

短整型

短整型

IdentityField

不支持

因特恒等式

不支持

FloatField

真实的

真实的

真实的

DoubleField

真实的

双精度

双精度

DecimalField

十进制的

数字的

数字的

CharField

瓦卡尔

瓦卡尔

瓦卡尔

FixedCharField

烧焦

烧焦

烧焦

TextField

文本

文本

文本

BlobField

斑点

二进制数据

斑点

BitField

整数

大整数

大整数

BigBitField

斑点

二进制数据

斑点

UUIDField

文本

UUID

VARCHAR(40)

BinaryUUIDField

斑点

二进制数据

ValBoice(16)

DateTimeField

日期时间

时间戳

日期时间

DateField

日期

日期

日期

TimeField

时间

时间

时间

TimestampField

整数

整数

整数

IPField

整数

大整数

大整数

BooleanField

整数

布尔

布尔

BareField

非类型化的

不支持

不支持

ForeignKeyField

整数

整数

整数

注解

在上表中看不到您要查找的字段?很容易创建自定义字段类型并将其与模型一起使用。

字段初始化参数

所有字段类型接受的参数及其默认值:

  • 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 of value, display

  • help_text = None --表示此字段的任何有用文本的字符串

  • verbose_name = None --表示此字段的“用户友好”名称的字符串

  • index_type = None --指定自定义索引类型,例如,对于Postgres,可以指定 'BRIN''GIN' 索引。

某些字段采用特殊参数…

字段类型

特殊参数

CharField

max_length

FixedCharField

max_length

DateTimeField

formats

DateField

formats

TimeField

formats

TimestampField

resolution, utc

DecimalField

max_digits, decimal_places, auto_round, rounding

ForeignKeyField

model, field, backref, on_delete, on_update, deferrable lazy_load

BareField

adapt

注解

两个 defaultchoices 可以在数据库级别实现为 DEFAULTCHECK 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)

注解

如果使用的字段接受可变类型(listdict, 等),并且希望提供默认值,最好将默认值包装在一个简单函数中,这样多个模型实例就不会共享对同一基础对象的引用:

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')])

注解

**记住:**使用 default 参数,这些值由peewee设置,而不是作为实际表和列定义的一部分。

ForeignKeyField

ForeignKeyField 是允许一个模型引用另一个模型的特殊字段类型。通常,外键将包含与其相关的模型的主键(但可以通过指定 field

外键允许数据 normalized . 在我们的示例模型中,有一个外键来自 TweetUser . 这意味着所有用户都存储在自己的表中,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

这个 BitFieldBigBitField 从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 .

示例用法:

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 也。例如代码,请检查 HStoreFieldplayhouse.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?

database

模型数据库

table_name

要存储数据的表的名称

table_function

动态生成表名的函数

indexes

要索引的字段列表

primary_key

CompositeKey 实例

constraints

表约束列表

schema

模型的数据库架构

only_save_dirty

调用model.save()时,只保存脏字段

options

用于创建表扩展名的选项字典

table_settings

将字符串设置在右括号后的列表

temporary

表示临时表

legacy_table_names

使用旧表名生成(默认情况下启用)

depends_on

指示此表依赖另一个表进行创建

without_rowid

指示表不应具有rowid(仅限于sqlite)

下面是一个显示可继承属性与不可继承属性的示例:

>>> 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对更改表的支持有限,因此在创建表之后,不能将外键约束添加到表中。