flask成长记(四)

来源:互联网 发布:Linux 日志切割 编辑:程序博客网 时间:2024/06/03 11:16

flask成长记(四)

Web 程序最常用基于关系模型的数据库,这种数据库也称为 SQL 数据库, 因为它们使用结构化查询语言。

我猜一下啊,SQL就是structure query language。

不过最近几年文档数据库和键值对数据库成了流行的替代选择, 这两种数据库合称 NoSQL数据库。

sql数据库

关系型数据库把数据存储在表中,表模拟程序中不同的实体。

表的列数是固定的, 行数是可变的。

表中有个特殊的列, 称为主键,其值为表中各行的唯一标识符。 表中还可以有称为外键的
列,引用同一个表或不同表中某行的主键。 行之间的这种联系称为关系,这是关系型数据
库模型的基础。

NoSQL数据库

NoSQL 数据库一般使用集合代替表,使用文档代替记录。

NoSQL 数据库采用的设计方式使联结变得困难,所以大
多数数据库根本不支持这种操作。

SQL 数据库擅于用高效且紧凑的形式存储结构化数据。这种数据库需要花费大量精力保证
数据的一致性。 NoSQL 数据库放宽了对这种一致性的要求,从而获得性能上的优势。

python 数据库框架

大多数的数据库引擎都有对应的 Python 包,包括开源包和商业包。 Flask 并不限制你使
用何种类型的数据库包, 因此可以根据自己的喜好选择使用 MySQL、 Postgres、 SQLite、
Redis、 MongoDB 或者 CouchDB。

这里说一下MongoDB,是芒果数据库。后期会有介绍。

如果这些都无法满足需求, 还有一些数据库抽象层代码包供选择,例如 SQLAlchemy 和
MongoEngine。你可以使用这些抽象包直接处理高等级的 Python 对象,而不用处理如表、
文档或查询语言此类的数据库实体。

数据库抽象包,提供了一系列的封装操作,MongoDB是个Nosql数据库,后面试试用法。

选择数据库框架时,你要考虑很多因素。

  • 易用性:如果直接比较数据库引擎和数据库抽象层,显然后者取胜。抽象层,也称为对象关系
    映射( Object-Relational Mapper, ORM)或对象文档映射( Object-Document Mapper,
    ODM), 在用户不知觉的情况下把高层的面向对象操作转换成低层的数据库指令。

话说啥叫数据库引擎?啥叫数据库抽象层?

数据库引擎是数据库提供的操作?就是那种操作就是引擎么?
百度如下:

数据库引擎是用于存储、处理和保护数据的核心服务。利用数据库引擎可控制访问权限并快速处理事务,从而满足企业内大多数需要处理大量数据的应用程序的要求。

那我就大大方方这样理解:数据库是存放数据的仓库,那数据库引擎就是怎么把数据放入数据库啊,完成增删改查这些操作的支持吧。

与之对应的就是数据库抽象层,也是对象关系映射,ORM

说的好听,说能抽象啥的,其实用惯了SQL语句反而觉得这个不太好用。

  • 性能:
    ORM 和 ODM 把对象业务转换成数据库业务会有一定的损耗。大多数情况下,这种性
    能的降低微不足道, 但也不一定都是如此。一般情况下, ORM 和 ODM 对生产率的提
    升远远超过了这一丁点儿的性能降低, 所以性能降低这个理由不足以说服用户完全放弃
    ORM 和 ODM。 真正的关键点在于如何选择一个能直接操作低层数据库的抽象层,以
    防特定的操作需要直接使用数据库原生指令优化。

-FLask集成度
选择框架时,你不一定非得选择已经集成了 Flask 的框架,但选择这些框架可以节省
你编写集成代码的时间。 使用集成了 Flask 的框架可以简化配置和操作,所以专门为
Flask 开发的扩展是你的首选。

