示例应用程序

我们将建立一个简单的 推特- 喜欢站点。示例的源代码可以在 examples/twitter 目录。你也可以 browse the source-code 在吉瑟布上。还有一个例子 blog app 如果这更符合你的喜好,但是本指南不包括。

示例应用程序使用 flask 很容易入门的Web框架。如果您还没有Flask,则需要安装它来运行示例:

pip install flask

运行示例

../_images/tweepee.jpg

安装Flask后, cd 进入twitter示例目录并执行 run_example.py 脚本:

python run_example.py

示例应用程序可在http://localhost:5000访问/

潜入代码

为简单起见,所有示例代码都包含在单个模块中, examples/twitter/app.py 。有关使用Peewee构建更大的Flask应用程序的指南,请查看 Structuring Flask Apps

模型

本着流行的Web框架Django的精神,Peewee使用声明性模型定义。如果您不熟悉django,那么您可以为每个表声明一个模型类。然后,模型类定义一个或多个字段属性,这些属性对应于表的列。对于Twitter克隆版,只有三种模式:

User:

表示一个用户帐户并存储用户名和密码,一个用于生成虚拟人物的电子邮件地址,使用 重力仪, 以及一个日期时间字段,指示创建该帐户的时间。

关系:

这是一个实用新型,它包含 User 模型和存储哪些用户相互跟踪。

消息:

类似于tweet。消息模型存储tweet的文本内容(创建时)和发布者(用户的外键)。

如果您喜欢UML,这些是表和关系:

../_images/schema.jpg

为了创建这些模型,我们需要实例化 SqliteDatabase 对象。然后我们定义模型类,将列指定为 Field 类上的实例。

# create a peewee database instance -- our models will use this database to
# persist information
database = SqliteDatabase(DATABASE)

# model definitions -- the standard "pattern" is to define a base model class
# that specifies which database to use.  then, any subclasses will automatically
# use the correct storage.
class BaseModel(Model):
    class Meta:
        database = database

# the user model specifies its fields (or columns) declaratively, like django
class User(BaseModel):
    username = CharField(unique=True)
    password = CharField()
    email = CharField()
    join_date = DateTimeField()

# this model contains two foreign keys to user -- it essentially allows us to
# model a "many-to-many" relationship between users.  by querying and joining
# on different columns we can expose who a user is "related to" and who is
# "related to" a given user
class Relationship(BaseModel):
    from_user = ForeignKeyField(User, backref='relationships')
    to_user = ForeignKeyField(User, backref='related_to')

    class Meta:
        # `indexes` is a tuple of 2-tuples, where the 2-tuples are
        # a tuple of column names to index and a boolean indicating
        # whether the index is unique or not.
        indexes = (
            # Specify a unique multi-column index on from/to-user.
            (('from_user', 'to_user'), True),
        )

# a dead simple one-to-many relationship: one user has 0..n messages, exposed by
# the foreign key. a users messages will be accessible as a special attribute,
# User.messages.
class Message(BaseModel):
    user = ForeignKeyField(User, backref='messages')
    content = TextField()
    pub_date = DateTimeField()

备注

注意,我们创建了一个 BaseModel 类,它只定义要使用的数据库。然后所有其他模型扩展这个类,并使用正确的数据库连接。

Peewee支持许多不同的 field types 它映射到数据库引擎通常支持的不同列类型。python类型与数据库中使用的类型之间的转换是透明的,允许您在应用程序中使用以下内容:

  • 字符串(Unicode或其他)

  • 整数、浮点数和 Decimal 数字。

  • 布尔值

  • 日期、时间和日期时间

  • None (空)

  • 二进制数据

创建表

为了开始使用模型,必须创建表。这是一个一次性操作,可以使用交互式解释器快速完成。我们可以创建一个小的助手函数来实现这一点:

def create_tables():
    with database:
        database.create_tables([User, Relationship, Message])

在示例应用程序旁边的目录中打开一个python shell并执行以下操作:

>>> from app import *
>>> create_tables()

备注

如果你遇到一个 ImportError 也就是说 flaskpeewee 找不到,可能安装不正确。检查 安装和测试 有关安装Peewee的说明文档。

每个模型都有一个 create_table() 运行SQL的ClassMethod CREATE TABLE 数据库中的语句。此方法将创建表,包括所有列、外键约束、索引和序列。通常,只要添加一个新模型,您就只需要执行一次。

Peewee提供了一个助手方法 Database.create_tables() 它将解决模型间的依赖关系并调用 create_table() 在每个模型上,确保按顺序创建表。

备注

在创建表之后添加字段将要求您删除表并重新创建,或者使用 更改表 查询。

或者,您可以使用 schema migrations 使用python更改数据库模式的扩展。

建立数据库连接

在上面的模型代码中,您可能注意到在名为 Meta 设置了 database 属性。Peewee允许每个模型指定它使用的数据库。有很多 Meta options 可以指定控制模型行为的对象。

这是一个peewee习语:

DATABASE = 'tweepee.db'

# Create a database instance that will manage the connection and
# execute queries
database = SqliteDatabase(DATABASE)

