1

从零开始用 Flask 搭建一个网站(一) 介绍了如何搭建 Python 环境,以及 Flask 应用基本项目结构。我们要搭建的网站是管理第三方集成的控制台,类似于 Slack。 本篇主要讲解数据如何在 Flask 应用中流动,其它的框架基本上也是大同小异。

数据库

既然是数据的流动,首先要建立起存取数据的地方,也就是数据库了(这里是指关系型数据库,NoSQL 不在这讨论)。第一节中我们使用了 Flask-SQLAlchemy 管理数据库,在 Flask-SQLAlchemy 中,数据库使用 URL 指定,最流行的数据库 URL 格式如下:

clipboard.png

在 config.py 中我们已经指定了数据库 URL,如果使用云平台部署程序,直接将生成的数据库 URL 写到 config.py 中 SQLALCHEMY_DATABASE_URI 即可。这里我们使用的是 SQLite 数据库。Flask-SQLAlchemy 采用数据库抽象层来操作数据库,也称为对象关系映射(Object-Relational Mapper, ORM),在用户不知不觉的情况下把高层的面向对象操作转换成低层的数据库指令,因此易用性好。我们已经在 app/__init__.py 中实例化了 SQLAlchemy 类:

app/__init__.py

from flask_sqlalchemy import SQLAlchemy
...
db = SQLAlchemy()
...

定义模型

模型类可以理解为数据库中的一张表,Flask-SQLAlchemy 提供了一个基类和一系列辅助类和函数来让我们定义模型的结构。我们直接在 app 文件夹下创建一个 models.py 文件。鉴于每个网站需求都不一样,所存的数据也不同,但本质上是大同小异的。这里以笔者网站需求为例,需要创建 Developer,Integration 和 Channel 三个表。

app/models.py 部分代码

from flask import current_app
from app import db

class Developer(db.Model):    
    __tablename__ = 'developers'    
    id = db.Column(db.Integer, primary_key=True)    
    dev_key = db.Column(db.String(40), unique=True, index=True)    
    platform = db.Column(db.String(50))    
    platform_id = db.Column(db.String(40), unique=True)    
    username = db.Column(db.String(150), index=True)    
    integrations = db.relationship('Integration', backref='developer')    
    channels = db.relationship('Channel', backref='developer')

class Integration(db.Model):    
    __tablename__ = 'integrations'    
    id = db.Column(db.Integer, primary_key=True)    
    integration_id = db.Column(db.String(40), unique=True)    
    name = db.Column(db.String(100))    
    description = db.Column(db.String(150))    
    icon = db.Column(db.String(150))    
    channel = db.Column(db.String(150))    
    token = db.Column(db.String(150))    
    developer_id = db.Column(db.Integer, db.ForeignKey('developers.id'))

class Channel(db.Model):    
    __tablename__ = 'channels'    
    id = db.Column(db.Integer, primary_key=True)    
    developer_id = db.Column(db.Integer, db.ForeignKey('developers.id'))    
    channel = db.Column(db.String(150))    

    def __repr__(self):        
        return '<Channel %r>' % self.channel

上面的每个 Class 都继承了 Model 类,因此每个类在数据库中都体现为一张表,表名用 tablename 表示,一般用复数形式。这里主要讲一下一对多的关系。可以看到上面 Developer 和 Integration 及 Channel 都有一个一对多的关系,一个用户可以有多个集成及多个频道。 看到这两句:

integrations = db.relationship('Integration', backref='developer')
developer_id = db.Column(db.Integer, db.ForeignKey('developers.id'))

第一句表明添加到 Developer 表的 integrations 属性表示这个关系的面向对象视角,对于一个 Developer 实例,integrations 属性将返回与 Developer 相关的所有 Integration,db.relationship() 第一个参数表明关系的另一端是哪个模型,backref 参数向 Integration 添加一个 developer 属性,从而定义反向关系。第二句是在 Integration 表中创建一个 developer_id 字段,这个字段被定义为外键,就是这个外键建立起了关系。传给 db.ForeignKey() 的参数 'developers.id' 表明这列的值是 developers 表中行的 id 值。另外,__repr__() 方法返回一个具有可读性的字符串表示模型,可以在调加粗文字试和测试时使用。下面我们就在命令行中操作一下数据库。
首先执行:

//创建 migrations 文件夹及相关文件
python manage.py db init

然后执行 :

//自动创建迁移脚本
python manage.py db migrate
//创建数据表或者升级到最新版本
python manage.py db upgrade

以后模型类有改动,比如删除或添加列,都要执行这两个命令,更新数据库表。现在在项目目录下应该自动生成了名为 dev 的数据库。
接下来执行如下命令进入 Python Shell:

python manage.py shell

创建表

使用 db.create_all() 就可以根据模型类创建表。如图:

