Flask Web开发-1.4Web程序与数据库处理

前言

数据库按照一定规则保存程序数据,程序再发起查询取回所需的数据。Web 程序最常用基 于关系模型的数据库,这种数据库也称为 SQL 数据库,因为它们使用结构化查询语言。而这些年文档数据库和键值对数据库成了流行的替代选择,这两种数据库合称 NoSQL 数据库
所以先让我们了解比较下这两种类型数据库,再考虑自己的需求,从而做出自己的选择。

一,SQL数据库

关系型数据库把数据存储在表中,表模拟程序中不同的实体。例如,订单管理程序的数据 库中可能有表 customers、products 和 orders。
表的列数是固定的,行数是可变的。因为列定义表所表示的实体的数据属性。例如,customers 表中可能有 name、address、phone 等列。表中的行定义各列对应的真实数据。
表中有个特殊的列,称为主键,其值为表中各行的唯一标识符。表中还可以有称为外键的 列,引用同一个表或不同表中某行的主键。行之间的这种联系称为关系,这是关系型数据 库模型的基础。
下图展示了一个简单数据库的关系图。这个数据库中有两个表,分别存储用户和用户角色。连接两个表的线代表两个表之间的关系。
在这里插入图片描述

在这个数据库关系图中,roles 表存储所有可用的用户角色,每个角色都使用一个唯一的 id 值(即表的主键值)进行标识。users 表包含用户列表,每个用户也有唯一的 id 值。除了 id 主键之外,roles 表中还有 name 列,users 表中还有 username 列和 password 列。users 表中的 role_id 列是外键,引用角色的 id,通过这种方式为每个用户指定角色。
从这个例子可以看出,关系型数据库存储数据很高效,而且避免了重复。将这个数据库中 的用户角色重命名也很简单,因为角色名只出现在一个地方。一旦在 roles 表中修改完角 色名,所有通过 role_id 引用这个角色的用户都能立即看到更新。
但从另一方面来看,把数据分别存放在多个表中还是很复杂的。生成一个包含角色的用户 列表会遇到一个小问题,因为在此之前要分别从两个表中读取用户和用户角色,再将其联结起来。关系型数据库引擎为联结操作提供了必要的支持。

二,NoSQL数据库

所有不遵循上节所述的关系模型的数据库统称为 NoSQL 数据库。NoSQL 数据库一般使用 集合代替表,使用文档代替记录。NoSQL 数据库采用的设计方式使联结变得困难,所以大 多数数据库根本不支持这种操作。对于结构上图所示的 NoSQL 数据库,若要列出各 用户及其角色,就需要在程序中执行联结操作,即先读取每个用户的 role_id,再在 roles 表中搜索对应的记录。
NoSQL 数据库更适合设计成下图所示的结构。这是执行反规范化操作得到的结果,它减少了表的数量,却增加了数据重复量。
在这里插入图片描述

这种结构的数据库要把角色名存储在每个用户中。如此一来,将角色重命名的操作就变得 很耗时,可能需要更新大量文档。
使用 NoSQL 数据库好处就是:数据重复可以提升查询速度。列出用户及其角色的操作很简单,因为(集成好)无需联结。

三,使用SQL还是NoSQL

SQL 数据库擅于用高效且紧凑的形式存储结构化数据。这种数据库需要花费大量精力保证 数据的一致性。NoSQL 数据库放宽了对这种一致性的要求,从而获得性能上的优势。
对中小型程序来说,SQL 和 NoSQL 数据库都是很好的选择,而且性能相当。
详细地数据库介绍和使用需要找专门的资料来学习!

四,Python数据库框架

