Flask 进阶系列:SQLAlchemy 扩展学习

Posted 编程派

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask 进阶系列:SQLAlchemy 扩展学习相关的知识,希望对你有一定的参考价值。

原文:http://www.bjhee.com/flask-ext4.html

熟悉 Java 的朋友们一定使用过 Hibernate 或 MyBatis 吧,这类的框架称为对象关系映射 ORM 框架,它将对数据库的操作从繁琐的 SQL 语言执行简化为对象的操作。Python 中也有类似的 ORM 框架,叫 SQLAlchemy。本篇我们将介绍 Flask 中支持 SQLAlchemy 框架的第三方扩展,Flask-SQLAlchemy。

安装和启用

在阅读此文之前,强烈建议读者先了解 SQLAlchemy 的基本知识。

我们依然通过 pip 安装:

 
   
   
 
  1. $ pip install Flask-SQLAlchemy

PyPI 自动会将其所依赖的 SQLAlchemy 包装上。我们可以采用下面的方法初始化一个 Flask-SQLAlchemy 的实例:

 
   
   
 
  1. from flask import Flask

  2. from flask.ext.sqlalchemy import SQLAlchemy

  3.  

  4. app = Flask(__name__)

  5. app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db/users.db'

  6. db = SQLAlchemy(app)

应用配置项"SQLALCHEMYDATABASEURI"指定了 SQLAlchemy 所要操作的数据库的连接字符串,本文中我们使用 SQLite3,连接字符串以"sqlite:///"开头,后面的"db/users.db"表示数据库文件是当前位置下 db 子目录中的"users.db"文件。

定义模型

一个模型即对应数据库中的一个表,这里我们来定义一个用户模型:

 
   
   
 
  1. class User(db.Model):

  2.     id = db.Column(db.Integer, primary_key=True)

  3.     name = db.Column(db.String(50), unique=True)

  4.     age = db.Column(db.Integer)

  5.  

  6.     def __init__(self, name, age):

  7.         self.name = name

  8.         self.age = age

  9.  

  10.     def __repr__(self):

  11.         return '<User %r>' % self.name

模型类必须继承"db.Model", db 即上一节的"db = SQLAlchemy(app)",上例中的 User 模型将自动映射到数据库中的"user"表。User 模型中定义了三个属性:

  1. "id":整型主键

  2. "name":最大长度为 50 的字符串,且值唯一

  3. "age":整型

这三个属性将分别对应"user"表中"id"主键, "name"和"age"字段。写好"init()"和"repr"() 方法,我们的模型就定义完成了。现在你就可以通过下面的代码来创建数据库和表:

 
   
   
 
  1.     db.create_all()

让我们来验证下,"user"表是否创建成功。首先打开数据库文件:

 
   
   
 
  1. $ sqlite3 db/users.db

查询下"user"表的 schema:

 
   
   
 
  1. sqlite> .schema user

你应该可以看到下面的信息:

 
   
   
 
  1. CREATE TABLE user (

  2. id INTEGER NOT NULL,

  3. name VARCHAR(50),

  4. age INTEGER,

  5. PRIMARY KEY (id),

  6. UNIQUE (name)

  7. );

另外,你可以通过"db.drop_all()"方法删除所有的表,不过数据库文件将会被保留。

添加数据

数据表创建完后,让我们添加些数据进去:

 
   
   
 
  1.     db.session.add(User('Michael', 18))

  2.     db.session.add(User('Tom', 21))

  3.     db.session.add(User('Jane', 17))

  4.     db.session.commit()

一定要记得调用"db.session.commit()"提交事务,不然数据不会保存到数据库中。我们无需指定每条记录的"id"主键值,数据库会自动使用自增的数值作为主键。

查询数据

每个数据模型都有"query"接口可以用来查询模型所对应的表的记录。比如,查询"user"表中的所有记录:

 
   
   
 
  1.     users = User.query.all()

