原文: http://www.catonlinepy.tech/
声明:原创不易,未经许可,不得转载
1. 你将学会什么
接第3天表单的使用课程,今天的课程主要涉及到与数据库相关的两个插件的使用,一个是Flask_SQLAlchemy,另外一个是Flask_Migrate。通过今天的学习,你将学会如何对数据库进行基本的操作,以及如何完成数据库的迁移。教程中的代码都会托管到github上,猫姐不厌其烦地强调,在学习本课内容时一定要自己尝试手敲代码,遇到问题再到github上查看代码,如果实在不知道如何解决,可以在日志下方留言。
2.使用Flask_SQLAlchemy管理数据库
2.1 Flask_SQLAlchemy的安装
对于web后台开发工作,必须要掌握的一项技能便是对数据库的CRUD(create, read, update, delete)操作,如果开发过程中直接使用原生的sql语句对数据库进行操作,将是非常痛苦的事件(毕竟sql语句有很多反人类的设计)。Flask_SQLAlchemy插件将开发人员从这个泥潭中解救出来了,我们只需要使用Python的类就能轻松的完成对表的增删改查操作,并且该插件还支持多种数据库类型,如MySQL、PostgreSQL、和SQLite等。
在进入正式学习之前,我们照旧要建立今天的项目目录,如下:
# 进入到虚拟环境目录,激活虚拟环境maojie@Thinkpad:~/flask-plan/$ source miao_venv/bin/activate# 到flask-course-primary目录下创建第四天的课程day4目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ mkdir day4# 进入day4目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ cd day4# 新建database_demo目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ mkdir database_demo# 进入到database_demo目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ cd database_demo/# 在database_demo目录中新建__init__.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ touch __init__.py# 在database_demo包中新建routes.py路由文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ touch routes.py# 在day4目录下新建run.py文件(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/$ touch run.py安装Flask_SQLAlchemy插件还是使用pip命令,如下:# 注意一定要在虚拟环境中(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ pip install Flask_SQLAlchemy
2.2 配置Flask_SQLAlchemy
我们的教程中使用的数据库是SQLite(Linux),主要原因是SQLite足够简单,不需要进行任何配置便可使用,十分适用于入门。下面在__init__.py文件中配置数据库,如下所示:
# 在__init__.py文件中的内容from flask import Flask# 从flask_sqlalchemy导入SQLAlchemy类from flask_sqlalchemy import SQLAlchemyimport os# 通过Flask创建一个app实例app = Flask(__name__)basedir = os.path.abspath(os.path.dirname(__file__))# Flask_SQLAlchemy插件从SQLALCHEMY_DATABASE_URI配置的变量中获取应用的数据库位置app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'database.db')# 通过SQLAlchemy创建db实例,表示程序使用的数据库,并且db能够使用Flask_SQLAlchemy的所有功能db = SQLAlchemy(app)
2.3 构建数据库模型
数据模型通常用来定义数据库中的表及表中的字段。下面代码中的User类和Post类就代表了数据库中的两张表(官方叫法是数据模型)。后面我们会看到,通过这里定义的两个Python类,我们就可以非常容易的完成表的增删改查,以下是routes.py文件中的内容。
# 在routes.py文件中的内容from database_demo import db# 定义类User,继承自基类db.Modelclass User(db.Model): # 定义数据库表的名称,为user表 __tablename__ = 'user' # db.Column类构造函数中的第一个参数表中该字段的类型和该字段的其它属性 id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(20), unique=True, nullable=False) # print User对象时,会打印return的字符串,方便调试 def __repr__(self): return f"User('{self.username}')"class Post(db.Model): __tablename__ = 'post' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100), unique=True, nullable=False) def __repr__(self): return f"Post('{self.title}')"
2.4 建立表之间的关系
关系型数据库是通过关系把数据库中不同表进行关联。下面,将介绍表之间的一种关系(一对多的关系),通常一个用户可以发表多篇日志,这也是与实际情况相符的。在routes.py文件中添加两行代码便可以建立这种“一对多”的关系了。猫姐提醒:”一对多“的关系在实际开发中十分常见,但是下面的两行建立关系的代码却不太容易看懂,因此大家不用太关心代码的细节,后面只要大家会照着葫芦画瓢,建立好这种关系就足够了。
# 修改routes.py文件的内容from database_demo import dbclass User(db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(20), unique=True, nullable=False) # 添加反向引用关系 posts = db.relationship('Post', backref='author', lazy=True) def __repr__(self): return f"User('{self.username}')"class Post(db.Model): __tablename__ = 'post' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100), unique=True, nullable=False) # 添加表user的外键user_id user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) def __repr__(self): return f"Post('{self.title}')"添加到Post模型中的user_id被称为外键,两个表的关系主要通过外键建立起表之间的联系。db.ForeignKey的参数'user.id'表示这列的值是user表中的id。添加到User模型的posts属性表示这个关系的面向对象视图,它不是实际数据库的字段,它是用户和其发表日志关系之间的视图。如果有一个User类的实例u,u.posts则返回用户发表过的所有日志。
3. 数据库的基本操作
3.1 数据库中表的创建
首先在run.py文件中输入如下代码:
# run.py文件中的内容from database_demo import appif __name__ == "__main__": app.run(debug=True)
然后在终端中输入flask shell命令,前提条件是必须在终端中配置环境变量,在第一课中猫姐已经讲过如何配置,这里不再重复。输入flask shell后进入python交互环境,如下所示:
在终端中输入命令flask shell(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask shell/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning. 'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and 'Python 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0] on linuxApp: database_demo [production]Instance: /home/meyou/flask-plan/flask-course-primary/day4/instance>>> # 注意:出现三个大于号,表示已经进入python交互环境
开始在数据库中创建user表和post表,如下所示:
>>> from database_demo import db # 从database_demo包中导入db>>> db.create_all() # 用db.create_all()创建数据库表 >>>
这时查看程序目录,发现已经存在database.db库了,如下所示:
# 数据库存在于database_demo包里(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ lsdatabase.db __init__.py __init__.pyc __pycache__ routes.py# database.db文件名是我们在__init__.py中配置的
要想查看dabase.db中是否存在新建的表,可以用下面的方式,如下所示:
# 在database_demo包目录中用sqlite3 加上数据库名字进入到数据库中(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ sqlite3 database.db SQLite version 3.22.0 2018-01-22 18:45:57Enter ".help" for usage hints.# 用.tables查看表sqlite> .tablespost usersqlite> # post,user是我们在建立数据模型时定义的两张表
虽然通过db.create_all()可以创建表,但是如果数据库中已经存在这两张表,再用db.create_all()时是不会重新创建表或是更新表,只有先通过db.drop_all()删除数据库中所有的表,然后再用db.create_all()创建表,这样原有的数据库中表的数据都被销毁了,这是我们不想看到的。所以,在本文末会用一种更有效的方式来管理数据库。
3.2 表的数据插入
继上面python shell环境,在user表中插入两行数据,如下所示:
>>> from database_demo.routes import User,Post # 需要导入模型User,Post所在的位置>>> user1=User(username='miaojie') # 创建用户1>>> user2=User(username='miaoge') # 创建用户2>>> print(user1.id) # 打印用户1的idNone>>> print(user2.id) # 打印用户2的idNone>>> # 因为这些数据还未被真正写到数据库中,所以用户的id并不会显示出来# 将数据对象添加到会话中>>> db.session.add(user1)>>> db.session.add(user2)>>># 只有将会话提交后,数据才会写到database.db文件中>>> db.session.commit()>>> # 再次查看>>> print(user1.id)1>>> print(user2.id)2
3.3 表的行数据修改
下面,继续在python shell会话中进行操作,现在把user1重新命名,如下所示:
>>> user1.username='miaomiao'>>> db.session.commit()>>> print(user1)User('miaomiao')>>>
3.4 表的行数据删除
在数据库中还可以使用delete()进行数据行的删除,如下所示:
>>> db.session.delete(user1)>>> db.session.commit()>>> user1 = User.query.filter_by(username="miaomiao").first()>>> print(user1)None>>>
3.5 表的查询
通过query可以查询模型中的所有的内容,接上个过程,如下:
>>> User.query.all()[User('miaoge')]>>> # 现在为用户2 ‘maoge’创建一篇日志>>> u2=User.query.get(2)>>> p2=Post(title='这是一篇关于数据库的日志',author=u2)>>> db.session.add(p2)>>> db.session.commit()>>> User.query.all()[User('miaoge')]>>> Post.query.filter_by(author=u2).all()[Post('这是一篇关于数据库的日志')]>>>
4. python shell上下文
刚开始在终端中输入flask shell,进入shell后,输入数据库模型等时它并没有定义,如下所示:
# 在终端中输入flask shell后,进入shell# 输入模型User时,发现它并没有被定义>>> UserTraceback (most recent call last): File "<console>", line 1, in <module>NameError: name 'User' is not defined>>> # 这就是因为刚开始进入shell时,它没有一个上下文,需要导入模型及其实例
这就需要每次进入flash shell环境时导入数据库的实例以及模型,这是非常浪费时间的。为了避免每次进入时导入,可以使用app.shell_context_processor装饰器,将定义的函数注册为一个shell上下文函数,当在终端中输入flask shell时,该函数会在shell中返回被注册的内容。在routes.py文件中添加如下代码:
# routes.py文件中的内容#!coding:utf8# 在这里需要将app导入,以供@app装饰器使用from database_demo import db, app......# 添加shell上下文函数# app.shell_context_processor装饰器将generate_shell_context函数注册为一个shell上下文函数@app.shell_context_processordef generate_shell_context(): return {'db': db, 'User': User, 'Post': Post}
在终端中重新输入flask shell,进入shell后查看效果:
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask shell/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning. 'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and 'Python 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0] on linuxApp: database_demo [production]Instance: /home/meyou/flask-plan/flask-course-primary/day4/instance>>> db<SQLAlchemy engine=sqlite:////home/meyou/flask-plan/flask-course-primary/day4/database_demo/database.db>>>> User<class 'database_demo.routes.User'>>>>
这时,所需要的模型都存在python shell里,而不需要每次进入shell时导入db及其数据模型。
5. 使用 Flask-Migrate 实现数据库迁移
在前面讲过创建数据库的方法,如果数据库中存在表或是需要修改数据库中模型,使用db.create_all()是不会更新数据库表的,需要先使用db.drop_all()删除旧表,再创建表。但是,原来表中的数据就都被删掉了,这时就出现了数据库迁移的概念。开发过程中,随着需求的变化,需要对表加入一些新的字段,但是旧表中又保存了很多数据,此时就需要创建新表,并将旧表中的数据迁移至新表中,Flask_Migrate这个插件就能帮我们快速完成数据从旧表到新表的迁移过程。
5.1 Flask_Migrate的安装
还是在虚拟环境中安装Flask_Migrate插件,如下:
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ pip install Flask_Migrate
5.2 Flask_Migrate的配置
在__init__.py文件中进行如下配置:
# __init__.py文件中的内容from flask import Flaskfrom flask_sqlalchemy import SQLAlchemy# 从flask_migrate中导入Migrate类from flask_migrate import Migrateimport osapp = Flask(__name__)basedir = os.path.abspath(os.path.dirname(__file__))app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'database.db')db = SQLAlchemy(app)# 通过类Migrate创建migrate实例migrate = Migrate(app, db)from database_demo.routes import *
5.3 数据库的迁移过程
Flask_Migrate插件中添加了flask db 命令来管理数据库迁移的所有事情,首先是迁移前的准备工作:
# 使用flask db init创建migration目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask db init/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning. 'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and ' Creating directory /home/meyou/flask-plan/flask-course-primary/day4/migrations ... done Creating directory /home/meyou/flask-plan/flask-course- primary/day4/migrations/versions ... done Generating /home/meyou/flask-plan/flask-course-primary/day4/migrations/README ... done Generating /home/meyou/flask-plan/flask-course-primary/day4/migrations/env.py ... done Generating /home/meyou/flask-plan/flask-course- primary/day4/migrations/script.py.mako ... done Generating /home/meyou/flask-plan/flask-course- primary/day4/migrations/alembic.ini ... done Please edit configuration/connection/logging settings in '/home/meyou/flask- plan/flask-course-primary/day4/migrations/alembic.ini' before proceeding.# 注意,如果在虚拟环境中没有设置环境变量FLASK_APP=run.py,则在使用命令flask db init时,会提醒需要先设置环境变量。在第一课最后讲到如何设置环境变量# 在day4目录下已经生成migrations目录(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ lsdatabase_demo migrations __pycache__ run.py run.pyc
使用flask db migrate命令检测Post,User类中是否添加了新的字段:
# 使用flask db migrate命令创建迁移脚本(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask db migrate/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning. 'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and 'INFO [alembic.runtime.migration] Context impl SQLiteImpl.INFO [alembic.runtime.migration] Will assume non-transactional DDL.INFO [alembic.env] No changes in schema detected.# 提示info消息没有任何改变
我们在routes.py文件中的User,Post模型中添加字段,这里,User表中添加了email字段,Post表中添加了content字段。然后再使用flask db migrate命令来执行,如下:
# routes.py文件中的内容#!coding:utf8from database_demo import db, appclass User(db.Model): __table__name = 'user' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(20), unique=True, nullable=False) email = db.Column(db.String(120), nullable=True) posts = db.relationship('Post', backref='author', lazy=True) def __repr__(self): return f"User('{self.username}')"class Post(db.Model): __table__name = 'post' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100), unique=True, nullable=False) content = db.Column(db.Text, nullable=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) def __repr__(self): return f"Post('{self.title}')"# 解释,添加email和content字段,unique必须设置为空或是不写unique,否则在用flask db migrate创建迁移脚本时会报错
再次使用flask db migrate命令检测Post,User类中是否添加了新的字段:
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask db migrate/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning. 'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and 'INFO [alembic.runtime.migration] Context impl SQLiteImpl.INFO [alembic.runtime.migration] Will assume non-transactional DDL.INFO [alembic.autogenerate.compare] Detected added column 'post.content'INFO [alembic.autogenerate.compare] Detected added unique constraint 'None' on '['content']'INFO [alembic.autogenerate.compare] Detected added column 'user.email'INFO [alembic.autogenerate.compare] Detected added unique constraint 'None' on '['email']' Generating /home/meyou/flask-plan/flask-course- primary/day4/migrations/versions/f49f801bddb6_.py ... done# 提示检测到有内容改变,添加了列content和列email字段
最后使用flask db upgrade命令完成数据库的迁移过程,如下所示:
# flask db upgrade可以把改变应用到数据库中,而不改变其中保存的数据(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask db upgrade/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning. 'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and 'INFO [alembic.runtime.migration] Context impl SQLiteImpl.INFO [alembic.runtime.migration] Will assume non-transactional DDL.INFO [alembic.runtime.migration] Running upgrade -> 8436eb139f65, empty message
6. 总结
学习完今天的内容,我们掌握了如下技能:
1.学习了Flask_SQLAlchemy的基本使用方法
2.学习了数据库模型及表之间关系的建立
3.学习了数据库的增删改查基本操作
4.学习了python shell上下文的使用
5.学习了使用Flask_Migrate完成数据库的迁移
下一课的教程,猫姐将带领大家一起学习用户登录功能的实现。今天的内容就到这里,喜欢的同学们可以在下面点赞留言,或是访问我的博客地址:http://www.catonlinepy.tech/ 加入我们的QQ群进一步交流学习!
7. 代码的获取
大家可以到github上获取今天教程的所有代码:https://github.com/miaojie19/...
具体下载代码的命令如下:
# 使用git命令下载flask-course-primary仓库所有的代码git clone https://github.com/miaojie19/flask-course-primary.git# 下载完成后,进入day4目录下面,即可看到今天的代码cd flask-course-primarycd day4