大多数的数据库引擎都有对应的 Python 包,包括开源包和商业包。Flask 并不限制使用何种类型的数据库包,因此可以根据自己的喜好选择使用 MySQL(开源且流行)、Postgres、SQLite、 Redis、MongoDB 或者 CouchDB。
如果这些都无法满足需求,并且想控制管理不同种类的数据库,还有一些数据库抽象层代码包供选择,例如 SQLAlchemy 和 MongoEngine。使用这些抽象包直接处理高等级的 Python 对象,而不用处理如表、 文档或查询语言此类的数据库实体。
选择数据库框架时,要考虑很多因素:
易用性
如果直接比较数据库引擎和数据库抽象层,显然后者取胜。抽象层,也称为对象关系映射(Object-Relational Mapper,ORM)或对象文档映射(Object-Document Mapper,ODM),在用户不知觉的情况下把高层的面向对象操作转换成低层的数据库指令。
性能
ORM 和 ODM 把对象业务转换成数据库业务会有一定的损耗。大多数情况下,这种性能的降低微不足道,但也不一定都是如此。一般情况下,ORM 和 ODM 对生产率的提升远远超过了这一丁点儿的性能降低,所以性能降低这个理由不足以说服用户完全放弃ORM 和 ODM。真正的关键点在于如何选择一个能直接操作低层数据库的抽象层,以防特定的操作需要直接使用数据库原生指令优化。
可移植性
选择数据库时,必须考虑其是否能在你的开发平台和生产平台中使用。例如,如果你打算利用云平台托管程序,就要知道这个云服务提供了哪些数据库可供选择。可移植性还针对 ORM 和 ODM。尽管有些框架只为一种数据库引擎提供抽象层,但其他框架可能做了更高层的抽象,它们支持不同的数据库引擎,而且都使用相同的面向对象接口。SQLAlchemy ORM 就是一个很好的例子,它支持很多关系型数据库引擎,包 括流行的 MySQL、Postgres 和 SQLite。
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 的低层功能。
在pycharm中(虚拟环境)搜索并安装Flask-SQLAlchemy ,如下图所示:
在这里插入图片描述
在 Flask-SQLAlchemy 中,数据库使用 URL 指定(即用类似url格式指明数据库相关信息)。最流行的数据库引擎采用的数据库 URL 格式如下表所示:
在这里插入图片描述
在这些 URL 中,hostname 表示 MySQL 服务所在的主机,可以是本地主机(localhost), 也可以是远程服务器。数据库服务器上可以托管多个数据库,因此 database 表示要使用的数据库名。如果数据库需要进行认证,username 和 password 表示数据库用户密令。
提示:SQLite 数据库不需要使用服务器,因此不用指定 hostname、username 和 password。URL 中的 database 是硬盘上文件的文件名
程序使用的数据库 URL 必须保存到 Flask 配置对象的 SQLALCHEMY_DATABASE_URI (注意是URI而不是URL)键中。配 置对象中还有一个很有用的选项,即 SQLALCHEMY_COMMIT_ON_TEARDOWN 键,将其设为 True 时,每次请求结束后都会自动提交数据库中的变动(类似于.commit()提交功能)。其他配置选项的作用请参阅 Flask SQLAlchemy 的文档。
在app.py中,下面展示了如何初始化及配置一个简单的MySQL数据库:

from flask_sqlalchemy import SQLAlchemy
app.config['SECRET_KEY'] = 'hard to guess string'
app.config['SQLALCHEMY_DATABASE_URI'] ='mysql://root:678@localhost/me'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)

由于私密信息,对其中的密码改动,仅做实例展示。下面划分下各部分功能:
在这里插入图片描述

db 对象是 SQLAlchemy 类的实例,表示程序使用的数据库,同时还获得了 Flask-SQLAlchemy 提供的所有功能。
值得注意的是如果开发者使用的是python3以上版本的话,数据库连接配置会有一些问题(ModuleNotFoundError: No module named ‘MySQLdb’),因为MySQLdb只支持Python2,还不支持3。
首先,先安装好pymysql库:
在这里插入图片描述

这样我们要在app.py中增加改动:

import pymysql
pymysql.install_as_MySQLdb()

六,定义模型

模型这个术语表示程序使用的持久化实体。在 ORM 中,模型一般是一个 Python 类,类中 的属性对应数据库表中的列。
Flask-SQLAlchemy 创建的数据库实例为模型提供了一个基类以及一系列辅助类和辅助函 数,可用于定义模型的结构。在第一节SQL数据库 中的 roles 表和 users 表可定义为模型 Role 和 User, 在app.py中,如下面所示:

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.name
class 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 类型:
在这里插入图片描述

db.Column 中其余的参数指定属性的配置选项。下表列出了一些可用选项:
在这里插入图片描述

注意:Flask-SQLAlchemy 要求每个模型都要定义主键,这一列经常命名为 id
虽然没有强制要求,但这两个模型都定义了 repr() 方法,返回一个具有可读性的字符 串表示模型,可在调试和测试时使用