返回的 users 是一个列表,其中每个元素都是一个 User 类型的对象,对应于"user"表中的一条记录。该方法相当于执行了 SQL 语句:

 
   
   
 
  1. SELECT * FROM user

"query"接口拥有丰富的方法,这里列举一些常用的:

  1. "filter_by()"方法,对查询结果过滤,参数必须是键值对"key=value"

 
   
   
 
  1.     # WHERE name='Tom'

  2.     users = User.query.filter_by(name='Tom')

  3.     # WHERE name='Tom' AND age=17

  4.     users = User.query.filter_by(name='Jane', age=17)

效果相当于使用了 WHERE 子句,多个键值对用逗号分割。

  1. "filter()"方法,对查询结果过滤,比"filter_by()"方法更强大,参数是布尔表达式

 
   
   
 
  1.     # WHERE age<20

  2.     users = User.query.filter(User.age<20)

  3.     # WHERE name LIKE 'J%' AND age<20

  4.     users = User.query.filter(User.name.startswith('J'), User.age<20)

多个查询条件用逗号分割。

  1. "first()"方法,取返回列表中的第一个元素,当我们只查询一条记录时非常有用

 
   
   
 
  1.     user = User.query.filter_by(name='Michael').first()

  1. "order_by()"方法,排序

 
   
   
 
  1.     from sqlalchemy import desc

  2.  

  3.     # ORDER BY name

  4.     user = User.query.order_by(User.name)

  5.     # ORDER BY age DESC, name

  6.     user = User.query.order_by(desc(User.age), User.name)

  1. "limit()"和"offset()"方法,分页

 
   
   
 
  1.     # LIMIT 10 OFFSET 10

  2.     user = User.query.limit(10).offset(10)

等同于 mysql 中的 LIMIT 和 OFFSET,上例中我们从第 11 条记录开始取,并最多只取 10 条。

  1. "slice(start, stop)",分页

 
   
   
 
  1.     # LIMIT 2 OFFSET 1

  2.     user = User.query.slice(1, 3)

从 start 位置开始取记录,到 stop 位置前结束。本质上来说,SQLAlchemy 会将其翻译成 LIMIT/OFFSET 语句来实现,上例中的"slice(1, 3)"等同于"LIMIT 2 OFFSET 1″。

更新数据

在添加数据时,我们使用了"add()"方法,其实它一样可以用来更新数据:

 
   
   
 
  1.     user = User.query.filter_by(name='Tom').first()

  2.     if user is not None:

  3.         user.age = 1

  4.         db.session.add(user)

  5.         db.session.commit()

SQLAlchemy 会自动判断,如果对象对应的记录已存在,就更新而不是添加。

SQLAlchemy 还支持批量更新,比如我们要将所有岁数小于 20 的人都加 1 岁:

 
   
   
 
  1.     User.query.filter(User.age<20).update({'age': User.age 1})

  2.     db.session.commit()

更新完后,别忘了提交事务。

删除数据

只需调用"delete()"方法即可,传入的参数是对应数据库中记录的对象。记得同"add()"一样,要调用"commit()"来提交事务:

 
   
   
 
  1.     user = User.query.filter_by(name='Michael').first()

  2.     if user is not None:

  3.         db.session.delete(user)

  4.         db.session.commit()

一对多关系

现在让我们再添加一个模型,成绩单。每个用户对于不同的课程,会有不同的分数,这样用户同成绩单之前就是一对多的关系。怎么在模型类的定义中体现这个一对多关系呢。保持 User 类不变,现在让我们添加一个 Score 类:

 
   
   
 
  1. from datetime import datetime

  2.  

  3. class Score(db.Model):

  4.     id = db.Column(db.Integer, primary_key=True)

  5.     course = db.Column(db.String(50))

  6.     assess_date = db.Column(db.DateTime)

  7.     score = db.Column(db.Float)

  8.     is_pass = db.Column(db.Boolean)

  9.  

  10.     user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

  11.     user = db.relationship('User', backref=db.backref('scores', lazy='dynamic'))

  12.  

  13.     def __init__(self, course, score, user, assess_date=None):

  14.         self.course = course

  15.         self.score = score

  16.         self.is_pass = (score >= 60)

  17.         if assess_date is None:

  18.             assess_date = datetime.now()

  19.         self.assess_date = assess_date

  20.         self.user = user

  21.  

  22.     def __repr__(self):

  23.         return '<Course %r of User %r>' % (self.course, self.user.name)

