【Python百日进阶-Web开发-Peewee】Day253 - Peewee 主键、复合主键等

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岳涛@心馨电脑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值