flask web开发笔记 -- 数据库

关系数据库基于关系,有主键、外键等。它存储数据高效,避免了重复;修改数据方便。缺点是连接的成本过高。

NoSQL尽量避免连接,但是增加了数据冗余,修改数据麻烦,不过查询快速。

对于中小型的应用,两者的效率差别不大。

python数据库框架

Flask对数据库没有限制,你可以使用MySQL, Postgres, SQLite, Redis, MongoDB或CouchDB等。另外还有数据抽象层SQLAlchemy和MongoEngine等。

选择数据库时需要关注:

  • 易用性:抽象层,比如ORM(object-relational mapper)或者ODM(object-document mapper)通常更有优势。

  • 性能:通常ORM或ODM因为需要多一次转换,性能更低,但是影响不是很大。

  • 移植性:要注意不同数据库和云平台等的移植性。

  • Flask集成:使用Flask的扩展通常更好。

使用Flask-SQLAlchemy进行数据库管理

安装pip install flask-sqlalchemy。各种数据的链接形式如下:

Database engine URL
MySQL mysql://username:password@hostname/database
Postgres postgresql://username:password@hostname/database
SQLite (Unix) sqlite:////absolute/path/to/database
SQLite (Windows) sqlite:///c:/absolute/path/to/database

下面我们示例一下连接sqlite:

from flask.ext.sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))app = Flask(__name__)app.config[‘SECRET_KEY‘] = ‘hard to guess string‘app.config[‘SQLALCHEMY_DATABASE_URI‘] =\    ‘sqlite:///‘ + os.path.join(basedir, ‘data.sqlite‘)app.config[‘SQLALCHEMY_COMMIT_ON_TEARDOWN‘] = True

SQLALCHEMY_COMMIT_ON_TEARDOWN会在每次请求结束后自动提交数据库改变。

模型定义

模型是应用程序使用的持久性实体。ORM下模型通常是Python类的属性和数据库表的列的对应。定义示例如下:

class Role(db.Model):
    __tablename__ = ‘roles‘
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship(‘User‘, backref=‘role‘, lazy=‘dynamic‘)

    def __repr__(self):
        return ‘<Role %r>‘ % self.nameclass User(db.Model):
    __tablename__ = ‘users‘
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    role_id = db.Column(db.Integer, db.ForeignKey(‘roles.id‘))

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

最常用的SQLAlchemy列类型如下:

Type name Integer Python type Description
Integer int Integerint Regular integer, typically 32 bits
SmallInteger int Short-range integer, typically 16 bits
BigInteger int or long Unlimited precision integer
Float float Floating-point number
Numeric decimal.Decimal Fixed-point number
String str Variable-length string
Text str Variable-length string, optimized for large or unbound length
Unicode unicode Variable-length Unicode string
UnicodeText unicode Variable-length Unicode string, optimized for large or unbound length
Boolean bool Boolean value
Date datetime.date Date value
Time datetime.time Time value
DateTime datetime.datetime Date and time value
Interval datetime.timedelta Time interval
Enum str List of string values
PickleType Any Python object Automatic Pickle serialization
LargeBinary str Binary blob

最常见的SQLAlchemy选项:

Option name Description
primary_key If set to True , the column is the table’s primary key.
unique If set to True , do not allow duplicate values for this column.
index If set to True , create an index for this column, so that queries are more efficient.
nullable If set to True , allow empty values for this column. If set to False , the column will not allow null values.
default Define a default value for the column.

关系

上面有两行是表示关系:

users = db.relationship(‘User‘, backref=‘role‘)role_id = db.Column(db.Integer, db.ForeignKey(‘roles.id‘))

Role类中的users属性代表关系基于对象的视图。能返回指定角色的相关人员列表。db.relationship()中如果对方表还没有定义好,可以用字符串表示。backref定义了反向关系,给User模型添加了role属性,它可以对象的形式代替Role模型的外键role_id。当外键由多个组成时,还需要考虑一些其他参数:

