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(),自动创建则可能丢失东西。
参考资料
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。