目录
一.数据库环境配置
在Anaconda Prompt里面切换到虚拟环境
conda activate flask_dev
使用下面的命令来下载Flask-SQLAlchemy 扩展来进行数据库操作
pip install flask-sqlalchemy
结果如图:
在eclipse里面新建一个.py文件来配置数据库,进行如下的步骤:
import os from flask_sqlalchemy import SQLAlchemy # 定义SQLite绝对路径 basedir = 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_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) # 实例化SQLAlchemy
二·定义模型
在 ORM(对象关系映射器)中,模型一般是一个 Python 类,类中的属性对应于数据库表中的列。Flask-SQLAlchemy 实例为模型提供了一个基类以及一系列辅助类和辅助函数,可用于定义模型的结构。
在这个数据库关系图中, roles 表存储所有可用的用户角色,每个角色都使用一个唯一的
id 值(即表的主键)进行标识。 users 表包含用户列表,每个用户也有唯一的 id 值。除了
id 主键之外, roles 表中还有 name 列, users 表中还有 username 列和 password 列。 users
表中的 role_id 列是外键,引用角色的 id ,通过这种方式为每个用户指定角色。
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
三·关系
关系型数据库使用关系把不同表中的行联系起来,上面那个关系图中表示的实际上是一种一对多关系,即一个角色可属于多个用户,而每个用户只能由一个角色。
在数据库模型中定义关系补充如下:
class Role(db.Model): # ... users = db.relationship('User', backref='role') class User(db.Model): # ... role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
注:(这两行代码应该要放到定义模型中去)
如图:
如果运行结果如图没有错误,那么可以进行下面的操作。
打开Anaconda Prompt然后再次进入到虚拟环境,找到刚刚在eclipse建立的.py文档的存放路径,启动shell命令:
存放路径的寻找可以通过右键打开Properties,就可以看到这个.py文档存放的位置
位置即下图的location所显示:
然后启动shell命令成功后的结果如图所示:
启动shell命令的代码:
flask shell
四、数据库操作
4.1.输入下面的命令来创建表:
注意:我用的配置数据库的文件名为app.py,你用的什么文件就要改成对应的名字。
from app import db db.create_all()
出现如下的错误:sqlite3.OperationalError:disk I/O error
在网上查阅之后,发现很有可能是权限问题
解决方法:
我的是因为D盘的权限不够,很久之前我的d盘出现一个改文件夹的名字都需要管理员权限,以及在eclipse里面不可以直接修改项目的名字然后我只能在d盘存放该项目的文件夹的地方进行修改,后面发现其实就是d盘的权限不够,
在D盘的属性里面
点击编辑再加上“修改”的权限就可以实现db.create_all()
如果你的错误和我的不一样可以看看下面的解决方法:
如果命令执行成功就可以在app.py文件的下面看到有一个data.sqlite的文件
2.插入行
下面这段代码创建了一些角色和用户:
模型的构造函数接受的参数是使用关键字参数指定的模型属性初始值。注意, role 属性也
可使用,虽然它不是真正的数据库列,但却是一对多关系的高级表示。这些新建对象的 id
属性并没有明确设定,因为主键是由 Flask-SQLAlchemy 管理的。现在这些对象只存在于
Python 中,还未写入数据库。因此 id 尚未赋值:
通过数据库会话管理对数据库所做的改动,在 Flask-SQLAlchemy 中,会话由 db.session
表示。准备把对象写入数据库之前,先要将其添加到会话中:
或者简写成:
把对象写入数据库,我们要调用 commit() 方法提交会话:
但是出现了如下的错误:sqlite3.OperationalError:no such table :roles
错误解决方法:
后面在和舍友探讨之后发现在每一次启动flask shell后都要先进行定位
如下图:Set Flask_APP=路径(存放数据库配置文件的路径+数据库配置文件的名字)
Set Flask_APP=D:\eclipse-workspace\Hello1\Homework3\app
然后在定位之后,重新输入创建表,插入行的操作再进行commit()提交就可以成功提交
再次查看id属性:
3.修改行
在数据库会话上调用 add() 方法也能更新模型。我们继续在之前的 shell 会话中进行操作,
下面这个例子把 "Admin" 角色重命名为 "Administrator" :
4.删除行
数据库会话还有个 delete() 方法。下面这个例子把 "Moderator" 角色从数据库中删除:
5.查询行
Flask-SQLAlchemy 为每个模型类都提供了 query 对象。最基本的模型查询是取回对应表中
的所有记录:
使用过滤器可以配置 query 对象进行更精确的数据库查询。下面这个例子查找角色为
"User" 的所有用户:
若要查看 SQLAlchemy 为查询生成的原生 SQL 查询语句,只需把 query 对象转换成字
符串:
分别从关系的两端查询角色和用户之间的一对
多关系:
在Role表里面添加
users = db.relationship('User', backref='role', lazy='dynamic')
即下图蓝色背景的地方
但是 出现如下的错误
因为视图函数的数据库实现并不需要以上的操作,所以这个错误没有得到解决。
五、视图函数建立数据库:
在虚拟环境中下载对应的插件
pip install flask_bootstrap
pip install flask_wtf
pip install wtforms
pip install wtforms.validators
在视图函数里面进行数据库的增删改查代码:
app.py
from flask_bootstrap import Bootstrap
from flask import Flask,render_template,session,redirect,url_for
from flask_wtf import FlaskForm
from wtforms import StringField,SubmitField
from wtforms.validators import DataRequired
import os
from flask_sqlalchemy import SQLAlchemy
# 定义SQLite绝对路径
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string!'
Bootstrap = Bootstrap(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \
os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app) # 实例化SQLAlchemy
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)
db.session.commit()
session['known']=False
else:
session['known']=True
session['name'] = form.name.data
form.name.data=''
return redirect(url_for('index'))
return render_template('index.html',form=form, name=session.get('name'),known = session.get('known', False))
#删除
@app.route('/delete/',methods=['GET',"POST"])
def delete():
form=DNameForm()
user=User.query.filter_by(username=form.name.data).first()
if user is None:
session['known']=True
if form.validate_on_submit():
if user is None:
session['known']=False
else:
db.session.delete(user)
db.session.commit()
session['known']=True
session['name']=form.name.data
form.name.data=''
return redirect(url_for('delete'))
return render_template('delete.html',form=form,name=session.get('name'),known=session.get('known',False))
#修改
@app.route('/modify/<name>',methods=['GET',"POST"])
def modify(name):
form=MNameForm()
if form.validate_on_submit():
user=User.query.filter_by(username=name).first()
if user is None:
session['known']=False
else:
user.username=form.name.data
db.session.add(user)
db.session.commit()
session['known']=True
session['name']=form.name.data
form.name.data=''
return redirect(url_for('modify',name=session.get('name')))
return render_template('modify.html',form=form,name=name,known=session.get('known' ,False))
#查询
@app.route('/query/',methods=['GET',"POST"])
def query() :
a=User.query.al1()
return render_template('query.html',name=a)
class NameForm(FlaskForm):
name=StringField('What is your name?',validators=[DataRequired()])
submit=SubmitField('Submit')
class MNameForm(FlaskForm):
name=StringField('what is the name you need to modify?',validators=[DataRequired()])
submit=SubmitField('Submit')
class DNameForm(FlaskForm):
name=StringField('what is the name you need to delete?',validators=[DataRequired()])
submit=SubmitField('Submit')
if __name__ == '__main__':
app.run(debug=True)
index.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{% if not known %}
<p>Pleased to meet you!</p>
{% else %}
<p>Happy to see you again!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
base.html
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor%}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
wtf.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name(id='my-text-field') }}
{{ form.submit() }}
</form>
</body>
</html>
delete.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello,请输入要删除的用户名:</h1>
{% if not known %}
<p>{{ name }}该用户不存在!</p>
{% else %}
<p>{{ name }}该用户删除成功!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
modify.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello,请输入要修改的用户名:</h1>
{% if not known %}
<p>{{ name }}该用户不存在!</p>
{% else %}
<p>{{ name }}该用户修改成功!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
query.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello,{% if name %}{{ name }}{% else %}stranger(% endif %}!</h1>
{% if not known %}
<p>{{ name }}查询失败!</p>
{% else %}
<p>{{ name }}查询成功!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
但是我的代码在运行第一个页面出来之后就出现了奇怪的错误
错误如下:ImportError: DLL load failed:找不到指定的模块
改正方法:
https://zhuanlan.zhihu.com/p/81841576
我在查阅上面的那篇文章之后就发现我的环境变量里面没有对应的Anaconda Promt的路径,然后我就把对应的路径加入我的进行环境变量之后就可以得到正确的页面运行结果
然后其他修改,查询以及删除的页面就需要通过在地址处添加上modify,query,delete来进行跳转。
在进行这一步的时候有可能会因为你退出了Anaconda Promt而导致之前的Role和Users表格数据丢失,如果在查询:Role.query.all(),User.query.all(),出现下面的结果,那么需要你重新按照上面的步骤把数据输入进去,输到commit()提交那一步即可
然后下面是运行结果:
先在Anaconda Promt里面查询结果:
可以看到我第一次查询的时候是没有kkm的,后面在主页提交上去之后终端就可以查到了
然后再次输入一样的名字就会变成:Happy to see you again!
接着在网址上面加上/modify/+你要修改成的名字,在框框里面输入你之前的名字,就可以得到下面的结果
然后在地址里面输入/delete
就可以得到下面的结果:
六、集成Python shell
每次启动 shell 会话都要导入数据库实例和模型,这真是份枯燥的工作。为了避免一直重复
导入,我们可以做些配置,让 Flask-Script 的 shell 命令自动导入特定的对象。
若想把对象添加到导入列表中,我们要为 shell 命令注册一个 make_context 回调函数
from flask.ext.script import Shell
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:
我的这一步出错了,不知道该如何解决,大家可以跳转到这个网页查看一下
https://blog.csdn.net/JineD/article/details/124774570
错误显示ModuleNotFoundError: No module named 'flask.ext',大家参考下面的方法
主要原因是新版的flask抛弃了flask.ext这种引入扩展的方法,更改为 flask_扩展名
以前:from flask.ext.script import Manager
现在:from flask_script import Manager
参考文章:https://blog.csdn.net/weixin_40651515/article/details/105383292
结果如图:
七、使用Flask-Migrate实现数据库迁移
1.创建迁移仓库
在虚拟环境中安装 Flask-Migrate:
pip install flask-migrate
配置 Flask-Migrate
from flask.ext.migrate import Migrate, MigrateCommand
# ...
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
终端执行结果:
(venv) PS F:\MyCode\Python\FlaskWebLearn\flasky> flask db init
Creating directory F:\MyCode\Python\FlaskWebLearn\flasky\migrations ... done
Creating directory F:\MyCode\Python\FlaskWebLearn\flasky\migrations\versions ... done
Generating F:\MyCode\Python\FlaskWebLearn\flasky\migrations\alembic.ini ... done
Generating F:\MyCode\Python\FlaskWebLearn\flasky\migrations\env.py ... done
Generating F:\MyCode\Python\FlaskWebLearn\flasky\migrations\README ... done
Generating F:\MyCode\Python\FlaskWebLearn\flasky\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'F:\\MyCode\\Python\\FlaskWebLearn\\flasky\\migrations\\alembic.ini' before proceeding.
2.创建迁移脚本
在 Alembic 中,数据库迁移用迁移脚本表示。脚本中有两个函数,分别是 upgrade() 和
downgrade() 。 upgrade() 函数把迁移中的改动应用到数据库中, downgrade() 函数则将改动
删除。Alembic 具有添加和删除改动的能力,因此数据库可重设到修改历史的任意一点。
我们可以使用 revision 命令手动创建 Alembic 迁移,也可使用 migrate 命令自动创建。
手动创建的迁移只是一个骨架, upgrade() 和 downgrade() 函数都是空的,开发者要使用Alembic 提供的 Operations 对象指令实现具体操作。自动创建的迁移会根据模型定义和数
据库当前状态之间的差异生成 upgrade() 和 downgrade() 函数的内容。
migrate 子命令用来自动创建迁移脚本:
(venv) $ python hello.py db migrate -m "initial migration"
INFO [alembic.migration] Context impl SQLiteImpl.
INFO [alembic.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate] Detected added table 'roles'
INFO [alembic.autogenerate] Detected added table 'users'
INFO [alembic.autogenerate.compare] Detected added index
'ix_users_username' on '['username']'
Generating /home/flask/flasky/migrations/versions/1bc
594146bb5_initial_migration.py...done
3.更新数据库
检查并修正好迁移脚本之后,我们可以使用 db upgrade 命令把迁移应用到数据库中:
(venv) $ python hello.py db upgrade
INFO [alembic.migration] Context impl SQLiteImpl.
INFO [alembic.migration] Will assume non-transactional DDL.
INFO [alembic.migration] Running upgrade None -> 1bc594146bb5, initial migration