七,关系

关系型数据库使用关系把不同表中的行联系起来。第一节的关系图表示用户和角色之 间的一种简单关系。这是角色到用户的一对多关系,因为一个角色可属于多个用户,而每 个用户都只能有一个角色。
其关系在模型类中的表示方法如下例所示:

class Role(db.Model):
	#已有部分省略
    users = db.relationship('User', backref='role')

class User(db.Model):
	#已有部分省略
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

如第一节的图关系,关系使用 users 表中的外键连接了两行。添加到 User 模型中的 role_id 列 被定义为外键,就是这个外键建立起了关系。传给 db.ForeignKey() 的参数 ‘roles.id’ 表 明,这列的值是 roles 表中行的 id 值。
添加到 Role 模型中的 users 属性代表这个关系的面向对象视角。对于一个 Role 类的实例, 其 users 属性将返回与角色相关联的用户组成的列表。db.relationship() 的第一个参数表 明这个关系的另一端是哪个模型。如果模型类尚未定义,可使用字符串形式指定。
db.relationship() 中的 backref 参数向 User 模型中添加一个 role 属性(User模型可以使用),从而定义反向关 系。这一属性可替代 role_id 访问 Role 模型,此时获取的是模型对象,而不是外键的值。
大多数情况下,db.relationship() 都能自行找到关系中的外键,但有时却无法决定把 哪一列作为外键。例如,如果 User 模型中有两个或以上的列定义为 Role 模型的外键, SQLAlchemy 就不知道该使用哪列。如果无法决定外键,你就要为 db.relationship() 提供 额外参数,从而确定所用外键。下表列出了定义关系时常用的配置选项:
在这里插入图片描述
在这里插入图片描述
拓展:除了一对多之外,还有几种其他的关系类型。一对一关系可以用前面介绍的一对多关系 表示,但调用 db.relationship() 时要把 uselist 设为 False,把“多”变成“一”。多对 一关系也可使用一对多表示,对调两个表即可,或者把外键和 db.relationship() 都放在 “多”这一侧。最复杂的关系类型是多对多,需要用到第三张表,这个表称为关系表。

八,数据库操作

现在模型已经按照第一节中的数据库关系图完成配置,可以随时使用了。学习如何使用 模型的最好方法是在 Python shell 中实际操作。在pycharm中我们打开的是python 终端窗口, 进行操作(相当于Python shell )。

1.创建表

首先,我们要在本地数据库(mysql)中创建数据库(本次创建的是名称为‘me’的数据库),其实,在连接配置数据库前我们已经先创建了(从语句app.config[‘SQLALCHEMY_DATABASE_URI’] ='mysql://root:678@localhost/me’也可看出),然后我们要让 Flask-SQLAlchemy 根据模型类创建数据库。方法是使用 db.create_all() 函数:

>>> from app import db
>>> db.create_all()

如果数据库表已经存在于数据库中,那么 db.create_all() 不会重新创建或者更新这个表。如果修改模型后要把改动应用到现有的数据库中,这一特 性会带来不便。更新现有数据库表的粗暴方式是先删除旧表再重新创建:

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

这个方法有个弊端:它把数据库中原有的数据都销毁了。后面会介绍使用数据库迁移用于更新数据库。

2.插入行

下面代码创建了一些角色和用户:

>>> from app 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 属性也 可使用,虽然它不是真正的数据库列,但却是一对多关系的高级表示。这些新建对象的 id 属性并没有明确设定,因为主键是由 Flask-SQLAlchemy 管理的。现在这些对象只存在于 Python 中,还未写入数据库(此处是在shell环境下的步骤演示,与完整程序操作有着许多不同之处)。因此 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() 后,添加到数据库会话 中的所有对象都会还原到它们在数据库时的状态。

3.修改行

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

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

4.删除行

数据库会话还有个 delete() 方法。下面这个例子把 “Moderator” 角色从数据库中删除:

>>> db.session.delete(mod_role)
>>> db.session.commit()

注意,删除与插入和更新一样,提交数据库会话后才会执行

5.查询行

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

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

使用过滤器可以配置 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)
在这里插入图片描述
在查询上应用指定的过滤器后,通过调用 all() 执行查询,以列表的形式返回结果。除了all() 之外,还有其他方法能触发查询执行。下表列出了执行查询的其他方法:
在这里插入图片描述