# Create a base-class all our models will inherit, which defines
# the database we'll be using.
class BaseModel(Model):
    class Meta:
        database = database

在开发Web应用程序时,通常在请求启动时打开连接,在返回响应时关闭连接。 您应该始终明确地管理您的连接. 例如,如果使用 connection pool ,只有在您调用 connect()close() .

我们将告诉flask,在请求/响应周期中,我们需要创建到数据库的连接。Flask提供了一些方便的装饰,使之成为一个亮点:

@app.before_request
def before_request():
    database.connect()

@app.after_request
def after_request(response):
    database.close()
    return response

备注

Peewee使用线程本地存储来管理连接状态,因此此模式可用于多线程WSGi服务器。

进行查询

User 模型有几个实例方法封装了一些特定于用户的功能:

  • following() :此用户在跟踪谁?

  • followers() :谁跟踪此用户?

这些方法在实现上相似,但在SQL中有一个重要的区别 JOINWHERE 条款:

def following(self):
    # query other users through the "relationship" table
    return (User
            .select()
            .join(Relationship, on=Relationship.to_user)
            .where(Relationship.from_user == self)
            .order_by(User.username))

def followers(self):
    return (User
            .select()
            .join(Relationship, on=Relationship.from_user)
            .where(Relationship.to_user == self)
            .order_by(User.username))

创建新对象

当新用户想要加入该站点时,我们需要确保用户名可用,如果可用,则创建一个新的 User 记录。看着 join() view, we can see that our application attempts to create the User using Model.create(). We defined the User.username 具有唯一约束的字段,因此如果使用用户名,数据库将引发 IntegrityError .

try:
    with database.atomic():
        # Attempt to create the user. If the username is taken, due to the
        # unique constraint, the database will raise an IntegrityError.
        user = User.create(
            username=request.form['username'],
            password=md5(request.form['password']).hexdigest(),
            email=request.form['email'],
            join_date=datetime.datetime.now())

    # mark the user as being 'authenticated' by setting the session vars
    auth_user(user)
    return redirect(url_for('homepage'))

except IntegrityError:
    flash('That username is already taken')

当用户希望跟踪某人时,我们将使用类似的方法。为了表示以下关系,我们在 Relationship 从一个用户指向另一个用户的表。由于上的唯一索引 from_userto_user ,我们将确保不会以重复的行结尾:

user = get_object_or_404(User, username=username)
try:
    with database.atomic():
        Relationship.create(
            from_user=get_current_user(),
            to_user=user)
except IntegrityError:
    pass

执行子查询

如果您登录并访问Twitter主页,您将看到来自您关注的用户的Twitter。为了干净地实现这一点,我们可以使用子查询:

备注

子查询, user.following() ,默认情况下通常会选择 User 模型。因为我们将它用作子查询,所以Peewee将只选择主键。

# python code
user = get_current_user()
messages = (Message
            .select()
            .where(Message.user.in_(user.following()))
            .order_by(Message.pub_date.desc()))

此代码对应于以下SQL查询:

SELECT t1."id", t1."user_id", t1."content", t1."pub_date"
FROM "message" AS t1
WHERE t1."user_id" IN (
    SELECT t2."id"
    FROM "user" AS t2
    INNER JOIN "relationship" AS t3
        ON t2."id" = t3."to_user_id"
    WHERE t3."from_user_id" = ?
)

其他感兴趣的主题

在这个示例应用程序中,还有其他一些有趣的事情值得一提。

  • 对结果列表分页的支持是在一个名为 object_list (在姜戈的推论之后)。此函数用于返回对象列表的所有视图。

    def object_list(template_name, qr, var_name='object_list', **kwargs):
        kwargs.update(
            page=int(request.args.get('page', 1)),
            pages=qr.count() / 20 + 1)
        kwargs[var_name] = qr.paginate(kwargs['page'])
        return render_template(template_name, **kwargs)
    
  • 简单的认证系统 login_required 装饰者。第一个函数只是在用户成功登录时将用户数据添加到当前会话中。装饰师 login_required 可用于包装视图函数,检查会话是否经过身份验证,以及是否重定向到登录页。

    def auth_user(user):
        session['logged_in'] = True
        session['user'] = user
        session['username'] = user.username
        flash('You are logged in as %s' % (user.username))
    
    def login_required(f):
        @wraps(f)
        def inner(*args, **kwargs):
            if not session.get('logged_in'):
                return redirect(url_for('login'))
            return f(*args, **kwargs)
        return inner
    
  • 返回404响应,而不是在数据库中找不到对象时引发异常。

    def get_object_or_404(model, *expressions):
        try:
            return model.get(*expressions)
        except model.DoesNotExist:
            abort(404)
    

备注

避免频繁复制/粘贴 object_list()get_object_or_404() ,这些功能是游戏厅的一部分。 flask extension module .

from playhouse.flask_utils import get_object_or_404, object_list

更多例子

在Peewee中有更多的例子 examples directory ,包括:

备注

喜欢这些片段,对更多感兴趣?退房 flask-peewee -一个flask插件,为您的Peewee模型提供类似于django的管理界面、RESTfulAPI、身份验证等。