示例应用程序
我们将建立一个简单的 推特- 喜欢站点。示例的源代码可以在 examples/twitter
目录。你也可以 browse the source-code 在吉瑟布上。还有一个例子 blog app 如果这更符合你的喜好,但是本指南不包括。
示例应用程序使用 flask 很容易入门的Web框架。如果您还没有Flask,则需要安装它来运行示例:
pip install flask
运行示例
安装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,这些是表和关系:
为了创建这些模型,我们需要实例化 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 也就是说 flask 或 peewee 找不到,可能安装不正确。检查 安装和测试 有关安装Peewee的说明文档。
每个模型都有一个 create_table()
运行SQL的ClassMethod CREATE TABLE 数据库中的语句。此方法将创建表,包括所有列、外键约束、索引和序列。通常,只要添加一个新模型,您就只需要执行一次。
Peewee提供了一个助手方法 Database.create_tables()
它将解决模型间的依赖关系并调用 create_table()
在每个模型上,确保按顺序创建表。
建立数据库连接
在上面的模型代码中,您可能注意到在名为 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中有一个重要的区别 JOIN 和 WHERE 条款:
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_user
和 to_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 ,包括:
Example blog app 使用长颈瓶和小瓶子。另请参阅 accompanying blog post 。
An encrypted command-line diary. There is a companion blog post 你可能也会喜欢的。
Analytics web-service (就像谷歌分析(Google Analytics)的精简版)。另请查看 companion blog post 。
备注
喜欢这些片段,对更多感兴趣?退房 flask-peewee -一个flask插件,为您的Peewee模型提供类似于django的管理界面、RESTfulAPI、身份验证等。