基于以上因素,选择使用的数据库框架是 Flask-SQLAlchemy( http://pythonhosted.org/Flask-SQLAlchemy/),这个 Flask 扩展包装了 SQLAlchemy( http://www.sqlalchemy.org/)框架。

使用Flask-SQLAlchemy管理数据库

Flask-SQLAlchemy 是一个 Flask 扩展,简化了在 Flask 程序中使用 SQLAlchemy 的操作。
SQLAlchemy 是一个很强大的关系型数据库框架, 支持多种数据库后台。 SQLAlchemy 提
供了高层 ORM,也提供了使用数据库原生 SQL 的低层功能。

和其他大多数扩展一样, Flask-SQLAlchemy 也使用 pip 安装:

pip install flask-sqlalchemy

时至今日蜗牛还是不知道框架这个到底是啥东西。
不过我聪明的头脑一想,框架就是人家给你做好一个壳壳,然后你照着人家的壳壳操作,干什么活儿用什么金刚钻人家都规定好了。

比如:

在 Flask-SQLAlchemy 中,数据库使用 URL 指定。最流行的数据库引擎采用的数据库 URL
格式如表 5-1 所示。

数据库 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

在这些 URL 中, hostname 表示 MySQL 服务所在的主机,可以是本地主机( localhost),
也可以是远程服务器。 数据库服务器上可以托管多个数据库,因此 database 表示要使用的
数据库名。如果数据库需要进行认证, username 和 password 表示数据库用户密令。

SQLite 数据库不需要使用服务器,因此不用指定 hostname、 username 和
password。 URL 中的 database 是硬盘上文件的文件名。

为什么呢?之前也说过,其实sqlite的本质是硬盘上的一个文件,文件,文件。

程序使用的数据库 URL 必须保存到 Flask 配置对象的 SQLALCHEMY_DATABASE_URI 键中。
配置对象中还有一个很有用的选项, 即 SQLALCHEMY_COMMIT_ON_TEARDOWN 键,将其设为 True
时,每次请求结束后都会自动提交数据库中的变动。
其他配置选项的作用请参阅 FlaskSQLAlchemy 的文档。

下面展示了如何初始化及配置一个简单的 SQLite 数据库。

from flask.ext.sqlalchemy import SQLAlchemybasedir = os.path.abspath(os.path.dirname(__file__))app = Flask(__name__)app.config['SQLALCHEMY_DATABASE_URI'] =\    'sqlite:///' + os.path.join(basedir, 'data.sqlite')app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = Truedb = SQLAlchemy(app)db 对象是 SQLAlchemy 类的实例,表示程序使用的数据库,同时还获得了 Flask-SQLAlchemy提供的所有功能。

定义模型

模型这个术语表示程序使用的持久化实体。 在 ORM 中,模型一般是一个 Python 类,类中
的属性对应数据库表中的列。

那这个类是个表还是啥?模型是一个类,注意模型是一个类……

Flask-SQLAlchemy 创建的数据库实例为模型提供了一个基类以及一系列辅助类和辅助函
数,可用于定义模型的结构。

下面示例的 roles 表和 users 表可定义为模型 Role 和 User。(你看,其实就是把表定义为模型了。)

定义 Role 和 User 模型class Role(db.Model):    __tablename__ = 'roles'    id = db.Column(db.Integer, primary_key=True)    name = db.Column(db.String(64), unique=True)    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)    def __repr__(self):        return '<User %r>' % self.username俩表就是俩模型。不过有点不同的是模型中有函数。类变量 __tablename__ 定义在数据库中使用的表名。如果没有定义 __tablename__, Flask-SQLAlchemy 会使用一个默认名字,但默认的表名没有遵守使用复数形式进行命名的约定,所以最好由我们自己来指定表名。其余的类变量都是该模型的属性,被定义为 db.Column类的实例。db.Column 类构造函数的第一个参数是数据库列和模型属性的类型。下面列出了一些可用的列类型以及在模型中使用的 Python 类型。