关系和查询的处理方式类似。下面这个例子分别从关系的两端查询角色和用户之间的一对 多关系:

>>> users = user_role.users
>>> users
[<User u'susan'>, <User u'david'>]
>>> users[0].role
<Role u'User'>

这个例子中的 user_role.users 查询有个小问题。执行 user_role.users 表达式时,隐含的 查询会调用 all() 返回一个用户列表。query 对象是隐藏的,因此无法指定更精确的查询 过滤器。就这个特定示例而言,返回一个按照字母顺序排序的用户列表可能更好。在app.py 中,我们修改了关系的设置,加入了 lazy = ‘dynamic’ 参数,从而禁止自动执行查询。

class Role(db.Model):
    #已有部分已省略
    users = db.relationship('User', backref='role', lazy='dynamic')

这样配置关系之后,user_role.users 会返回一个尚未执行的查询,因此可以在其上添加过 滤器:

>>> user_role.users.order_by(User.username).all()
[<User u'david'>, <User u'susan'>]
>>> user_role.users.count()
2

九,在视图函数中操作数据库

前面介绍的数据库操作可以直接在视图函数中进行。下例的新版首页在app.py中, 将把用户输入的名字写入了数据库:

@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
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('home.html',form = form, name = session.get('name'),known = session.get('known', False))

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

