SQLAlchemy 是 Python 下非常好的 ORM 框架,支持使用 MySQL、PostgreSQL、SQLite 等主流数据库。Flask-SQLAlchemy 基于 SQLAlchemy 对 Flask 进行了适配,使其在 Flask 下的使用变得简单。
在建立模型之前,需要对客户需求进行分析。
假设现在需要实现一个简单的文章发布系统,用户可以在其中发布文章,管理员用户可管理(增、删、改、查)所有文章。此处可以将以上需求分解为“用户模块”与“文章模块”,然后为各个模板设计相应的模型。
如果需要实现基本的登录功能,则用户模型需要拥有“用户名”“密码”这些属性。用户还可以作为管理员,为了区分普通用户和管理员,用户模型还需要拥有“是否为管理员”属性。另外,如果需要创建文章,便需要使用“id”属性将用户模型与文章模型进行关联。
这里使用的是 SQLite 数据库,可视化工具为 sqlitestudio。
表 1 用户模型结构
模型属性 | Python 中数据类型 | 数据表中字段类型 | 说明 |
---|---|---|---|
id | int | INTEGER | 用于指向用户的唯一编号 |
username | str | VARCHAR | 用户名 |
password | str | VARCHAR | 密码 |
is_admin | bool | BOOLEAN | 是否为管理员 |
表2 文章模型结构
模型属性 | Python 中数据类型 | 数据表中字段类型 | 说明 |
---|---|---|---|
id | int | INTEGER | 用于指向文章的唯一编号 |
title | str | VARCHAR | 文章标题 |
content | str | VARCHAR | 文章正文内容 |
初始化 Flask-SQLAlchemy 并且定义用户模型和文章模型
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os
from flask import Flask, render_template
from flask_script import Manager, Shell
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 设置数据库存储位置
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLCLCHEMY_TRACK_MODIFICATIONS'] = False
# flask_sqlalchemy 初始化代码
db = SQLAlchemy()
db.init_app(app)
manager = Manager(app)
@app.route('/', methods=['post'])
def login():
return "Hello World!"
# 定义用户模型
class UserModel(db.Model):
__tablename__ = 'user'
id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)
username = db.Column(db.VARCHAR, unique=True)
password = db.Column(db.VARCHAR)
is_admin = db.Column(db.BOOLEAN, default=False, nullable=False)
# 定义文章模型
class ArticleModel(db.Model):
__tablename__ = 'article'
id = db.Column(db.INTEGER, primary_key=True)
title = db.Column(db.VARCHAR)
content = db.Column(db.VARCHAR)
# 交互式 shell
def make_shell_context():
return dict(app=app, db=db, UserModel=UserModel, ArticleModel=ArticleModel)
manager.add_command('shell', Shell(make_context=make_shell_context))
if __name__ == "__main__":
manager.run()
输入命令 python app.py shell
进入交互界面,用命令 db.create_all()
创建数据库 data.sqlite
。
通常情况下数据库的操作主要通过 SQL 语句进行,而 SQLAlchemy 提供了将数据映射到对象中的操作方式。这样的方式使得对数据的操作变得简单,使用者无须学习 SQL 语法即可完成数据操作。
增加数据(交互式 shell)
# 创建用户对象
In [1]: user_1 = UserModel(
...: username='admin',
...: password='123456',
...: is_admin=True)
In [2]: user_2 = UserModel(
...: username='user',
...: password='123456',
...: is_admin=False)
# 将用户对象添加到数据操作队列
In [3]: db.session.add(user_1)
In [4]: db.session.add(user_2)
In [5]: db.session.commit()
如果需要在 Python 中获取相应的用户数据,则需要通过模型类查询来实现。如果要实现用户登录,便只需要将用户输入数据与数据库中查询到的用户数据进行比较,正确则登录成功,反之登录失败。
模型类查询代码(交互式 shell)
# 获取所有用户对象
In [1]: users = UserModel.query.all()
In [2]: print(users)
[<UserModel 1>, <UserModel 2>]
# 获取所有符合条件的用户对象
In [3]: users = UserModel.query.filter_by(is_admin=True).all()
In [4]: print(users)
[<UserModel 1>]
# 获取所有符合条件的用户对象的数据
In [5]: num = UserModel.query.filter_by(is_admin=True).count()
In [6]: print(num)
1
# 获取一个符合条件的用户对象
In [7]: user = UserModel.query.filter(UserModel.id > 1).first()
In [8]: print(user)
<UserModel 2>
In [1]: user = UserModel.query.get(2)
In [2]: user.password = 'admin'
In [3]: user.is_admin = True
In [4]: db.session.add(user)
In [5]: db.session.commit()
删除数据的操作是建立在查询数据基础之上的,在获取到模型对象后,将模型对象加入数据操作队列,执行提交即可进行改动。不同之处在于,删除时使用 delete()
方法进行操作。
In [10]: user1 = UserModel.query.get(1)
In [11]: user2 = UserModel.query.get(2)
In [12]: db.session.delete(user1)
In [13]: db.session.delete(user2)
In [14]: db.session.commit()
交互式 shell
# 快速添加5 个测试用的文章模型
for i in range(5):
article = ArticleModel(title=' 文章_%d' % i, content='This is article_%d.' % i)
db.session.add(article)
article = ArticleModel(title=' 测试文章1', content='Hello World!')
db.session.add(article)
article = ArticleModel(title=' 测试文章2', content='Hello Flask!')
db.session.add(article)
db.session.commit()
# 从第 3 个对象(索引从 0 开始)开始,获取所有数据
articles = ArticleModel.query.offset(2).all()
print(articles)
# 获取前 2 个对象
articles = ArticleModel.query.limit(2).all()
print(articles)
# 从第 2 个对象开始,获取 3 个对象
articles = ArticleModel.query.offset(1).limit(3).all()
print(articles)
# 获取所有文章对象,并根据 id 对所有文章进行降序排列
articles = ArticleModel.query.order_by(ArticleModel.id.desc()).all()
print(articles)
# 获取所有文章对象,并根据 id 对所有文章进行升序排列(默认)
articles = ArticleModel.query.order_by(ArticleModel.id.asc()).all()
print(articles)
# 获取文章内容中包含“Hello ”的所有文章
articles = ArticleModel.query.filter(ArticleModel.content.contains('Hello')).all()
print(articles)
# 引入 and_ 、or_ 操作
from sqlalchemy import and_, or_
# 获取文章内容中包含“article”,或文章标题中包含“测试”的所有文章
articles = ArticleModel.query.filter(or_(
ArticleModel.content.contains('article'),
ArticleModel.title.contains(' 测试'),
)).all()
print(articles)
# 获取id > 3 ,且文章内容中包含“article”的所有文章
articles = ArticleModel.query.filter(and_(
ArticleModel.id > 3,
ArticleModel.content.contains('article'),
)).all()
print(articles)
在网站使用流程中,文章由用户创建,即每一篇文章都会对应一个作者;可以根据文章找到对应的用户,也可以根据特定的用户查询到与之对应的文章。 要实现上述效果,仅需为这两个模型建立外键关联。 在文章模型代码中添加外键(作者id),以及反向关联属性(在用户模型中定义)。
# 定义用户模型
class UserModel(db.Model):
__tablename__ = 'user'
id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)
username = db.Column(db.VARCHAR, unique=True)
password = db.Column(db.VARCHAR)
is_admin = db.Column(db.BOOLEAN, default=False, nullable=False)
# 建立一对多映射关系(uselist=True)
# 为了便于IDE 识别变量类型,此处声明articles 类型为list
articles: list = db.relationship('ArticleModel', backref='author', uselist=True)
# 定义文章模型
class ArticleModel(db.Model):
__tablename__ = 'article'
id = db.Column(db.INTEGER, primary_key=True)
title = db.Column(db.VARCHAR)
content = db.Column(db.VARCHAR)
# 根据用户 id 与文章模型进行外键关联
# author_id 不作为主键,为了保证效率,需要为字段建立索引(index=True)
author_id = db.Column(db.INTEGER, db.ForeignKey('user.id'), index=True)
# 声明 author 属性类型,以便 IDE 进行识别
author: UserModel
数据分页显示是一个很常见的功能。例如之前所设计的文章模型,当文章数量达到一定程度(例如10篇)时,便不再适合在单个页面中展示,这时便需要用到数据分页显示功能,使每一页加载部分文章数据。 当然,像这种常见需求,Flask-SQLAlchemy与Flask-Bootstrap都已经实现好了,只要引入相应功能并使用即可。
@app.route('/paginator')
def paginator():
# 每页显示 3 篇文章
pagination = ArticleModel.query.paginate(per_page=3)
return render_template('paginator1.html', pagination=pagination)
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据分页显示title>
{% import 'pagination.html' as pagi %}
head>
<body>
<div class="container">
{% for item in pagination.items %}
<h1>{{ item.title }}h1>
<p>{{ item.content | safe }}p>
{% endfor %}
{# 根据pagination(分页)对象渲染分页按钮 #}
{{ pagi.render_pagination(pagination) }}
div>
body>
html>