Integer int 普通整数,一般是 32 位
SmallInteger int 取值范围小的整数,一般是 16 位
BigInteger int 或 long 不限制精度的整数
Float float 浮点数
String str 变长字符串
Text str 变长字符串,对较长或不限长度的字符串做了优化
Unicode unicode 变长 Unicode 字符串
Boolean bool 布尔值
Date datetime.date 日期
Time datetime.time 时间
DateTime datetime.datetime 日期和时间

db.Column 中其余的参数指定属性的配置选项。

primary_key 如果设为 True,这列就是表的主键
unique 如果设为 True,这列不允许出现重复的值
index 如果设为 True,为这列创建索引,提升查询效率
nullable 如果设为 True,这列允许使用空值;如果设为 False,这列不允许使用空值
default 为这列定义默认值

Flask-SQLAlchemy 要求每个模型都要定义主键,这一列经常命名为 id。

虽然没有强制要求,但这两个模型都定义了 _repr()_ 方法,返回一个具有可读性的字符
串表示模型,可在调试和测试时使用。

那这么说,模型就是一个python类,这个类其实就是定义了一个表。顺便把方法也定义了。

关系

关系数据库关系数据库,那最重要的自然是关系了!

关系其实就是外键,说白了。

那游戏中的玩家和角色来说,一个玩家只能创建一个角色,但是一个角色可以被多个玩家创建。

如果玩家和角色是两个不同的模型的话,那玩家里面一定有角色这个属性。

按以前的说,你要是给玩家建立一张表,那角色这个东西就是外键。

而再回到关系,角色和玩家是一对多的关系。这是一种最简单的关系了……

关系class Role(db.Model):    # ...    users = db.relationship('User', backref='role')class User(db.Model):    # ...    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))role_id是一个属性,也就是一个列。不过那两个继承自db.Model倒是挺有意思的。关系使用 users 表中的外键连接了两行。添加到 User 模型中的 role_id 列被定义为外键, 就是这个外键建立起了关系。传给 db.ForeignKey() 的参数 'roles.id' 表明,这列的值是 roles 表中行的 id 值。添加到 Role 模型中的 users 属性代表这个关系的面向对象视角。对于一个 Role 类的实例,其 users 属性将返回与角色相关联的用户组成的列表。 db.relationship() 的第一个参数表明这个关系的另一端是哪个模型。如果模型类尚未定义,可使用字符串形式指定。db.relationship() 中的 backref 参数向 User 模型中添加一个 role 属性,从而定义反向关系。这一属性可替代 role_id 访问 Role 模型,此时获取的是模型对象,而不是外键的值。大多数情况下, db.relationship() 都能自行找到关系中的外键,但有时却无法决定把哪一列作为外键。 例如,如果 User 模型中有两个或以上的列定义为 Role 模型的外键,SQLAlchemy 就不知道该使用哪列。 如果无法决定外键,你就要为 db.relationship() 提供额外参数,从而确定所用外键。

上边的不是重点,谁想学的更深入那就自行看文档,下面才是基础。

蜗牛也只会基础。蜗牛也很绝望啊。

数据库操作

创建表:

首先,我们要让 Flask-SQLAlchemy 根据模型类创建数据库。方法是使用 db.create_all()
函数:

$ python hello.py shell>>> from hello import db>>> db.create_all()之前说这个db其实是一个包含了基本的操作的对象。这个是db = SQLAlchemy(app)来的。话说这个 shell是几个意思?之前好像讲过,忘了,后边的参数还能是runserver,具体的再查吧。

如果你查看程序目录,会发现新建了一个名为 data.sqlite 的文件。(为啥不是.db呢?数据库不应该是这个样子的么?在目录下还真的是.sqlite的文件。)这个 SQLite 数据库文件
的名字就是在配置中指定的。 如果数据库表已经存在于数据库中,那么 db.create_all()
不会重新创建或者更新这个表。如果修改模型后要把改动应用到现有的数据库中,这一特
性会带来不便。更新现有数据库表的粗暴方式是先删除旧表再重新创建:

度娘是这么说的:

SQLite不是文件,而是一种轻型数据库,用SQLite软件是可以操作SQLite数据库的。

于是我试着用slqliteExpert打开一个.sqlite文件。
夭寿啦!居然能打开!而且两张表的列什么的和模型里面的一样啊!

>>> db.drop_all()>>> db.create_all()

遗憾的是,这个方法有个我们不想看到的副作用,它把数据库中原有的数据都销毁了。

插入行

下面这段代码创建了一些角色和用户:>>> from hello import Role, User>>> admin_role = Role(name='Admin')>>> mod_role = Role(name='Moderator')>>> user_role = Role(name='User')>>> user_john = User(username='john', role=admin_role)>>> user_susan = User(username='susan', role=user_role)>>> user_david = User(username='david', role=user_role)

有没有感觉很奇怪?Role是模型。刚才用模型建表,现在的模型成了构造函数了。
模型的构造函数接受的参数是使用关键字参数指定的模型属性初始值。

这里的role也只有一个属性name属性。

这些新建对象的 id属性并没有明确设定,因为主键是由 Flask-SQLAlchemy 管理的。现在这些对象只存在于Python 中,还未写入数据库。因此 id 尚未赋值:

>>> print(admin_role.id)None>>> print(mod_role.id)None>>> print(user_role.id)None

通过数据库会话管理对数据库所做的改动, 在 Flask-SQLAlchemy 中,会话由 db.session
表示。准备把对象写入数据库之前,先要将其添加到会话中:

>>> db.session.add(admin_role)>>> db.session.add(mod_role)>>> db.session.add(user_role)>>> db.session.add(user_john)>>> db.session.add(user_susan)>>> db.session.add(user_david)

或者简写:

>>> db.session.add_all([admin_role, mod_role, user_role,... user_john, user_susan, user_david])

为了把对象写入数据库,我们要调用 commit() 方法提交会话:

>>> db.session.commit()

这都是些什么框架……把“行”写进会话,用会话保存。

大概是这里怎么看,那个新建的记录都是一个对象。
其实之前人家说过,说是隐藏了细节,让用户看起来只是对一些对象操作了。

再次查看 id 属性,现在它们已经赋值了:

>>> print(admin_role.id)1>>> print(mod_role.id)2>>> print(user_role.id)3

数据库会话 db.session 和之前介绍的 Flask session 对象没有关系。
数据库会话也称为事务。

那既然这样话好好说不好么?

这些修改需要添加到事务中,用事务去提交修改。

数据库会话能保证数据库的一致性。提交操作使用原子方式把会话中的对象全部写入数据
库。如果在写入会话的过程中发生了错误, 整个会话都会失效。如果你始终把相关改动放
在会话中提交,就能避免因部分更新导致的数据库不一致性。

数据库会话也可回滚。调用 db.session.rollback() 后,添加到数据库会话
中的所有对象都会还原到它们在数据库时的状态。

修改:

其实就是重新提交一个新的,新的会覆盖掉旧的。
在数据库会话上调用 add() 方法也能更新模型。我们继续在之前的 shell 会话中进行操作,
下面这个例子把 “Admin” 角色重命名为 “Administrator”:

>>> admin_role.name = 'Administrator'>>> db.session.add(admin_role)>>> db.session.commit()

删除:

数据库会话还有个 delete() 方法。下面这个例子把 "Moderator" 角色从数据库中删除:>>> db.session.delete(mod_role)>>> db.session.commit()注意,删除与插入和更新一样,提交数据库会话后才会执行。

其实想想增删改都容易,最麻烦的是查。

增删改都是已经以后了一个数据,往进放那就简单了。

但是查就不一样了,查是往出拿数据,这就难了。

就好比花钱容易,但是要钱就难对不对?

查询:

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

>>> Role.query.all()[<Role u'Administrator'>, <Role u'User'>]>>> User.query.all()[<User u'john'>, <User u'susan'>, <User u'david'>]