clipboard.png

使用 db.drop_all() 方法就可以删除所有的表,但是这种方式比较粗暴,所有的数据也一同销毁了。

插入行

以下命令在数据库表中插入了一条数据:

clipboard.png

通过数据库会话 db.session 来管理对数据库所做的改动,在准备把对象写入数据库之前,首先要添加到会话中,然后用 commit() 方法提交会话。接下来我们继续插入一条 Integration 数据:

>>>from app.models import Integration,Channel
>>>integration = Integration(integration_id='i0001',name='github',description='first >>>application',channel='github_event',developer=developer)
>>> db.session.add(integration)
>>> db.session.commit()

注意上面的 developer 属性,正是我们在 models.py 中 Developer 模型中定义的一对多关系 integrations 属性的 backref 值, 所谓的反向关系即指在 Integration 表中每条数据都有一个 developer 属性指向 Developer 表中的某条数据,这是一对多关系的高级表示。现在可以用 developer.integrations 来查看该 developer 拥有的哪些集成,运行截图如下:

clipboard.png

修改行

在提交数据库会话之前,改变对象的某个属性然后再提交即可更新行数据。如:

>>> developer.username = 'lisi'
>>> db.session.add(developer)
>>> db.session.commit()

运行截图:

clipboard.png

删除行

调用 db.session.delete() 方法即可删除行:

>>>db.session.delete(developer)
>>>db.session.commit()

查询行

Flask-SQLAlchemy 为每个模型提供了 query 对象,最基本的查询是返回表中的所有记录:

>>>Developer.query.all()

使用过滤器可以进行更精确的查询:

>>>Developer.query.filter_by(platform='qq').all()

如果你退出了 shell 会话,前面创建的对象就不会以 Python 对象的形式存在,而是作为各自数据库表中的行。这时需要重数据库中读取行,再重新创建 Python 对象,如:

>>> new_developer = Developer.query.filter_by(dev_key=12345).first()

注意上面的 first() 方法,如果不加上,将返回一个 BaseQuery 对象,这样就不能直接用 new_developer 来访问它的属性值。

在视图函数中操作数据库

上面介绍的所有数据库操作可以直接在视图函数中进行。假设我们要做一个简陋的注册功能,下面我们就来看看如何从网页中获取数据并保存在数据库中。我们先定义一个用户模型:

app/models.py

class User(db.model):
    __tablename__ = 'users'
    id = db.column(db.Integer, primary_key=True)
    username = db.column(db.String(50), unique=True)
    password = db.column(db.String(100))

    def __repr__(self):
        return '<User %r>'  % self.username

然后在 main 文件夹下创建一个 forms.py 文件。

app/main/forms.py

from flask_wtf import Form
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import DataRequired

class UserForm(Form):      
    username = StringField('Input your username', validators=[DataRequired()])    
    password = PasswordField('Input your password', validators=[DataRequired()])    
    submit = SubmitField('Submit')

我们使用了 flask-wtf 扩展(pip install flask-wtf)来处理表单。
然后我们用在 index 页面中显示这个表单。

index.html

{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}注册{% endblock %}

{% block content %}
    {% for message in get_flashed_messages() %}    
        <div class="alert alert-warning">        
            <button type="button" class="close" data-dismiss="alert">×</button>        
            {{ message }}    
        </div>
    {% endfor %}
    <div id="form">
        <div class="col-md-4">
            {{ wtf.quick_form(form) }}
        </div>
    </div>
{% endblock %}

现在回到 views.py 中,在我们的视图函数中作如下改动:

app/main/views.py

...
from .forms import UserForm
from ..models import User
from app import db


@main.route('/', methods=['GET', 'POST'])
def signin():
    form = UserForm()
    if form is None:
        flash('Should input username and password')
    elif  form.validate_on_submit():
        user = User(username=form.username.data, password=form.password.data)
        db.session.add(user)
        try:
            db.session.commit()
            flash('User created !')
        except:
            db.session.rollback()
            flash('User create failed')
    return render_template('index.html', form=form)

接下来我们运行一下这个小试验:

python manage.py runserver

总结

本节我们介绍了在 Flask 中是如何使用 Flask-SQLAlchemy 、Flask-Migrate来管理数据库,并且示范了数据从网页储存到数据库的过程,这只是最基础的部分,下一节我们将探索如何在网页上发送请求并且得到数据,以及如何在页面之间传递数据。


极光JIGUANG
1.3k 声望1.3k 粉丝

极光(www.jiguang.cn)是中国领先的移动大数据服务商。其团队核心成员来自腾讯、摩根士丹利、豆瓣、Teradata和中国移动等公司。公司自2011年成立以来专注于为app开发者提供稳定高效的消息推送、统计分析、即时通...