Score 模型中有这些属性:

  1. "id":整型主键

  2. "course":最大长度为 50 的字符串

  3. "assess_date":日期时间类型

  4. "score":浮点型

  5. "is_pass":布尔型

分别对应数据库"score"表中"id"主键, "course", "accessdate", "score"和"ispass"字段。另外,它还有两个属性:

  1. "user_id":整型外键,对应于"user"表的主键"id"

  2. "user":User 对象

"user_id"字段声明了外键,也就相当于声明了"user"表同"score"表的一对多关系。"user"属性并不是数据表中的字段,它使用了"db.relationship()"方法,使得我们可以通过"Score.user"访问当前 score 记录的 user 对象,它的第一个参数"User"就表明了对应的对象模型是 User。而第二个参数"backref"定义了从 User 模型反向引用 Score 模型的方法,上例中,我们就可以用"User.scores"获取当前 user 对象所有的 score 记录,它是一个列表。"db.backref()"方法的"lazy"参数决定了在 User 对象中什么时候加载其 scores 列表的值,延迟加载可以提高性能,并避免内存的浪费,"lazy"参数的选择可以 参阅这里。

现在查询下"score"表的 schema,你会看到下面的结果:

 
   
   
 
  1. CREATE TABLE score (

  2. id INTEGER NOT NULL,

  3. course VARCHAR(50),

  4. assess_date DATETIME,

  5. score FLOAT,

  6. is_pass BOOLEAN,

  7. user_id INTEGER,

  8. PRIMARY KEY (id),

  9. CHECK (is_pass IN (0, 1)),

  10. FOREIGN KEY(user_id) REFERENCES user (id)

  11. );

让我们添加些 score 记录:

 
   
   
 
  1.     user = User.query.filter_by(name='Tom').first()

  2.     if user is not None:

  3.         db.session.add(Score('Math', 80.5, user))

  4.         db.session.add(Score('Politics', 58, user))

  5.     user = User.query.filter_by(name='Jane').first()

  6.     if user is not None:

  7.         db.session.add(Score('Math', 88, user))

  8.     db.session.commit()


然后试试通过"User.scores"查询某个用户的成绩:

 
   
   
 
  1. def scores(name):

  2.     user = User.query.filter_by(name=name).first()

  3.     if user is not None:

  4.         for score in user.scores:

  5.             print 'Name "%s" course "%s", score is %s' % (name, score.course, score.score)

对于多对多关系,大家可以创建一个单独的关系表,然后每个表同这个关系表都是一对多的关系。或者大家可以参考 官方文档上的例子来实现多对多关系。

更多参考资料

SQLAlchemy 的官方文档 
Flask-SQLAlchemy 的官方文档 
Flask-SQLAlchemy 的源码


题图:pexels,CC0 授权。

点击阅读原文,查看更多 Python 教程和资源。

以上是关于Flask 进阶系列:SQLAlchemy 扩展学习的主要内容,如果未能解决你的问题,请参考以下文章

Flask-WTF进阶和WTForms扩展

那些年我们学Flask-SQLAlchemy,实现数据库操作,分页等功能

Flask框架 之数据库扩展Flask-SQLAlchemy

Flask 插件系列 - Flask-SQLAlchemy

建议收藏Flask系列教程SQLAlchemy数据库

Python flask-sqlalchemy初级解析