Option name Description
backref Add a back reference in the other model in the relationship.
primaryjoin Specify the join condition between the two models explicitly. This is necessary only for ambiguous relationships.
lazy
uselist If set to False , use a scalar instead of a list.
order_by Specify the ordering used for the items in the relationship.
secondary Specify the name of the association table to use in many-to-many relationships.
secondaryjoin Specify the secondary join condition for many-to-many relationships when SQLAlchemy cannotdetermine it on its own.

数据库操作

创建表

$  python hello.py shell

In [1]: from hello import db

In [2]: db.create_all()In [3]: db.drop_all()In [4]: db.create_all()

第一次执行db.create_all()的时候会生成data.sqlite文件。

插入行

先插入一些角色和用户。

In [1]: from hello import Role, User

In [2]: admin_role = Role(name=‘Admin‘)In [3]: mod_role = Role(name=‘Moderator‘)In [4]: user_role = Role(name=‘User‘)In [5]: user_john = User(username=‘john‘, role=admin_role)In [6]: user_susan = User(username=‘susan‘, role=user_role)In [7]: user_david = User(username=‘david‘, role=user_role)

现在上述角色和用户是没有写入数据库的:

In [8]: print(admin_role.id)NoneIn [9]: print(mod_role.id)NoneIn [10]: print(user_role.id)None

下面写入数据库:

In [12]: from hello import db

In [13]: db.session.add_all([admin_role, mod_role, user_role,user_john, user_susan, user_david])In [14]: db.session.commit()In [15]: print(admin_role.id)1In [16]: print(mod_role.id)2In [17]: print(user_role.id)3

注意这里session表示数据库会话。另外也可以单条的形式写入:db.session.add(admin_role)。db.session.commit()会按事务形式操作,db.session.rollback()还支持回滚。

修改行

add()可用于更新行:

In [18]: admin_role.name = ‘Administrator‘In [19]: db.session.add(admin_role)In [20]: db.session.commit()

删除行

add()可用于更新行:

In [21]: db.session.delete(mod_role)In [22]: db.session.commit()

查询行

In [23]: Role.query.all()Out[23]: [<Role u‘Administrator‘>, <Role u‘User‘>]n [24]: User.query.all()Out[24]: [<User u‘john‘>, <User u‘susan‘>, <User u‘david‘>]In [25]: User.query.filter_by(role=user_role).all()Out[25]: [<User u‘susan‘>, <User u‘david‘>]In [26]: str(User.query.filter_by(role=user_role))Out[26]: 实际的sql语句In [27]: user_role = Role.query.filter_by(name=‘User‘).first()In [28]: user_role
Out[28]: <Role u‘User‘>In [29]: users = user_role.users

In [30]: users
Out[30]: <sqlalchemy.orm.dynamic.AppenderBaseQuery at 0x7f57338b4690>In [31]: users[0].role
Out[31]: <Role u‘User‘>In [32]: user_role.users.order_by(User.username).all()Out[32]: [<User u‘david‘>, <User u‘susan‘>]In [33]: user_role.users.count
Out[33]: <bound method AppenderBaseQuery.count of <sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x7f57338b4e50>>

注意app/models.py中的定义:

users = db.relationship(‘User‘, backref=‘role‘, lazy=‘dynamic‘)

上面的dynamic是一定要指定的,要不然后面不能指定其他过滤条件,默认查询所有,负担比较重。

在视图函数中使用数据库

可以在hello.py的视图函数直接使用使用数据库,修改如下:

import osfrom flask import Flask, render_template, session, redirect, url_forfrom flask.ext.script import Managerfrom flask.ext.bootstrap import Bootstrapfrom flask.ext.moment import Momentfrom flask.ext.wtf import Formfrom wtforms import StringField, SubmitFieldfrom wtforms.validators import Requiredfrom flask.ext.sqlalchemy import SQLAlchemy

