Flask ORM SQLAlchemy数据操作完整案例
Posted 多鱼的夏天
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask ORM SQLAlchemy数据操作完整案例相关的知识,希望对你有一定的参考价值。
ORM
-
ORM
全拼Object-Relation Mapping
. -
中文意为
对象-关系映射
. -
主要实现模型对象到关系数据库数据的映射.
- 比如:把数据库表中每条记录映射为一个模型对象
优点
- 只需要面向对象编程, 不需要面向数据库编写代码.
- 对数据库的操作都转化成对类属性和方法的操作.
- 不用编写各种数据库的
sql语句
.
- 实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异.
- 不在关注用的是
mysql
、oracle
…等. - 通过简单的配置就可以轻松更换数据库, 而不需要修改代码.
- 不在关注用的是
缺点
- 相比较直接使用SQL语句操作数据库,有性能损失.
- 根据对象的操作转换成SQL语句,根据查询的结果转化成对象, 在映射过程中有性能损失
Flask-SQLAlchemy安装及设置
- SQLALchemy 实际上是对数据库的抽象,让开发者不用直接和 SQL 语句打交道,而是通过 Python 对象来操作数据库,在舍弃一些性能开销的同时,换来的是开发效率的较大提升
- SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。
- 文档地址:http://docs.jinkan.org/docs/flask-sqlalchemy
环境搭建
- 安装 flask-sqlalchemy
pip install flask-sqlalchemy
- 如果连接的是 mysql 数据库,需要安装 mysqldb
pip install flask-mysqldb
数据库连接设置
- 在 Flask-SQLAlchemy 中,数据库使用URL指定,而且程序使用的数据库必须保存到Flask配置对象的 SQLALCHEMY_DATABASE_URI 键中
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'
- 其他设置:
# 动态追踪修改设置,如未设置只会提示警告
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
#查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
连接其他数据库
完整连接 URI 列表请跳转到 SQLAlchemy 下面的文档 (Supported Databases) 。这里给出一些 常见的连接字符串。
- Postgres:
postgresql://scott:tiger@localhost/mydatabase
- MySQL:
mysql://scott:tiger@localhost/mydatabase
- Oracle:
- oracle://scott:tiger@127.0.0.1:1521/sidname
- SQLite (注意开头的四个斜线):
sqlite:absolute/path/to/foo.db
常用的SQLAlchemy字段类型
类型名 | python中类型 | 说明 |
---|---|---|
Integer | int | 普通整数,一般是32位 |
SmallInteger | int | 取值范围小的整数,一般是16位 |
BigInteger | int或long | 不限制精度的整数 |
Float | float | 浮点数 |
Numeric | decimal.Decimal | 普通整数,一般是32位 |
String | str | 变长字符串 |
Text | str | 变长字符串,对较长或不限长度的字符串做了优化 |
Unicode | unicode | 变长Unicode字符串 |
UnicodeText | unicode | 变长Unicode字符串,对较长或不限长度的字符串做了优化 |
Boolean | bool | 布尔值 |
Date | datetime.date | 时间 |
Time | datetime.datetime | 日期和时间 |
LargeBinary | str | 二进制文件 |
常用的SQLAlchemy列选项
选项名 | 说明 |
---|---|
primary_key | 如果为True,代表表的主键 |
unique | 如果为True,代表这列不允许出现重复的值 |
index | 如果为True,为这列创建索引,提高查询效率 |
nullable | 如果为True,允许有空值,如果为False,不允许有空值 |
default | 为这列定义默认值 |
常用的SQLAlchemy关系选项
选项名 | 说明 |
---|---|
backref | 在关系的另一模型中添加反向引用 |
primary join | 明确指定两个模型之间使用的联结条件 |
uselist | 如果为False,不使用列表,而使用标量值 |
order_by | 指定关系中记录的排序方式 |
secondary | 指定多对多关系中关系表的名字 |
secondary join | 在SQLAlchemy中无法自行决定时,指定多对多关系中的二级联结条件 |
数据库基本操作
- 在Flask-SQLAlchemy中,插入、修改、删除操作,均由数据库会话管理。
- 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 commit() 方法提交会话。
- 在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。
- 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
在视图函数中定义模型类
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
#设置连接数据库的URL
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
#查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
class Role(db.Model):
# 定义表名
__tablename__ = 'roles'
# 定义列对象
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
us = db.relationship('User', backref='role')
#repr()方法显示一个可读字符串
def __repr__(self):
return 'Role:%s'% self.name
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, index=True)
password = db.Column(db.String(64))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __repr__(self):
return 'User:%s'%self.name
if __name__ == '__main__':
app.run(debug=True)
常用的SQLAlchemy查询过滤器
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit | 使用指定的值限定原查询返回的结果 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
常用的SQLAlchemy查询执行器
方法 | 说明 |
---|---|
all() | 以列表形式返回查询的所有结果 |
first() | 返回查询的第一个结果,如果未查到,返回None |
first_or_404() | 返回查询的第一个结果,如果未查到,返回404 |
get() | 返回指定主键对应的行,如不存在,返回None |
get_or_404() | 返回指定主键对应的行,如不存在,返回404 |
count() | 返回查询结果的数量 |
paginate() | 返回一个Paginate对象,它包含指定范围内的结果 |
创建表
db.create_all()
删除表
db.drop_all()
插入一条数据
ro1 = Role(name='admin')
db.session.add(ro1)
db.session.commit()
#再次插入一条数据
ro2 = Role(name='user')
db.session.add(ro2)
db.session.commit()
一次插入多条数据
us1 = User(name='wang', password='123456', role_id=ro1.id)
us2 = User(name='zhang', password='201512', role_id=ro2.id)
us3 = User(name='chen', password='987654', role_id=ro2.id)
us4 = User(name='zhou', password='456789', role_id=ro1.id)
us5 = User(name='tang', password='158104', role_id=ro2.id)
us6 = User(name='wu', password='5623514', role_id=ro2.id)
us7 = User(name='qian', password='1543567', role_id=ro1.id)
us8 = User(name='liu', password='867322', role_id=ro1.id)
us9 = User(name='li', password='4526342', role_id=ro2.id)
us10 = User(name='sun', password='235523', role_id=ro2.id)
db.session.add_all([us1, us2, us3, us4, us5, us6, us7, us8, us9, us10])
db.session.commit()
查询:filter_by精确查询
返回名字等于wang的所有人
User.query.filter_by(name='wang').all()
first()返回查询到的第一个对象
User.query.first()
filter模糊查询,返回名字结尾字符为g的所有数据。
User.query.filter(User.name.endswith('g')).all()
get():参数为主键,如果主键不存在没有返回内容
User.query.get()
User.query.get('1')
逻辑非,返回名字不等于wang的所有数据
User.query.filter(User.name!='wang').all()
not_ 相当于取反
from sqlalchemy import not_
User.query.filter(not_(User.name=='chen')).all()
逻辑与,需要导入and,返回and()条件满足的所有数据
from sqlalchemy import and_
User.query.filter(and_(User.name!='wang',User.email.endswith('163.com'))).all()
逻辑或,需要导入or_
from sqlalchemy import or_
User.query.filter(or_(User.name!='wang',User.email.endswith('163.com'))).all()
查询数据后删除
user = User.query.first()
db.session.delete(user)
db.session.commit()
User.query.all()
更新数据
user = User.query.first()
user.name = 'dong'
db.session.commit()
User.query.first()
关联查询示例
- 查询角色的所有用户
#查询roles表id为1的角色
ro1 = Role.query.get(1)
#查询该角色的所有用户
ro1.us.all()
- 查询用户所属角色
#查询users表id为3的用户
us1 = User.query.get(3)
#查询用户属于什么角色
us1.role
综合案例
数据库迁移
- 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
- 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
- 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
- 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。
首先要在虚拟环境中安装Flask-Migrate。
pip install flask-migrate
如下示例:定义模型类。
# _*_ coding: utf-8 _*_
# @Author : zzg
# motto : 有教无类,陪伴呵护
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate,MigrateCommand
from datetime import datetime
app = Flask(__name__)
manager = Manager(app)
#设置连接数据
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:12345678@127.0.0.1:3306/test2'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
#第一个参数是Flask的实例,第二个参数是Sqlalchemy数据库实例
Migrate(app,db)
#manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
manager.add_command('db',MigrateCommand)
class Infos(db.Model):
# __table_args__ = 'extend_existing': True
__tablename__ = 'db_infos'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=True)
codecoll = db.Column(db.String(10),nullable=True)
echnology = db.Column(db.String(200),nullable=True)
direction = db.Column(db.String(200),nullable=True)
price = db.Column(db.String(50),nullable=True)
create_time = db.Column(db.DateTime,default=datetime.now)
def __repr__(self):
return "<Role %s>" % self.name
@app.route('/')
def index():
return 'adad'
if __name__ == '__main__':
manager.run()
创建迁移仓库
#这个命令会创建migrations文件夹,所有迁移文件都放在里面。
python manage.py db init
生成迁移脚本
python manage.py db migrate
更新数据库
python manage.py db upgrade
添加测试数据
insert into `db_infos` values (null ,'王发奋','2','python','网络安全','9K',now());
insert into `db_infos` values (null ,'李向前','3','python','爬虫开发','15K',now());
insert into `db_infos` values (null ,'张得帅','3','python','web开发','12K',now());
insert into `db_infos` values (null ,'貂蝉','3','python','web开发','12K',now());
insert into `db_infos` values (null ,'吕布','3','python','web开发','12K',now());
insert into `db_infos` values (null ,'王昭君','3','python','web开发','12K',now());
insert into `db_infos` values (null ,'鲁班','3','python','web开发', '12K',now());
insert into `db_infos` values (null ,'孙猴子','3','python','web开发','12K',now());
insert into `db_infos` values (null ,'杨贵妃','3','python','web开发', '12K',now());
执行数据库导入脚本
mysql -uroot -p -D test2 < data.sql
前端页面编写
- 打开课件提供的WEB文件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>CSDN项目实战课</title>
<link rel="stylesheet" href="../static/jq-1.css">
<link rel="styleSheet" href="../static/index.css" >
<style>
.box
float: right;
#boxs
width: 100%;
#box1
margin-left: 36%;
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">我的项目实战课程</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="javascript:void(0);" target="_blank">Login</a></li>
<li><a href="#">CSDN学院</a></li>
<li><a href="#">help</a></li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li class="active"><a href="#" style="color: #0f0f0f;font-size: 18px;background-color:grey">菜单栏</a></li>
<li><a href="#">数据中心</a></li>
<li><a href="#">你的私人小秘密</a></li>
<li><a href="#">等你来开发</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<form class="navbar-form" role="search">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" name="key">
</div>
<button type="submit" class="btn btn-info">搜索</button>
</form>
<div class="box">
<button type="submit" class="btn btn-success">添加</button>
</div>
<h3 class="sub-header">我是后台管理页面</h3>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>编号</th>
<th>代号</th>
<th>码龄</th>
<th>熟悉语言</th>
<th>主攻方向</th>
<th>薪资</th>
<th>操作</th>
</tr>
</thead>
<tbody class="datas">
<tr>
<td>1</td>
<td>老王</td>
<td>2年</td>
<td>python/Java</td>
<td>web开发</td>
<td>25K</td>
<td><button type="button" class="btn btn-danger btn-sm">删除</button>
<button type="button" class="btn btn-warning btn-sm">编辑</button></td>
</tr>
<tr>
<td>2</td>
<td>老李</td>
<td>3年</td>
<td>python/Java</td>
<td>爬虫</td>
<td>30K</td>
<td><button type="button" class="btn btn-danger btn-sm">删除</button>
<button type="button" class="btn btn-warning btn-sm">编辑</button></td>
</tr>
</tbody>
</table>
</div>
<div id="boxs">
<ul class="pagination" id="box1">
<li><a href="#">«</a></li>
<li class="active"><a href="#">1</a></li>
<li class="disabled"><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li><a href="#">»</a></li>
</ul>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script>
var $lis = $('.nav-sidebar li');
$lis.click(function ()
$(this).addClass('active').siblings('li').removeClass('active');
);
</script>
</body>
</html>
数据展示
class Admins(MethodView):
def get(self):
page = request.args.get('page', default=1, type=int)
key = request.args.get('key',None)
data = Infos.query.filter(Infos.name.like('%'+key+'%')) if key else Infos.query
pagination = data.paginate(
page=page,
per_page=2,
)
persons = pagination.items
return render_template('admins.html',pagination=pagination,person=persons)
app.add_url_rule('/',view_func=Admins.as_view(name='mains'))
数据添加
前端模版页面
% extends 'admins.html' %
% block content %
<form class="form-horizontal" >
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">代号</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="inputEmail3" placeholder="请输入代号">
</div>
</div>
<div class="form-group">
<label for="inputEmail4" class="col-sm-2 control-label">码龄</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="inputEmail4" placeholder="请输入代号">
</div>
</div>
<div class="form-group">
<label for="inputEmail5" class="col-sm-2 control-label">方向</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="inputEmail5" placeholder="请输入代号">
</div>
</div>
<div class="form-group">
<label for="inputEmail6" class="col-sm-2 control-label">语言</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="inputEmail6" placeholder="请输入代号">
</div>
</div>
<div class="form-group">
<label for="inputEmail7" class="col-sm-2 control-label">薪资</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="inputEmail7" placeholder="请输入代号">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-success" >提交</button>
</div>
</div>
</form>
% endblock %
后台业务代码
@app.route('/add',methods=['GET','POST'])
def add_data():
if request.method.lower() =='post':
form = DataForm(request.form)
if form.validate():
info = Infos()
info.name = form.dh.data
info.codecoll = form.ml.data
info.echnology = form.yy.data
info.direction = form.fx.data
info.price = request.form.get('xz')
db.session.add(info)
db.session.commit()
flash('数据添加成功')
return redirect('/')
else:
return jsonify('message':form.errors)
return render_template('add_edit.html')
数据认证–使用flask表单
from flask_wtf import FlaskForm
from wtforms import StringField,IntegerField
from wtforms.validators import Regexp,ValidationError,EqualTo,InputRequired,Length,Required,DataRequired
class DataForm(FlaskForm):
dh = StringField(validators=[DataRequired(message='请填写代号'),Length(1, 50,message='须在6~50个字符之间')])
ml = StringField(validators=[DataRequired(message='请填写马岭'),Length(1, 3,message='须在1~3个字符之间')])
yy = StringField(validators=[DataRequired(message='请填写语言'),Length(1, 50,message='须在1~50个字符之间')])
fx = StringField(validators=[DataRequired(message='请填写方向'),Length(1, 50,message='须在1~50个字符之间')])
xz = StringField(validators=[DataRequired(message='请填写薪资'),Length(1, 50,message='须在1~50个字符之间')])
注:flask针对数据提交需要认证,设置token返回给前端页面
from flask_wtf import CSRFProtect
app.config["SECRET_KEY"] = '79537d00f4834892986f09a100aa1edf'
CSRFProtect(app)
...
<input type="hidden" name="csrf_token" value=" csrf_token() " />
前端业务处理
- 前端路由
<script>
$('.box button').click(function ()
window.location.href = '/add'
);
</script>
- 使用宏 封装前端
% extends 'admins.html' %
% block content %
<!-- 定义宏 -->
% macro input(for="",label="", type="text", name="",id="", value="",pl="") %
<div class="form-group">
<label for=" for " class="col-sm-2 control-label"> label </label>
<div class="col-sm-8">
<input type=" type " name=" name " class="form-control" id=" id " value=" value " placeholder=" pl ">
</div>
</div>
% endmacro %
<!-- 调用宏 -->
<form class="form-horizontal" style="margin: 80px auto" method="post">
<input type="hidden" name="csrf_token" value=" csrf_token() " />
input("inputEmail3","代号", name="dh",id='inputEmail3',pl='请输入代号')
input("inputEmail4","码龄", name="ml",id='inputEmail4',pl='请输入码领')
input("inputEmail5","语言", name="yy",id='inputEmail5',pl='请输入语言')
input("inputEmail6","方向", name="fx",id='inputEmail6',pl='请输入方向')
input("inputEmail7","薪资", name="xz",id='inputEmail7',pl='请输入薪资')
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-success" >添加</button>
</div>
</div>
</form>
% endblock %
数据修改
视图处理
@app.route('/edit/<id>',methods=['GET','POST'])
def edit_data(id):
data = Infos.query.filter_by(id=id).first()
if request.method.lower() == 'post':
if data:
form = DataForm(request.form)
if form.validate():
data.name = form.dh.data
data.codecoll = form.ml.data
data.echnology = form.yy.data
data.direction = form.fx.data
data.price = request.form.get('xz')
db.session.add(data)
db.session.commit()
flash('数据修改成功')
return redirect('/')
else:
return jsonify('message': form.errors)
else:
abort(Response('大哥,别费劲了'))
else:
return render_template('add_edit.html',data=data)
数据认证
注:数据相同,可以使用同一个表单认证
前端处理
-
前端路由
<script>
$('#edit').click(function ()
console.log($(this).data('id'));
window.location.href = '/edit/' + $(this).data('id')
)
</script>
注意:ID选择器是唯一的,这个地方需要使用类选择器获取ID,否则只能获取第一行
-
前端业务代码
<form class="form-horizontal" style="margin: 80px auto" method="post">
<input type="hidden" name="csrf_token" value=" csrf_token() " />
% if data %
input("inputEmail3","代号", name="dh",id='inputEmail3',value=data.name,pl='请输入代号')
input("inputEmail4","码龄", name="ml",id='inputEmail4',value=data.codecoll,pl='请输入码领')
input("inputEmail5","语言", name="yy",id='inputEmail5',value=data.echnology,pl='请输入语言')
input("inputEmail6","方向", name="fx",id='inputEmail6',value= data.direction ,pl='请输入方向')
input("inputEmail7","薪资", name="xz",id='inputEmail7',value=data.price ,pl='请输入薪资')
% else %
input("inputEmail3","代号", name="dh",id='inputEmail3',pl='请输入代号')
input("inputEmail4","码龄", name="ml",id='inputEmail4',pl='请输入码领')
input("inputEmail5","语言", name="yy",id='inputEmail5',pl='请输入语言')
input("inputEmail6","方向", name="fx",id='inputEmail6',pl='请输入方向')
input("inputEmail7","薪资", name="xz",id='inputEmail7',pl='请输入薪资')
% endif %
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-success" >% if not data %添加% else %修改% endif %</button>
</div>
</div>
</form>
数据删除
后台视图
@app.route('/del/<id>',methods=['GET','POST'])
def del_data(id):
data = Infos.query.filter_by(id=id).first()
if not data:
flash("参数有问题")
try:
db.session.delete(data)
db.session.commit()
except Exception as e:
db.session.rollback()
return redirect('/')
前端路由
$('.del').click(function ()
window.location.href = '/del/' + $(this).data('id')
)
以上是关于Flask ORM SQLAlchemy数据操作完整案例的主要内容,如果未能解决你的问题,请参考以下文章