7.5 主键、复合键和其他技巧
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')
在极少数情况下,您希望声明一个没有主键的模型,您可以在模型选项中指定。primary_key = FalseMeta
7.5.1 非整数主键
如果您想使用非整数主键(我通常不推荐),您可以primary_key=True在创建字段时指定。当您希望使用非自动递增主键为模型创建新实例时,您需要确保save()指定 force_insert=True.
from peewee import *
class UUIDModel(Model):
id = UUIDField(primary_key=True)
顾名思义,自动递增 ID 是在您将新行插入数据库时自动为您生成的。当您调用 时 ,peewee 会根据主键值的存在save()来确定是执行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使用与它们相关的主键相同的底层存储类型。
7.5.2 复合主键
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主键的模型的外键。如果您希望向具有复合主键的模型添加外键,请复制相关模型上的列并添加自定义访问器(例如属性)。
7.5.3 手动指定主键
有时您不希望数据库自动为主键生成值,例如在批量加载关系数据时。要一次性auto_increment处理此问题,您可以简单地告诉 peewee 在导入期间关闭:
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()API:
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
7.5.4 没有主键的模型
如果你想创建一个没有主键的模型,你可以 在内部类中指定:primary_key = FalseMeta
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()and delete_instance()
(您可以改用insert(),update()和 delete())。
7.6 自引用外键
在创建层次结构时,有必要创建一个自引用外键,它将子对象链接到其父对象。由于在实例化自引用外键时未定义模型类,因此请使用特殊字符串’self’来指示自引用外键:
class Category(Model):
name = CharField()
parent = ForeignKeyField('self', null=True, backref='children')
如您所见,外键向上指向父对象,反向引用被命名为children。
注意力
自引用外键应始终为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))
7.7 循环外键依赖
有时,您会在两个表之间创建循环依赖关系。
笔记
我个人的看法是,循环外键是一种代码味道,应该重构(例如,通过添加中间表)。
使用 peewee 添加循环外键有点棘手,因为在您定义任一外键时,它指向的模型尚未定义,导致NameError.
class User(Model):
username = CharField()
favorite_tweet = ForeignKeyField(Tweet, null=True) # NameError!!
class Tweet(Model):
message = TextField()
user = ForeignKeyField(User, backref='tweets')
一种选择是简单地使用 anIntegerField来存储原始 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 不会自动为任何延迟外键创建外键约束。
要创建表和外键约束,可以使用 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 对更改表的支持有限,所以在创建表后不能将外键约束添加到表中。