叶榭做网站,快影,网站规划内容,wordpress如何做优化在Flask使用数据库
我们将使用Flask-SQLAlchemy 的扩展来管理数据库。由SQLAlchemy项目提供的#xff0c;已封装了关系对象映射#xff08;ORM#xff09;的一个插件。
ORMs允许数据库程序用对象的方式替代表和SQL语句。面向对象的操作被ORM转化为数据库命令。这样就意味着已封装了关系对象映射ORM的一个插件。
ORMs允许数据库程序用对象的方式替代表和SQL语句。面向对象的操作被ORM转化为数据库命令。这样就意味着不用sql语句让Flask-SQLAlchemy为我们执行sql语句。 迁移
大多数数据库教程都覆盖了创建和使用一个数据库的方法但是没有充分解决当应用程序扩展时数据库更新的问题。通常你会删除旧的数据库然后再创建一个新的数据库来达到更新的效果这样就丢失了所有的数据。如果这些数据创建起来很费劲那么我们不得不写导入导出的脚本了。
幸运的是我们有了更好的方案.
我们现在可以使用SQLAlchemy-migrate做数据库迁移的更新了虽然它增加了数据库启动时的负担但这点小小的代价还是值得的毕竟我们不用担心手动迁移数据库的问题了。
理论学习完毕我们开始吧
配置
我们的小程序使用sqlite数据库。sqlite是小程序数据库的最佳选择一个可以以单文件存储的数据库。
在我们的配置文件中添加新的配置项 (fileconfig.py):
?12345import 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)
?12345678from 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表的一个图标: id字段通常作为主键的形式用在所有的models里面每个在数据库中的user都有一个指定的唯一id值。幸运的是这些都是自动的我们只需要提供一个id字段。
nickname和email字段被定义为string类型他们的长度也已经被指定这样可以节省数据库存储空间。
role字段被定义为integer类型我们用来标识users是admins还是其他类型。
现在我们已经明确了users表的结构接下来转换为编码的工作将相当简单了(fileapp/models.py)
?12345678910111213from app import db ROLE_USER 0ROLE_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):
?123456789101112#!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))注意这个脚本是完全通用的所有的应用路径名都是从配置文件读取的。当你用在自己的项目时你可以把脚本拷贝到你apps目录下就能正常使用了。
创建数据库你只需要运行下面的一条命令注意windows下稍微有些不同:
?1./db_create.py运行这条命令之后你就创建了一个新的app.db文件。这是个支持迁移的空sqlite数据库同时也会生成一个带有几个文件的db_repository目录这是SQLAlchemy-migrate存储数据库文件的地方注意如果数据库已存在它就不会再重新生成了。这将帮助我们在丢失了现有的数据库后再次自动创建出来。.
第一次迁移
既然我们已经定义好了model也把它和数据库做了关联接下来我们来初次尝试下做一个改变应用数据库结构的一次迁移这将帮助我们从一个空的数据库变成一个可以存储users信息的数据库。
做一个迁移我使用另一个Python小助手脚本 (filedb_migrate.py):
?123456789101112131415#!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、字段或者对现有字段的类型修改。我总是检查下生成的迁移脚本是否正确。
不用多讲在你试图迁移数据库前必须做好备份以防出现问题。不要在生产用的数据库上运行第一次使用的脚本先在开发用的数据库上运行下。
继续前进记录下我们的迁移:
?1./db_migrate.py脚本将打印出以下信息:
?1New migration saved as db_repository/versions/001_migration.py Current database version: 1这个脚本信息显示了迁移脚本的存放位置还有当前数据库的版本号。空数据库的版本号是0当我们导入users信息后版本号变为1.
数据库的升级和回滚
现在你可能想知道为什么我们要做额外的工作来做数据库的迁移记录。
试想一下你有个应用在开发机器上同时服务器上也有一个复制的应用正在运行。
比方说在你产品的下个版本你的models层作了修改比如增加了一个新表。没有迁移文件的话你需要同时解决在开发机和服务器上数据库格式修改的问题这将是个很大的工作量。
如果你已经有了一个支持迁移的数据库那么当你向生产服务器发布新的应用版本时你只需要记录下新的迁移记录把迁移脚本拷贝到你的生产服务器上然后运行一个简单的应用改变脚本就行。数据库的升级可以使用下面的Python脚本filedb_upgrade.py
?123456#!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):
?1234567#!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表 posts表包含一个必须的id,微博的内容body还有一个时间戳。没有什么新东西但是user_id字段值得解释下。
我们想建立用户和他们写的微博之间的关联这种方法就是通过添加一个包含用户id的字段来标识谁写的微博这个id叫做外键。我们的数据库设计工具也显示了外键作为一个外键和id字段指向表的连接。这种关联叫做一对多关联也就是一个用户可以写多篇文章。让我们修改下models来响应这些变化 (app/models.py):
?1234567891011121314151617181920212223from app import db ROLE_USER 0ROLE_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实例创建一个微博。如果理解不了不要担心在文章的后面我们将通过一个例子来解释。
让我们用另外一个迁移文件记录下这次的改变。简单运行下面脚本
?1./db_migrate.py运行脚本后将得到如下输出
?1New migration saved as db_repository/versions/002_migration.py Current database version: 2我们没必要每次都用一个独立的迁移文件来记录数据库model层的小变化一个迁移文件通常只是记录一个发布版本的改变。接下来更重要的事情是我们需要了解下迁移系统的工作原理。
应用实践
我们已经花了大量的时间在数据库定义上但是我们仍然没有看到他是如何工作的因为我们的应用程序里没有任何的数据相关的编码接下来我们将在Python解释器里使用我们的崭新数据库吧。
继续前进启动Python。 在 Linux 或者 OS X:复制代码 代码如下:flask/bin/pythonWindows下:复制代码 代码如下:flask/Scripts/python当你在Python命令行提示符中输入下面信息
1 from app import db, models 这样我们的数据库模块和models就被加载到了内存里.
让我们来创建个新用户
1234 u models.User(nicknamejohn, emailjohnemail.com, rolemodels.ROLE_USER) db.session.add(u) db.session.commit() 在同一个会话环境下更改数据库多次的修改可以积累到一个会话中最后通过调用一个db.session.commit()命令提交提交同时也保证了原子性。如果在会话中出现了错误会调用db.session.rollback()把数据库回滚到会话之前的状态。如果调用的既不是提交也不是回滚那么系统会默认回滚这个会话。Sessions会话保证了数据库的数据一致性。
让我们来添加另外一个用户
1234 u models.User(nicknamesusan, emailsusanemail.com, rolemodels.ROLE_USER) db.session.add(u) db.session.commit() 现在我们可以查询出用户信息
?123456789 users models.User.query.all() print users [User ujohn, User ususan] for u in users: ... print u.id,u.nickname ... 1 john 2 susan 此处我们使用了query查询函数在所有的model类中都可以使用这个函数。注意id是如何自动生成的。
还有另外一种方式来查询如果我们知道了用户的id我们可以使用下面的方式查找用户信息
1234 u models.User.query.get(1) print u User ujohn 现在让我们添加一条微博信息
12345 import datetime u models.User.query.get(1) p models.Post(bodymy first post!, timestampdatetime.datetime.utcnow(), authoru) db.session.add(p) db.session.commit()这个地方我们把时间设置为UTC时区所有的存储在数据库里的时间将是UTC格式用户可能在世界各地写微博因此我们需要使用统一的时间单位。在以后的教程中我们将学习如何在用户本地时区使用这些时间。
你也许注意到我们没有在Post类中设置user_id字段取而代之的是把用户对象存储到了author字段。auhtor字段是个通过Flask-SQLAlchemy添加的虚拟字段用来建立关联关系的我们之前已经定义好了这个名字参照model中的db.relationship中backref参数。通过这些信息ORM层就能知道如何取到user_id。
要完成这个会话让我们来看看更多可做的数据库查询
12345678910111213141516171819202122232425# get all posts from a user u models.User.query.get(1) print u User ujohn posts u.posts.all() print posts [Post umy 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 ususan print u.posts.all() [] # get all users in reverse alphabetical order print models.User.query.order_by(nickname desc).all() [User ususan, User ujohn] 要了解更多的数据库查询选项最好的方式就是去看 Flask-SQLAlchemy 的文档。
在结束会话之前我们把之前创建的测试用户和文章删除掉。
12345678910 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() 01 Get提交
1前端
script src{{url_for(static,filenamejs/jquery.js)}}/script
!--Get请求--
scriptvar data{name:kikay,age:18}$.ajax({type:GET,url:{{url_for(test.test_get)}},data:data,dataType:json,//希望服务器返回json格式的数据success:function(data){alert(JSON.stringify(data));alert(data[test])}});
/script
123456789101112131415161718
2后端
test.route(/test_get/,methods[POST,GET])
def test_get():#获取Get数据namerequest.args.get(name)ageint(request.args.get(age))#返回if namekikay and age18:return jsonify({result:ok})else:return jsonify({result:error})
1234567891002 Post方式
1前端
script src{{url_for(static,filenamejs/jquery.js)}}/script
!--Post请求--
scriptvar data{name:kikay,age:18}$.ajax({type:POST,url:{{url_for(test.test_post)}},data:data,dataType:json,//希望服务器返回json格式的数据success:function(data){alert(JSON.stringify(data));}});
/script
1234567891011121314151617
2后端
test.route(/test_post/,methods[POST,GET])
def test_post():#获取POST数据namerequest.form.get(name)ageint(request.form.get(age))#返回if namekikay and age18:return jsonify({result:ok})else:return jsonify({result:error})
1234567891003 Json提交
1前端
如果前端要向后端提交Json格式的数据需要设置content-type参数为application/json并且将data参数设置为字符串形式。
script src{{url_for(static,filenamejs/jquery.js)}}/script
!--Json--
scriptvar data{name:kikay,age:18}$.ajax({type:POST,url:{{url_for(test.test_json)}},data:JSON.stringify(data), //转变传递的参数为字符串格式contentType:application/json; charsetUTF-8, //指定传递给服务器的是Json格式数据dataType:json,//希望服务器返回json格式的数据success:function(data){alert(JSON.stringify(data));}});
/script
123456789101112131415161718
2后端
test.route(/test_json/,methods[POST,GET])
def test_json():#获取JSON数据datarequest.get_json()namedata.get(name)ageint(data.get(age))#返回if namekikay and age18:return jsonify({result:ok})else:return jsonify({result:error})
1234567891011