Python的Flask框架与数据库连接的教程


Posted in Python onApril 20, 2015

 命令行方式运行Python脚本

在这个章节中,我们将写一些简单的数据库管理脚本。在此之前让我们来复习一下如何通过命令行方式执行Python脚本.

如果Linux 或者OS X的操作系统,需要有执行脚本的权限。例如:

chmod a+x script.py

该脚本有个指向使用解释器的命令行。再脚本赋予执行权限后就可以通过命令行执行,就像这样: like this:

./script.py <arguments>

然而,在Windows系统上这样做是不行的,你必须提供Python解释器作为必选参数,如:

flask/Scripts/python script.py <arguments>

为了避免Python解释器路径输入出错,你可以将你的文件夹microoblog/flask/Scripts添加到系统路径,确保能正常显示Python解释器。

从现在开始,在Linux/OS X上的语句简洁。如果你使用Windows系统请记得转换语句。 
 
在Flask使用数据库

我们将使用Flask-SQLAlchemy 的扩展来管理数据库。由SQLAlchemy项目提供的,已封装了关系对象映射(ORM)的一个插件。

ORMs允许数据库程序用对象的方式替代表和SQL语句。面向对象的操作被ORM转化为数据库命令。这样就意味着,不用sql语句,让Flask-SQLAlchemy为我们执行sql语句。
 
迁移

大多数数据库教程都覆盖了创建和使用一个数据库的方法,但是没有充分解决当应用程序扩展时数据库更新的问题。通常,你会删除旧的数据库,然后再创建一个新的数据库来达到更新的效果,这样就丢失了所有的数据。如果这些数据创建起来很费劲,那么我们不得不写导入导出的脚本了。

幸运的是,我们有了更好的方案.

我们现在可以使用SQLAlchemy-migrate做数据库迁移的更新了,虽然它增加了数据库启动时的负担,但这点小小的代价还是值得的,毕竟我们不用担心手动迁移数据库的问题了。

理论学习完毕,我们开始吧!

配置

我们的小程序使用sqlite数据库。sqlite是小程序数据库的最佳选择,一个可以以单文件存储的数据库。

在我们的配置文件中添加新的配置项 (fileconfig.py):
 

import os
basedir = os.path.abspath(os.path.dirname(__file__))
 
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')

SQLALCHEMY_DATABASE_URI是the Flask-SQLAlchemy必需的扩展。这是我们的数据库文件的路径。

SQLALCHEMY_MIGRATE_REPO 是用来存储SQLAlchemy-migrate数据库文件的文件夹。

最后,初始化应用的时候也需要初始化数据库。这里是升级后的init文件(fileapp/__init):
 

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
 
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
 
from app import views, models

注意生成的脚本已改动2个地方。我们现在开始创建数据库的adb对象,引用新的模块。马上来写这个模块。

数据库模型

我们在数据库存储的数据通过数据库model层被映射为一些类里面的对象,ORM层将根据类对象映射到数据库对应的字段.

让我们来创建个映射到users的model。使用WWW SQL Designer工具,我们创建了代表users表的一个图标: 

Python的Flask框架与数据库连接的教程

 id字段通常作为主键的形式用在所有的models里面,每个在数据库中的user都有一个指定的唯一id值。幸运的是,这些都是自动的,我们只需要提供一个id字段。

nickname和email字段被定义为string类型,他们的长度也已经被指定,这样可以节省数据库存储空间。

role字段被定义为integer类型,我们用来标识users是admins还是其他类型。

现在我们已经明确了users表的结构,接下来转换为编码的工作将相当简单了(fileapp/models.py):
 

from app import db
 
ROLE_USER = 0
ROLE_ADMIN = 1
 
class User(db.Model):
  id = db.Column(db.Integer, primary_key = True)
  nickname = db.Column(db.String(64), index = True, unique = True)
  email = db.Column(db.String(120), index = True, unique = True)
  role = db.Column(db.SmallInteger, default = ROLE_USER)
 
  def __repr__(self):
    return '<User %r>' % (self.nickname)

