【Python百日进阶-Web开发-Peewee】Day251 - Peewee 外键字段等

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 开始, BitFieldBigBitField是新的。前者提供了一个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')
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是使用 FastAPI 中 Tortoise-ORM、SQLAlchemy 和 peewee 进行 ORM 查询的示例。 ## Tortoise-ORM ```python from fastapi import FastAPI from tortoise import fields from tortoise.contrib.fastapi import register_tortoise, HTTPNotFoundError from tortoise.models import Model from tortoise import Tortoise class User(Model): id = fields.IntField(pk=True) name = fields.CharField(50) email = fields.CharField(50) class Meta: table = "users" app = FastAPI() @app.on_event("startup") async def startup(): await Tortoise.init( db_url="sqlite://db.sqlite3", modules={"models": ["main"]} ) await Tortoise.generate_schemas() @app.on_event("shutdown") async def shutdown(): await Tortoise.close_connections() @app.get("/users") async def get_users(): users = await User.all() return users @app.get("/users/{user_id}") async def get_user(user_id: int): user = await User.get_or_none(id=user_id) if user is None: raise HTTPNotFoundError return user register_tortoise( app, db_url="sqlite://db.sqlite3", modules={"models": ["main"]}, generate_schemas=True, add_exception_handlers=True ) ``` ## SQLAlchemy ```python from fastapi import FastAPI from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) name = Column(String(50)) email = Column(String(50)) engine = create_engine("sqlite:///db.sqlite3") SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) app = FastAPI() @app.get("/users") async def get_users(): db = SessionLocal() users = db.query(User).all() db.close() return users @app.get("/users/{user_id}") async def get_user(user_id: int): db = SessionLocal() user = db.query(User).filter(User.id == user_id).first() db.close() if user is None: raise HTTPNotFoundError return user ``` ## peewee ```python from fastapi import FastAPI from peewee import SqliteDatabase, Model, CharField, IntegerField from playhouse.shortcuts import model_to_dict db = SqliteDatabase("db.sqlite3") class User(Model): id = IntegerField(primary_key=True) name = CharField() email = CharField() class Meta: database = db table_name = "users" app = FastAPI() @app.on_event("startup") def startup(): db.connect() db.create_tables([User]) @app.on_event("shutdown") def shutdown(): db.close() @app.get("/users") async def get_users(): users = [model_to_dict(user) for user in User.select()] return users @app.get("/users/{user_id}") async def get_user(user_id: int): user = User.get_or_none(User.id == user_id) if user is None: raise HTTPNotFoundError return model_to_dict(user) ``` 注意:以上示例中的代码仅用于演示 ORM 查询的基本用法,并且没有进行错误处理。在实际应用中,你应该根据需要添加适当的错误处理和安全性检查。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

岳涛@心馨电脑

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

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

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

打赏作者

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

抵扣说明:

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

余额充值