文章目录
7.1.4 外键字段
ForeignKeyField是一种特殊的字段类型,允许一个模型引用另一个模型。通常,外键将包含与其相关的模型的主键(但您可以通过指定 a 来指定特定列 field)。
外键允许数据被规范化。在我们的示例模型中,有一个外键 from Tweetto User。这意味着所有用户都存储在他们自己的表中,就像推文一样,从推文到用户的外键允许每条推文指向一个特定的用户对象。
笔记
有关模型之间的外键、连接和关系的深入讨论,请参阅关系和连接文档。
在 peewee 中,访问 a 的值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数据被选为查询的一部分。有关此技术的更多示例,请参阅避免 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
7.1.5 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)
7.1.6 日期时间字段、日期字段和时间字段
专门用于处理日期和时间的三个字段具有允许访问年、月、小时等内容的特殊属性。
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
7.1.7 位域和大位域
从3.0.0 开始, BitField
和BigBitField
是新的。前者提供了一个IntegerField适合将功能切换存储为整数位掩码的子类。后者适用于存储大型数据集的位图,例如表示成员资格或位图类型的数据。
作为使用 的示例,BitField假设我们有一个Post模型,并且我们希望存储有关发布方式的某些 True/False 标志。我们可以将所有这些功能切换存储在它们自己的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.
批量更新 a 中的一个或多个位时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)
7.1.8 BareField
该类BareField旨在仅与 SQLite 一起使用。由于 SQLite 使用动态类型并且不强制使用数据类型,因此声明没有任何数据类型的字段是完全可以的。在这些情况下,您可以使用 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)
7.1.9 创建自定义字段
在 peewee 中添加对自定义字段类型的支持很容易。在此示例中,我们将为 postgresql 创建一个 UUID 字段(它具有本机 UUID 列类型)。
要添加自定义字段类型,您需要首先确定字段数据将存储在哪种类型的列中。如果您只想在例如十进制字段(例如创建货币字段)上添加 python 行为,您只需子类DecimalField。另一方面,如果数据库提供自定义列类型,则需要让 peewee 知道。这是由 Field.field_type属性控制的。
笔记
Peewee 附带一个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和update之类的自定义运算符。您也可以指定自定义操作。例如代码,请查看HStoreField, in的源代码playhouse.postgres_ext。
7.1.10 字段命名冲突
Model类实现了许多类和实例方法,例如Model.save()or 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')