User类把我们刚刚创建的几个字段定义为类变量。字段使用db.Column类创建实例,字段的类型作为参数,另外还提供一些其他可选参数。例如,标识字段唯一性和索引的参数.

__repr__方法告诉Python如何打印class对象,方便我们调试使用。

创建数据库

把配置和model放到正确的目录位置,现在我们创建数据库文件。SQLAlchemy-migrate包自带命令行工具和APIs来创建数据库,这样的方式可以方便以后更新。但是我觉得使用这个命令行工具有些别扭,所以我自己写了个python脚本来调用迁移的APIs.

这里有个创建数据库的脚本 (filedb_create.py):
 

#!flask/bin/python
from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
from app import db
import os.path
db.create_all()
if not os.path.exists(SQLALCHEMY_MIGRATE_REPO):
  api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository')
  api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
else:
  api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))

注意这个脚本是完全通用的,所有的应用路径名都是从配置文件读取的。当你用在自己的项目时,你可以把脚本拷贝到你app`s目录下就能正常使用了。

创建数据库你只需要运行下面的一条命令(注意windows下稍微有些不同):

./db_create.py

运行这条命令之后,你就创建了一个新的app.db文件。这是个支持迁移的空sqlite数据库,同时也会生成一个带有几个文件的db_repository目录,这是SQLAlchemy-migrate存储数据库文件的地方,注意如果数据库已存在它就不会再重新生成了。这将帮助我们在丢失了现有的数据库后,再次自动创建出来。.

第一次迁移

既然我们已经定义好了model,也把它和数据库做了关联,接下来我们来初次尝试下做一个改变应用数据库结构的一次迁移,这将帮助我们从一个空的数据库变成一个可以存储users信息的数据库。

做一个迁移我使用另一个Python小助手脚本 (filedb_migrate.py):
 

#!flask/bin/python
import imp
from migrate.versioning import api
from app import db
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
migration = SQLALCHEMY_MIGRATE_REPO + '/versions/%03d_migration.py' % (api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + 1)
tmp_module = imp.new_module('old_model')
old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
exec old_model in tmp_module.__dict__
script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata)
open(migration, "wt").write(script)
a = api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print 'New migration saved as ' + migration
print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

这个脚本看起来很复杂,其实做的东西真不多。SQLAlchemy-migrate通过对比数据库的结构(从app.db文件读取)和models结构(从app/models.py文件读取)的方式来创建迁移任务,两者之间的差异将作为一个迁移脚本记录在迁移库中,迁移脚本知道如何应用或者撤销一次迁移,所以它可以方便的升级或者降级一个数据库的格式。

虽然我使用上面的脚本自动生成迁移时没遇到什么问题,但有时候真的很难决定数据库旧格式和新格式究竟有啥改变。为了让SQLAlchemy-migrate更容易确定数据库的改变,我从来不给现有字段重命名,限制了添加删除models、字段,或者对现有字段的类型修改。我总是检查下生成的迁移脚本是否正确。

不用多讲,在你试图迁移数据库前必须做好备份,以防出现问题。不要在生产用的数据库上运行第一次使用的脚本,先在开发用的数据库上运行下。

继续前进,记录下我们的迁移:

./db_migrate.py

脚本将打印出以下信息:

New migration saved as db_repository/versions/001_migration.py Current database version: 1

这个脚本信息显示了迁移脚本的存放位置,还有当前数据库的版本号。空数据库的版本号是0,当我们导入users信息后版本号变为1.

数据库的升级和回滚

现在你可能想知道为什么我们要做额外的工作来做数据库的迁移记录。

试想一下,你有个应用在开发机器上,同时服务器上也有一个复制的应用正在运行。

比方说,在你产品的下个版本你的models层作了修改,比如增加了一个新表。没有迁移文件的话,你需要同时解决在开发机和服务器上数据库格式修改的问题,这将是个很大的工作量。

如果你已经有了一个支持迁移的数据库,那么当你向生产服务器发布新的应用版本时,你只需要记录下新的迁移记录,把迁移脚本拷贝到你的生产服务器上,然后运行一个简单的应用改变脚本就行。数据库的升级可以使用下面的Python脚本(filedb_upgrade.py):
 

#!flask/bin/python
from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

当你运行上面的脚本时,数据库将升级到最新版本,并通过脚本将改变信息存储到数据库中。

把数据库回滚到旧的格式,这是不常见的一个方式,但以防万一,SQLAlchemy-migrate也很好的支持(filedb_downgrade.py):
 

#!flask/bin/python
from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v - 1)
print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

这个脚本将回滚数据库的一个版本,你可以通过运行多次的方式向前回滚多个版本。

数据库关联

关系型数据库最擅长存储数据之间的关系。假如用户会写一篇微博,用户的信息被存储在users表中,微博存储在post表中。记录谁写的微博最有效的方式是建立两条数据之间的关联.

一旦用户和微博的关系表建立之后,我们有两种查询方式可以使用。.最琐碎的一个就是当你看到一篇微博,你想知道是哪个用户写的。更复杂的一个是反向的查询,如果你知道一个用户,你想了解下他写的全部微博。Flask-SQLAlchemy将给我们提供对两种方式查询的帮助。

让我们对数据做一下扩展来存储微博信息,这样我们就能看到对应的关系了。我们回到我们使用的数据库设计工具来创建个posts表: 

Python的Flask框架与数据库连接的教程

 posts表包含一个必须的id,微博的内容body,还有一个时间戳。没有什么新东西,但是user_id字段值得解释下。

我们想建立用户和他们写的微博之间的关联,这种方法就是通过添加一个包含用户id的字段来标识谁写的微博,这个id叫做外键。我们的数据库设计工具也显示了外键作为一个外键和id字段指向表的连接。这种关联叫做一对多关联,也就是一个用户可以写多篇文章。

 

让我们修改下models来响应这些变化 (app/models.py):
 

from app import db
 
ROLE_USER = 0
ROLE_ADMIN = 1
 
class User(db.Model):
  id = db.Column(db.Integer, primary_key = True)
  nickname = db.Column(db.String(64), unique = True)
  email = db.Column(db.String(120), unique = True)
  role = db.Column(db.SmallInteger, default = ROLE_USER)
  posts = db.relationship('Post', backref = 'author', lazy = 'dynamic')
 
  def __repr__(self):
    return '<User %r>' % (self.nickname)
 
class Post(db.Model):
  id = db.Column(db.Integer, primary_key = True)
  body = db.Column(db.String(140))
  timestamp = db.Column(db.DateTime)
  user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
 
  def __repr__(self):
    return '<Post %r>' % (self.body)

我们增加了一个表示用户写的微博的Post类,user_id字段在Post类中被初始化指定为一个外键,因此Flask-SQLAlchemy会知道这个字段将会和用户做关联。

注意我们还在User类中添加了一个新字段命名为posts,它被定义成一个db.relationship字段,这个字段并非是数据库中实际存在的字段,所以它不在我们的数据库图表中。对于一对多的关联db.relationship字段通常只需要在一边定义。根据这个关联我们可以获取到用户的微博列表。db.relationship的第一个参数表示“many”一方的类名。backref参数定义了一个字段将"many"类的对象指回到"one"对象,就我们而言,我们可以使用psot.author获取到User实例创建一个微博。如果理解不了不要担心,在文章的后面我们将通过一个例子来解释。

让我们用另外一个迁移文件记录下这次的改变。简单运行下面脚本:

./db_migrate.py

运行脚本后将得到如下输出:

New migration saved as db_repository/versions/002_migration.py Current database version: 2

我们没必要每次都用一个独立的迁移文件来记录数据库model层的小变化,一个迁移文件通常只是记录一个发布版本的改变。接下来更重要的事情是我们需要了解下迁移系统的工作原理。

应用实践

我们已经花了大量的时间在数据库定义上,但是我们仍然没有看到他是如何工作的,因为我们的应用程序里没有任何的数据相关的编码,接下来我们将在Python解释器里使用我们的崭新数据库吧。

继续前进,启动Python。 在 Linux 或者 OS X:

flask/bin/python

Windows下:

flask/Scripts/python

当你在Python命令行提示符中输入下面信息:

>>> from app import db, models >>>

这样我们的数据库模块和models就被加载到了内存里.

让我们来创建个新用户:
 

>>> u = models.User(nickname='john', email='john@email.com', role=models.ROLE_USER)
>>> db.session.add(u)
>>> db.session.commit()
>>>

在同一个会话环境下更改数据库,多次的修改可以积累到一个会话中最后通过调用一个db.session.commit()命令提交,提交同时也保证了原子性。如果在会话中出现了错误,会调用db.session.rollback()把数据库回滚到会话之前的状态。如果调用的既不是提交也不是回滚,那么系统会默认回滚这个会话。Sessions(会话)保证了数据库的数据一致性。
 

让我们来添加另外一个用户:
 

>>> u = models.User(nickname='susan', email='susan@email.com', role=models.ROLE_USER)
>>> db.session.add(u)
>>> db.session.commit()
>>>

现在我们可以查询出用户信息:
 

>>> users = models.User.query.all()
>>> print users
[<User u'john'>, <User u'susan'>]
>>> for u in users:
...   print u.id,u.nickname
...
1 john
2 susan
>>>

此处我们使用了query查询函数,在所有的model类中都可以使用这个函数。注意id是如何自动生成的。

还有另外一种方式来查询,如果我们知道了用户的id,我们可以使用下面的方式查找用户信息:
 

>>> u = models.User.query.get(1)
>>> print u
<User u'john'>
>>>

现在让我们添加一条微博信息:
 

>>> import datetime
>>> u = models.User.query.get(1)
>>> p = models.Post(body='my first post!', timestamp=datetime.datetime.utcnow(), author=u)
>>> db.session.add(p)
>>> db.session.commit()

这个地方我们把时间设置为UTC时区,所有的存储在数据库里的时间将是UTC格式,用户可能在世界各地写微博,因此我们需要使用统一的时间单位。在以后的教程中我们将学习如何在用户本地时区使用这些时间。

你也许注意到我们没有在Post类中设置user_id字段,取而代之的是把用户对象存储到了author字段。auhtor字段是个通过Flask-SQLAlchemy添加的虚拟字段用来建立关联关系的,我们之前已经定义好了这个名字,参照:model中的db.relationship中backref参数。通过这些信息,ORM层就能知道如何取到user_id。

要完成这个会话,让我们来看看更多可做的数据库查询:
 

# get all posts from a user
>>> u = models.User.query.get(1)
>>> print u
<User u'john'>
>>> posts = u.posts.all()
>>> print posts
[<Post u'my first post!'>]
 
# obtain author of each post
>>> for p in posts:
...   print p.id,p.author.nickname,p.body
...
1 john my first post!
 
# a user that has no posts
>>> u = models.User.query.get(2)
>>> print u
<User u'susan'>
>>> print u.posts.all()
[]
 
# get all users in reverse alphabetical order
>>> print models.User.query.order_by('nickname desc').all()
[<User u'susan'>, <User u'john'>]
>>>

要了解更多的数据库查询选项,最好的方式就是去看 Flask-SQLAlchemy 的文档。

在结束会话之前,我们把之前创建的测试用户和文章删除掉,就可以在接下来的章节,从一个干净的数据库开始:
 

>>> users = models.User.query.all()
>>> for u in users:
...   db.session.delete(u)
...
>>> posts = models.Post.query.all()
>>> for p in posts:
...   db.session.delete(p)
...
>>> db.session.commit()
>>>

结束语

这一长篇新手入门,我们了解到了数据库的基本操作,但我们还没有将数据库关联到程序中。在下一个章节中我们通过用户登录系统来练习所学的数据库操作。

在此同时,如果你还没开始写程序,你需要下载当前文件 microblog-0.4.zip.注意,zip文件中没有包括数据库,但是已经有存储脚本。用db_create.py创建一个新的数据库,用db_upgrade.py把你的数据库升级到最新版本。

Python 相关文章推荐
python支持断点续传的多线程下载示例
Jan 16 Python
Python三元运算实现方法
Jan 12 Python
Python表示矩阵的方法分析
May 26 Python
Python 结巴分词实现关键词抽取分析
Oct 21 Python
python中urlparse模块介绍与使用示例
Nov 19 Python
Python元组拆包和具名元组解析实例详解
Mar 26 Python
用Python3创建httpServer的简单方法
Jun 04 Python
python将txt文件读入为np.array的方法
Oct 30 Python
简单了解python gevent 协程使用及作用
Jul 22 Python
Python企业编码生成系统总体系统设计概述
Jul 26 Python
Python实现Restful API的例子
Aug 31 Python
Django之路由层的实现
Sep 09 Python
Python的Flask框架中web表单的教程
Apr 20 #Python
在Python的Flask框架中使用模版的入门教程
Apr 20 #Python
使用Node.js和Socket.IO扩展Django的实时处理功能
Apr 20 #Python
利用Python的Django框架中的ORM建立查询API
Apr 20 #Python
对于Python的框架中一些会话程序的管理
Apr 20 #Python
介绍Python的Django框架中的QuerySets
Apr 20 #Python
使用Python的Django框架实现事务交易管理的教程
Apr 20 #Python
You might like
开启CURL扩展,让服务器支持PHP curl函数(远程采集)
2011/03/19 PHP
php数组函数序列之asort() - 对数组的元素值进行升序排序,保持索引关系
2011/11/02 PHP
str_replace只替换一次字符串的方法
2013/04/09 PHP
php去掉URL网址中带有PHPSESSID的配置方法
2014/07/08 PHP
PHP反射机制原理与用法详解
2017/02/15 PHP
使用laravel和ajax实现整个页面无刷新的操作方法
2019/10/03 PHP
调试php程序的简单步骤
2019/10/04 PHP
laravel批量生成假数据的方法
2019/10/09 PHP
Javascript - HTML的request类
2007/01/09 Javascript
Extjs中DisplayField的日期或者数字格式化扩展
2010/09/03 Javascript
jQuery中prop()方法用法实例
2015/01/05 Javascript
js中键盘事件实例简析
2015/01/10 Javascript
BootStrap下jQuery自动完成的样式调整
2016/05/30 Javascript
jQuery实现遮罩层登录对话框
2016/12/29 Javascript
详解vue项目中使用token的身份验证的简单实践
2019/03/08 Javascript
JS实现的tab切换并显示相应内容模块功能示例
2019/08/03 Javascript
Vue设置长时间未操作登录自动到期返回登录页
2020/01/22 Javascript
记录一次websocket封装的过程
2020/11/23 Javascript
绘制微信小程序验证码功能的实例代码
2021/01/05 Javascript
Python ORM框架SQLAlchemy学习笔记之数据查询实例
2014/06/10 Python
Python最火、R极具潜力 2017机器学习调查报告
2017/12/11 Python
简单了解python模块概念
2018/01/11 Python
Python使用progressbar模块实现的显示进度条功能
2018/05/31 Python
python进行TCP端口扫描的实现
2018/12/21 Python
python-django中的APPEND_SLASH实现方法
2019/06/21 Python
对Python函数设计规范详解
2019/07/19 Python
Keras使用tensorboard显示训练过程的实例
2020/02/15 Python
python2和python3哪个使用率高
2020/06/23 Python
Python 实现二叉查找树的示例代码
2020/12/21 Python
Python爬虫定时计划任务的几种常见方法(推荐)
2021/01/15 Python
德国二手设计师时装和复古时装跳蚤市场:Mädchenflohmarkt
2020/11/09 全球购物
刑事代理授权委托书
2014/09/17 职场文书
村主任群众路线教育实践活动个人对照检查材料思想汇报
2014/10/01 职场文书
私人房屋买卖协议书
2014/10/04 职场文书
2014年纠风工作总结
2014/12/08 职场文书
vue实现拖拽交换位置
2022/04/07 Vue.js