Flask ORM SQLAlchemy数据操作完整案例

Posted 多鱼的夏天

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask ORM SQLAlchemy数据操作完整案例相关的知识,希望对你有一定的参考价值。

ORM

  • ORM 全拼Object-Relation Mapping.

  • 中文意为 对象-关系映射.

  • 主要实现模型对象到关系数据库数据的映射.

    • 比如:把数据库表中每条记录映射为一个模型对象

优点

  • 只需要面向对象编程, 不需要面向数据库编写代码.
    • 对数据库的操作都转化成对类属性和方法的操作.
    • 不用编写各种数据库的sql语句.
  • 实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异.
    • 不在关注用的是mysqloracle…等.
    • 通过简单的配置就可以轻松更换数据库, 而不需要修改代码.

缺点

  • 相比较直接使用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中类型说明
Integerint普通整数,一般是32位
SmallIntegerint取值范围小的整数,一般是16位
BigIntegerint或long不限制精度的整数
Floatfloat浮点数
Numericdecimal.Decimal普通整数,一般是32位
Stringstr变长字符串
Textstr变长字符串,对较长或不限长度的字符串做了优化
Unicodeunicode变长Unicode字符串
UnicodeTextunicode变长Unicode字符串,对较长或不限长度的字符串做了优化
Booleanbool布尔值
Datedatetime.date时间
Timedatetime.datetime日期和时间
LargeBinarystr二进制文件

常用的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="#">&laquo;</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="#">&raquo;</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数据操作完整案例的主要内容,如果未能解决你的问题,请参考以下文章

Flask ORM SQLAlchemy数据操作完整案例

sqlalchemy和flask-sqlalchemy

Flask-SQLAlchemy

快速入门流行ORM框架~Flask-SQLAlchemy

Flask web开发之路六

Flask的ORM和查询操作