basedir = os.path.abspath(os.path.dirname(__file__))app = Flask(__name__)app.config[‘SECRET_KEY‘] = ‘hard to guess string‘app.config[‘SQLALCHEMY_DATABASE_URI‘] =\    ‘sqlite:///‘ + os.path.join(basedir, ‘data.sqlite‘)app.config[‘SQLALCHEMY_COMMIT_ON_TEARDOWN‘] = Truemanager = Manager(app)bootstrap = Bootstrap(app)moment = Moment(app)db = SQLAlchemy(app)class Role(db.Model):
    __tablename__ = ‘roles‘
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship(‘User‘, backref=‘role‘, lazy=‘dynamic‘)

    def __repr__(self):
        return ‘<Role %r>‘ % self.nameclass User(db.Model):
    __tablename__ = ‘users‘
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    role_id = db.Column(db.Integer, db.ForeignKey(‘roles.id‘))

    def __repr__(self):
        return ‘<User %r>‘ % self.usernameclass NameForm(Form):
    name = StringField(‘What is your name?‘, validators=[Required()])
    submit = SubmitField(‘Submit‘)@app.errorhandler(404)def page_not_found(e):
    return render_template(‘404.html‘), [email protected](500)def internal_server_error(e):
    return render_template(‘500.html‘), [email protected](‘/‘, methods=[‘GET‘, ‘POST‘])def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username=form.name.data)
            db.session.add(user)
            session[‘known‘] = False
        else:
            session[‘known‘] = True
        session[‘name‘] = form.name.data        return redirect(url_for(‘index‘))
    return render_template(‘index.html‘, form=form, name=session.get(‘name‘),
                           known=session.get(‘known‘, False))if __name__ == ‘__main__‘:
    manager.run()

模板也需要进行相应的修改:

{% extends "base.html" %}{% import "bootstrap/wtf.html" as wtf %}{% block title %}Flasky{% endblock %}{% block page_content %}<div class="page-header">
    <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
    {% if not known %}
    <p>Pleased to meet you!</p>
    {% else %}
    <p>Happy to see you again!</p>
    {% endif %}</div>{{ wtf.quick_form(form) }}{% endblock %}

与python shell集成

在hello.py增加:

from flask.ext.script import Manager, Shelldef make_shell_context():
    return dict(app=app, db=db, User=User, Role=Role)manager.add_command("shell", Shell(make_context=make_shell_context))

执行结果

$ python hello.py shell

In [1]: app
Out[1]: <Flask ‘hello‘>In [2]: db
Out[2]: <SQLAlchemy engine=‘sqlite:////home/andrew/flasky/data.sqlite‘>In [3]: User
Out[3]: __main__.User

使用Flask-Migrate进行数据迁移

flask-migrate基于Alembic,安装:pip install flask-migrate。加入数据迁移一般只需要添加几行:

$ python hello.py shellfrom flask.ext.migrate import Migrate, MigrateCommand# ...migrate = Migrate(app, db)manager.add_command(‘db‘, MigrateCommand)

下面创建数据迁移目录:

$ python hello.py db init
  Creating directory /home/andrew/flasky/migrations ... done
  Creating directory /home/andrew/flasky/migrations/versions ... done
  Generating /home/andrew/flasky/migrations/env.pyc ... done
  Generating /home/andrew/flasky/migrations/script.py.mako ... done
  Generating /home/andrew/flasky/migrations/README ... done
  Generating /home/andrew/flasky/migrations/alembic.ini ... done
  Generating /home/andrew/flasky/migrations/env.py ... done
  Please edit configuration/connection/logging settings in
  ‘/home/andrew/flasky/migrations/alembic.ini‘ before proceeding.

创建自动迁移脚本:

python hello.py db migrate -m "initial migration"INFO [alembic.migration] Context impl SQLiteImpl.INFO [alembic.migration] Will assume non-transactional DDL.INFO [alembic.autogenerate] Detected added table ‘roles‘INFO [alembic.autogenerate] Detected added table ‘users‘INFO [alembic.autogenerate.compare] Detected added index‘ix_users_username‘ on ‘[‘username‘]‘Generating /home/flask/flasky/migrations/versions/1bc594146bb5_initial_migration.py...done$ python hello.py db upgrade
INFO [alembic.migration] Context impl SQLiteImpl.INFO [alembic.migration] Will assume non-transactional DDL.INFO [alembic.migration] Running upgrade None -> 1bc594146bb5, initial migration

Alembic的手工创建脚本使用命令revision,自动创建使用命令migrate。手工创建生成空的upgrade()和downgrade(),自动创建则可能丢失东西。


参考资料


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。