{% extends "bases.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flask Web云端主页{% endblock %}
{% block page_content %}
<div class="page-header">
    <h1>你好, {% if name %}{{ name }}{% else %}新客人{% endif %}!</h1>
    {% if not known %}
    <h3>第一次遇到你很高兴!</h3>
    {% else %}
    <p>非常高兴再次遇到你!</p>
    {% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}

这个模板使用 known 参数在欢迎消息中加入了第二行, 从而对已知用户和新用户显示不同的内容。

十,集成Python shell

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

from flask_script import Shell,Manager
manager = Manager(app)
def 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=mysql://root:***@localhost/me?charset=utf8>
>>> User
<class 'app.User'>

提示:由于新版本的flask将里面一些代码去掉,我们可能会遇到如下图的问题:
在这里插入图片描述
解决方法:ModuleNotFoundError:no module named ‘flask._compat‘

注意:在pycharm中终端使用的是本地python环境,而我们在这个项目中使用的是创建的虚拟环境,我们要做一些改动(如果你想使用本地python环境的话,就要老实地用pip指令去下载那些第三方库):
第一步:在终端输入“cd venv/Scripts”,进入后输入“activate”,启动虚拟环境;
第二步:激活后,输入‘cd <你的项目地址>’,如:(venv) D:\E盘\python\Flask Web应用\基本应用\venv\Scripts>cd D:\E盘\python\Flask Web应用\基本应用
第三步:在已激活并使用的环境下,可以使用对应的任务语句。
在这里插入图片描述

十一,使用Flask-Migrate实现数据库迁移

在开发程序的过程中,你会发现有时需要修改数据库模型,而且修改之后还需要更新数据库。
仅当数据库表不存在时,Flask-SQLAlchemy 才会根据模型进行创建。因此,更新表的唯一方式就是先删除旧表,不过这样做会丢失数据库中的所有数据。
更新表的更好方法是使用数据库迁移框架。源码版本控制工具可以跟踪源码文件的变化, 类似地,数据库迁移框架能跟踪数据库模式的变化,然后增量式的把变化应用到数据库中。
SQLAlchemy 的主力开发人员编写了一个迁移框架,称为 Alembic(https://alembic.readthedocs.org/en/latest/index.html)。除了直接使用 Alembic 之外,Flask 程序还可使用 Flask-Migrate (http://flask-migrate.readthedocs.org/en/latest/)扩展。这个扩展对 Alembic 做了轻量级包装,并 集成到 Flask-Script 中,所有操作都通过 Flask-Script 命令完成。
在pycharm中安装如下:
在这里插入图片描述
注意:由于使用受限建议安装2.7.0版本,点击右边方法即可查看Flask-Migrate2.7.0安装方法

1.创建迁移仓库

在app.py中,配置如下:

from flask_migrate import Migrate,MigrateCommand
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
#已有部分已省略

为了导出数据库迁移命令,Flask-Migrate 提供了一个 MigrateCommand 类,可附加到 Flask Script 的 manager 对象上。在这个例子中,MigrateCommand 类使用 db 命令附加。
在维护数据库迁移之前,要使用 init 子命令创建迁移仓库:

python app.py db init

在这里插入图片描述

这个命令会创建 migrations 文件夹,所有迁移脚本都存放其中。
注意:数据库迁移仓库中的文件要和程序的其他文件一起纳入版本控制

2.创建迁移脚本

在 Alembic 中,数据库迁移用迁移脚本表示。脚本中有两个函数,分别是 upgrade() 和 downgrade()。upgrade() 函数把迁移中的改动应用到数据库中,downgrade() 函数则将改动 删除。Alembic 具有添加和删除改动的能力,因此数据库可重设到修改历史的任意一点。
我们可以使用 revision 命令手动创建 Alembic 迁移,也可使用 migrate 命令自动创建。 手动创建的迁移只是一个骨架,upgrade() 和 downgrade() 函数都是空的,开发者要使用Alembic 提供的 Operations 对象指令实现具体操作。自动创建的迁移会根据模型定义和数 据库当前状态之间的差异生成 upgrade() 和 downgrade() 函数的内容。
自动创建的迁移不一定总是正确的,有可能会漏掉一些细节。自动生成迁移 脚本后一定要进行检查。
migrate 子命令用来自动创建迁移脚本:

 python app.py db migrate -m "initial migration"

3.更新数据库

检查并修正好迁移脚本之后,我们可以使用 db upgrade 命令把迁移应用到数据库中:

python app.py db upgrade

对第一个迁移来说,其作用和调用 db.create_all() 方法一样。但在后续的迁移中, upgrade 命令能把改动应用到数据库中,且不影响其中保存的数据。

十二,开发中可能遇到的问题解决方法汇总

点击即可查看对应方法链接
pip命令失效
Flask-Migrate数据库迁移
ModuleNotFoundError:no module named ‘flask._compat‘
ImportError: cannot import name ‘MigrateCommand‘ from ‘flask_migrate‘
在pycharm终端启动项目自带的虚拟环境
pycharm的terminal如何进入虚拟环境

十三,相关代码模块

仅展示对于本专栏前面的模块改动的部分,其他默认不变

1.home.html

{% extends "bases.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flask Web云端主页{% endblock %}
{% block page_content %}
<div class="page-header">
    <h1>你好, {% if name %}{{ name }}{% else %}新客人{% endif %}!</h1>
    {% if not known %}
    <h3>第一次遇到你很高兴!</h3>
    {% else %}
    <p>非常高兴再次遇到你!</p>
    {% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}

2.app.py

from flask import Flask, render_template,url_for,redirect,session,flash
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from datetime import datetime
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import Required
from flask_sqlalchemy import SQLAlchemy
from flask_script import Shell,Manager
from flask_migrate import Migrate,MigrateCommand
import pymysql


#程序初始配置
pymysql.install_as_MySQLdb()
app = Flask(__name__)
bootstrap = Bootstrap(app)
moment = Moment(app)
manager = Manager(app)
def make_shell_context():
    return dict(app=app, db=db, User=User, Role=Role)
manager.add_command("shell", Shell(make_context=make_shell_context))


#数据库连接配置
app.config['SECRET_KEY'] = 'hard to guess string'
app.config['SQLALCHEMY_DATABASE_URI'] ='mysql://root:678@localhost/me'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)

class NameForm(FlaskForm):
    name = StringField('你叫什么名字?', validators=[Required()])
    submit = SubmitField('提交')

#数据库模型定义
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.name

class 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



@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
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('home.html',form = form, name = session.get('name'),known = session.get('known', False))

@app.route('/user/<name>')
def users(name):
    return render_template('user.html',name=name,current_time=datetime.utcnow())

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html',current_time=datetime.utcnow()),404

@app.errorhandler(500)
def internal_error(e):
    return render_template('500.html',current_time=datetime.utcnow()),500

@app.route('/form')
def useform():
    form=NameForm()
    return  render_template('FORM.html',form=form)

if __name__ == '__main__':
    manager.run()

最后,文中如有不足,敬请批评指正!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值