我还是感觉很奇怪,这可是俩类啊,这么个用是调用了类的静态成员了么?

但是python中……哪来的静态成员?

使用过滤器可以配置 query 对象进行更精确的数据库查询。下面这个例子查找角色为
“User” 的所有用户:

>>> User.query.filter_by(role=user_role).all()[<User u'susan'>, <User u'david'>]

若要查看 SQLAlchemy 为查询生成的原生 SQL 查询语句,只需把 query 对象转换成字
符串:

>>> str(User.query.filter_by(role=user_role))'SELECT users.id AS users_id, users.username AS users_username,users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'

如果你退出了 shell 会话,前面这些例子中创建的对象就不会以 Python 对象的形式存在,而
是作为各自数据库表中的行。如果你打开了一个新的 shell 会话,就要从数据库中读取行,
再重新创建 Python 对象。下面这个例子发起了一个查询,加载名为 “User” 的用户角色:

其实照我的理解就是,在内存里面的时候就是对象,在硬盘上面的时候就是数据库里面的行。

>>> user_role = Role.query.filter_by(name='User').first()

filter_by() 等过滤器在 query 对象上调用,返回一个更精确的 query 对象。多个过滤器可
以一起调用,直到获得所需结果。
下面列出了可在 query 对象上调用的常用过滤器。完整的列表参见 SQLAlchemy 文档
( http://docs.sqlalchemy.org)。

filter() 把过滤器添加到原查询上,返回一个新查询
filter_by() 把等值过滤器添加到原查询上,返回一个新查询
limit() 使用指定的值限制原查询返回的结果数量,返回一个新查询
offset() 偏移原查询返回的结果,返回一个新查询
order_by() 根据指定条件对原查询结果进行排序,返回一个新查询
group_by() 根据指定条件对原查询结果进行分组,返回一个新查询

在查询上应用指定的过滤器后,通过调用 all() 执行查询,以列表的形式返回结果。除了
all() 之外,还有其他方法能触发查询执行。

执行查询的其他方法:

all() 以列表形式返回查询的所有结果
first() 返回查询的第一个结果,如果没有结果,则返回 None
first_or_404() 返回查询的第一个结果,如果没有结果,则终止请求,返回 404 错误响应
get() 返回指定主键对应的行,如果没有对应的行,则返回 None
get_or_404() 返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回 404 错误响应
count() 返回查询结果的数量
paginate() 返回一个 Paginate 对象,它包含指定范围内的结果

在视图函数中操作数据库

这个话题是比较重要的。
前一节介绍的数据库操作可以直接在视图函数中进行。下面展示了首页路由的新版本,
已经把用户输入的名字写入了数据库。

@app.route('/', 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('know`', False))

在这个修改后的版本中,提交表单后,程序会使用 filter_by() 查询过滤器在数据库中查
找提交的名字。 变量 known 被写入用户会话中,因此重定向之后,可以把数据传给模板,
用来显示自定义的欢迎消息。 注意,要想让程序正常运行,你必须按照前面介绍的方法,
在 Python shell 中创建数据库表。

集成Python shell

每次启动 shell 会话都要导入数据库实例和模型,这真是份枯燥的工作。为了避免一直重复
导入,我们可以做些配置,让 Flask-Script 的 shell 命令自动导入特定的对象。
若想把对象添加到导入列表中, 我们要为 shell 命令注册一个 make_context 回调函数。

为 shell 命令添加一个上下文from flask.ext.script import Shelldef make_shell_context():    return dict(app=app, db=db, User=User, Role=Role)manager.add_command("shell", Shell(make_context=make_shell_context))make_shell_context() 函数注册了程序、数据库实例以及模型,因此这些对象能直接导入 shell:$ python hello.py shell>>> app<Flask 'app'>>>> db<SQLAlchemy engine='sqlite:////home/flask/flasky/data.sqlite'>>>> User<class 'app.User'>
0 0
原创粉丝点击