目录
- [标准 Flask 项目结构](#标准 Flask 项目结构)
- 代码介绍
- 配置管理
- 定义url
- method请求方法
- 重定向
- Jinja2模版
- 加载静态文件
- python操作mysql驱动
- flask-SQLAIchemy
- flask-SQLAIchemy基本使用
- ORM模型
- 模型迁移
标准 Flask 项目结构
powershell
myflask/
├── app.py # 主程序
├── static/ # 静态文件(css/js/img)
└── templates/ # 网页模板
powershell
# 创建项目文件夹
mkdir myflask
cd myflask
# 创建核心文件
touch app.py
mkdir static templates
# 写入基础代码
echo "from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello Flask 项目!'
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)" > app.py
运行
powershell
# 运行(默认端口5000)
python app.py
# 指定端口
python app.py --port=8000
# 后台运行
nohup python app.py &
访问
powershell
http://127.0.0.1:5000
代码介绍
python
from flask import Flask
# 创建Flask对象,传入__name__
# 作用:确定项目根路径,确定 templates 和 static 路径
app = Flask(__name__)
# 创建一个根路由,当访问 http://127.0.0.1:5000 时会访问这个路由
@app.route('/')
def index():
# 视图函数:view function
return 'Hello Flask 项目!'
# 如果当前文件作为项目入口文件,那么__name__ = __main__
# 打开debug模式,自定义host和port
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
配置管理
通过app.config来配置
python
from flask import Flask
app = Flask(__name__)
app.config["SECRET_KEY"] = "fsagsag"
随着项目功能越来越复杂,配置项也越来越多,如果全部通过app.config,那么代码将非常冗余,这时候我们可以通过配置文件的形式来配置,比如写一个config.py,然后添加一些配置项:
python
# config.py
SECRET_KEY = "XXXX"
SQLALHEMY_DATABASE_URI = "sqlite3://sqlite3.db"```
python
# app.py
from flask import Flask
import config
app = Flask(__name__)
app.config.from_object(config)
定义url
无参url
无参url的定义非常简单,只需要通过@app.route即可定义,比如以下定义一个个人中心的无参url:
python
@app.route("/profile")
def profile():
return "这是个人中心"
有参url
在url上传递参数,有两种形式,我们拿到获取博客详情为例:
- 定义为
/blog/<blog_id>,那么访问的时候就是/blog/123就可以将参数blog_id=123传递给视图函数。这种形式叫做path传参 - 定义为
/blog,那么访问的时候就是/blog/?blog_id=123,也可以将参数blog_id=123传给视图函数。这种形式叫做query string传参
path传参
python
# blog_id为str类型
@app.route("/blog/<blog_id>")
def blog_detail(blog_id):
print(type(blog_id))
return f"获取到的博客id为:{blog_id}"
# 将str类型转为int类型
@app.route("/blog/<int:blog_id_int>")
def blog_detail_int(blog_id_int):
print(type(blog_id_int))
return f"获取到的博客id为:{blog_id_int}"
# 当访问http://127.0.0.1:5000/blog/234时,会优先调用blog_detail_int视图函数
# 当没有写类型转换时,会自上而下优先调用第一个视图函数
除了int外,还有其他类型
string:字符串类型,默认类型int:整形float:浮点类型path:路径。类型string,但是中间即使添加了/,也会将其当成一个整体uuid:UUID类型any:选择其中的任何一个。例如@app.route("/blog/list/<any(python, flask, django):category>"),那么参数category就只能是python,flask和django中的一个,否则就会出现404错误。
query string传参
query string传参在参数定义时与无参数url一样,然后在视图函数中通过request.args.get("参数名")来获取参数。示例代码如下:
python
from flask import Flask, request
# 一个参数
@app.route("/blog")
def blog_detail():
blog_id = request.args.get("blog_id")
print(type(blog_id)) # 默认是str类型
return f"获取到的博客id为:{blog_id}"
# 访问 http://127.0.0.1:5000/blog?blog_id=123
# 多个参数使用&符号连接
@app.route("/blog/list")
def blog_detail_list():
page = request.args.get("page")
size = request.args.get("size")
print(type(page), type(size)) # 默认是str类型
return f"获取到的博客page为:{page},size为:{size}"
# 访问 http://127.0.0.1:5000/blog/list?page=12&size=40
# 设置默认值并传换类型
@app.route("/blog/list")
def blog_detail_list_default():
page = request.args.get("page", 1, type=int)
size = request.args.get("size", 12, type=int)
print(type(page), type(size))
return f"获取到的博客page为:{page},size为:{size}"
# 访问 http://127.0.0.1:5000/blog/list
method请求方法
在定义url的时候,不同的操作应该使用不同的方法。比如从服务器获取数据就是get请求,提交数据到服务器用post请求,修改服务器数据用put请求,从服务器上删除数据用delete请求。示例代码如下:
python
@app.route("/blog/add", methods=['POST'])
def blog_add():
return "使用post方法添加博客"
# 使用postman测试
除了@app.route装饰器,我们还可以使用一些快捷路由来限制请求方法。
| 快捷路由装饰器 | 描述 |
|---|---|
| app.get("/login") | 等价于app.route("/login", methods='GET') |
| app.post("/login") | 等价于app.route("/login", methods='POST') |
| app.put("/login") | 等价于app.route("/login", methods='PUT') |
| app.delete("/login") | 等价于app.route("/login", methods='DELETE') |
| app.patch("/login") | 等价于app.route("/login", methods='PATCH') |
重定向
- 永久重定向,301
- 临时重定向,302
在flask中,重定向通过flask.redirect(location, code=302)函数实现的。location表示需要重定向到哪个uri,code表示状态码,默认是302临时重定向。
python
from flask import Flask, request, redirect
@app.route('/login')
def login():
return "登录页面"
@app.route("/profile")
def profile():
name = request.args.get('name')
if not name:
# 如果没有name,说明没有登录,重定向到登录页面
return redirect('/login')
else:
return name
# 访问 http://127.0.0.1:5000/profile?name=huazi
# 访问 http://127.0.0.1:5000/profile
Jinja2模版
在flask中,渲染html通常会交给模板引擎来做,而flask中默认配套的模板引擎是Jinja2,Jinja2的作者也是Flask的作者,Jinja2可以独立于Flask使用,比如被Django使用。Jinja2是一个高效,可扩展的模板引擎。
在flask项目中,有一个templates文件夹,如果没有修改模板查找路径,默认会在这个文件夹下寻找模板文件。模板文件可以是任意纯文本格式的文件,比如txt,html,xml等,但是为了让项目更规范,也为了与前端开发者更无缝的协作,通常我们使用html文件来写模板代码。
基本使用
python
from flask import Flask, request, redirect, render_template
@app.route('/')
def index():
# 会在项目根路径下的templates下寻找index.html文件
return render_template("index.html")
index.html模板代码如下:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>简易页面</title>
</head>
<body>
<h1>欢迎访问页面</h1>
<p>这是一段基础HTML代码</p>
</body>
</html>
渲染变量
在html文件中,有些数据是需要从数据库中动态加载的,比如博客列表,不可能新增一篇博客就要修改html的代码,正确的做法是每次用户访问这个博客列表url时,在视图函数中就读取最新的博客数据,然后把最新的博客数据传递到模版中。这时候就涉及到在模板中怎么使用变量,比如下面渲染模板的时候,我们就会传入一个hobby参数,示例代码如下:
python
@app.route("/variable")
def variable():
hobby = "游戏"
return render_template("variable.html", hobby=hobby)
variable.html如下:
python
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>变量使用</title>
</head>
<body>
<h1>我的兴趣爱好是:{{ hobby }}</h1>
</body>
</html>
访问:http://127.0.0.1:5000/variable
也可以使用对象.属性 或者 字典.key的语法来访问对应的值。比如下面代码传给模板文件有一个user对象和一个person对象。示例代码如下:
python
class User:
def __init__(self, username, email):
self.username = username
self.email = email
@app.route("/variable")
def variable():
hobby = "游戏"
person = {
"name": "张三",
"age": 18
}
user = User("李四", "xx@qq.com")
context = {
"hobby": hobby,
"person": person,
"user": user
}
# return render_template("variable.html", hobby=hobby, person=person, user=user)
return render_template("variable.html", **context)
控制语句
Jinja2模板中没有continue和break语句,读者需要注意
if语句
模板中,也存在if语句和python中的if语句非常的类似,可以使用>, <, >=, <=, ==, !=来判断,也可以通过and,or,not来进行逻辑操作。
python
@app.route("/if")
def if_statement():
age = 18
return render_template("if.html", age=age)
这里我们定义了一个age变量,并且把这个age传给一个叫做if.html的模板。在if.html模板中,我们根据age的大小,来判断是否成年。if.html的代码如下:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>if语句</title>
</head>
<body>
{% if age > 18 %}
<div>您已经成年了</div>
{% elif age < 18 %}
<div>您未成年</div>
{% else %}
<div>您刚成年</div>
{% endif %}
</body>
</html>
上诉代码中,if结束后必须根一个endif语句,另外在模板中的缩紧并不是必须的,只是为了代码阅读性更强
for语句
Jinja2中的for循环与python中的for循环也是非常类似的,只是比python中的for循环多一个endfor代码块
python
@app.route("/for")
def for_statement():
books = [
{"name": "三国演义", "author": "罗贯中", "price": 100},
{"name": "水浒传", "author": "施耐庵", "price": 99},
{"name": "红楼梦", "author": "曹雪芹", "price": 101},
{"name": "西游记", "author": "吴承恩", "price": 102}
]
return render_template("for.html",books=books)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>for语句</title>
</head>
<body>
<table>
<thead>
<tr>
<th>书名</th>
<th>作者</th>
<th>价格</th>
</tr>
</thead>
<tbody>
{% for book in books %}
<tr>
<td>{{ book.name }}</td>
<td>{{ book.author }}</td>
<td>{{ book.price }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
模版继承
一个网站中,大部分网页的模块是重复的,比如顶部的导航栏,底部的备案信息。如果在每个页面中都重复的去写这些代码,会让项目变得臃肿,提高后期维护成本。
比较好的做法是,通过模板继承,把一些重复性的代码写在父模板中,子模板继承父模板后,再分别实现自己页面的代码。我们首先来看一个父模板base.html的例子。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %} | 华子</title>
{% block head %}{% endblock %}
</head>
<body>
<nav>
<ul>
<li><a href="#">首页</a></li>
<li><a href="#">发布</a></li>
</ul>
</nav>
<main>
{% block main %}{% endblock %}
</main>
<footer>
Copyright 2008 by <a href="#">you</a>
</footer>
</body>
</html>
以上父模板中,编写好网页的整体结构,并且把所有子模板都需要用到的样式文件base.css也提前引用好,然后针对子模板需要重写的地方,则定义成block。比如以上定义的title,head,body,footer这几个block,子模板仔继承了父模板后,重写对应block的代码,即可完成子模板的渲染。这里我们用继承base.html的方式,实现一个list.html文件和detail.html,代码如下:
list.html
html
{% extends "base/base.html" %}
{% block title %}列表页{% endblock %}
{% block head %}
<style>
body{
background: antiquewhite;
}
</style>
{% endblock %}
{% block main %}
<h1>这是华子列表页</h1>
{% endblock %}
detail.html
html
{% extends "base/base.html" %}
{% block title %}详情页{% endblock %}
{% block head %}
<style>
body{
background: cadetblue;
}
</style>
{% endblock %}
{% block main %}
<h1>这是文章详情页</h1>
{% endblock %}
首先,我们通过 extends 语法,加载父模板,因为 base.html 和 list.html、detail.html 是不在同一级目录,所以直接写 "base/base.html"。这里需要注意的是,extends 必须出现在子模板所有代码的最前面。接下来分别实现了 title、head、content 这三个 block,实现的 block 中的代码,将会被自动填充到父模板指定的位置,并且最终会生成一个完整 HTML 结构的 list.html、detail.html 文件。
python
@app.route("/list")
def article_list():
return render_template("base/list.html")
@app.route("/detail")
def article_detail():
return render_template("base/detail.html")
加载静态文件
一个网页中,除了 HTML 代码以外,还需要 CSS、JavaScript 和图片文件才能更加美观和实用。静态文件默认是存放到当前项目的 static 文件夹中。在模板文件中,可以通过 url_for 加载静态文件,示例代码如下:
- static.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<!-- <link rel="stylesheet" href="/static/css/mystatic.css"> -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/mystatic.css') }}">
<title>静态文件加载</title>
</head>
<body>
<h1 class="title">静态文件加载</h1>
</body>
</html>
- mystatic.css
html
.title{
background: red;
}
python
@app.route("/static")
def template_static():
return render_template("static.html")
python操作mysql驱动
Flask 想要操作数据库,必须要先安装 Python 操作 MySQL 的驱动。在 Python 中,目前有以下 MySQL 驱动包。
mysqlclient:是目前为止执行速度最快的驱动,请通过命令pip install mysqlclient安装,而不要在 PyCharm 中安装,否则很容易出错。pymysql:纯 Python 实现的一个驱动。因为是纯 Python 编写的,因此执行效率不如mysqlclient。也正因为是纯 Python 写的,因此可以和 Python 代码无缝衔接。mysql-connector-python:MySQL 官方推出的纯 Python 连接 MySQL 的驱动,执行效率比pymysql还慢。
这里我们用 mysqlclient 作为驱动程序。
shell
pip install mysqlclient
flask-SQLAIchemy
在 Flask 中,我们很少会使用驱动程序(比如 mysqlclient)直接写原生 SQL 语句去操作数据库,更多的是通过 SQLAlchemy 提供的 ORM 技术,类似于操作普通 Python对象一样实现数据库的增删改查操作,而 Flask-SQLAlchemy 是对 SQLAlchemy 的一个封装,使得在 Flask 中使用 SQLAlchemy 更加方便。 Flask-SQLAlchemy 是需要单独安装,因为 Flask-SQLAlchemy 依赖 SQLAlchemy,所以只要安装了 Flask-SQLAlchemy, SQLAlchemy 会自动安装。安装命令如下。
shell
pip install flask-sqlalchemy
SQLAlchemy 类似于 Jinja2,是可以独立于 Flask 而被使用的,完全可以在任何 Python 程序中被使用。SQLAlchemy 的功能非常强大,本课件不可能全部都讲到。读者如果有兴趣,可以在学完本章后阅读 SQLAlchemy 的官方文档,链接为:https://www.sqlalchemy.org/
flask-SQLAIchemy基本使用
连接mysql
使用 Flask-SQLAlchemy 操作数据库之前,要先创建一个由 Flask-SQLAlchemy 提供的 SQLAlchemy 类的对象。在创建这个类的时候,要传入当前的 app。然后还需要在 app.config 中设置 SQLALCHEMY_DATABASE_URI,来配置数据库的连接,示例代码如下。
python
from flask import Flask, request, redirect, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
app = Flask(__name__)
HOSTNAME = "127.0.0.1"
PORT = 3306
USERNAME = "root"
PASSWORD = "123456"
DATABASE = "db2"
# 如果驱动程序是mysqlclient,那么以下前缀就是:mysql+mysqldb
# 如果驱动程序是pymysql,那么以下前缀就是:mysql+pymysql
app.config['SQLALCHEMY_DATABASE_URI'] = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
with app.app_context():
# 测试是否连接成功
with db.engine.connect() as conn:
rs = conn.execute(text("select 1"))
print("测试结果:", rs.fetchone()[0])。 # 测试结果为1表示连接成功
ORM模型
ORM介绍
ORM(Object Relationship Mapping),对象关系映射,是一种可以用Python面向对象的方式来操作关系型数据库的技术。具有可以映射到数据库表能力的Python类我们称之为ORM模型,一个ORM模型与数据库中一个表相对应,ORM模型中的每个类属性分别对应表的每个字段,ORM模型的每个实例对象对应表中每条记录。ORM技术提供了面向对象与SQL交互的桥梁,让开发者用面向对象的方式操作数据库,使用ORM模型具有以下优势。
- 开发效率高:几乎不需要写原生SQL语句,使用纯Python的方式操作数据库,大大的提高了开发效率。
- 安全性高:ORM模型底层代码对一些常见的安全问题,比如SQL注入做了防护,比直接使用SQL语句更加安全。
- 灵活性强:Flask-SQLAlchemy底层支持SQLite、MySQL、Oracle、PostgreSQL等关系型数据库,但针对不同的数据库,ORM模型代码几乎一模一样,只需修改少量代码,即可完成底层数据库的更换。
定义ORM模型
以下我们用Flask-SQLALchemy来创建一个User模型,示例代码如下:
python
from flask import Flask, request, redirect, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import config
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import MetaData, Integer, String
app = Flask(__name__)
app.config.from_object(config)
# 定义命名约束的Base类
class Base(DeclarativeBase):
metadata = MetaData(naming_convention={
# ix: index,索引。
"ix": 'ix_%(column_0_label)s',
# un: unique,唯一约束
"uq": "uq_%(table_name)s_%(column_0_name)s",
# ck: Check,检查约束
"ck": "ck_%(table_name)s_%(constraint_name)s",
# fk: Foreign Key,外键约束
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
# pk: Primary Key,主键约束
"pk": "pk_%(table_name)s"
})
db = SQLAlchemy(app=app, model_class=Base)
class User(db.Model):
__tablename__ = "user"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
username: Mapped[str] = mapped_column(String(50), nullable=False)
password: Mapped[str] = mapped_column(String(200))
with app.app_context():
db.create_all()
python
HOSTNAME = "127.0.0.1"
PORT = 3306
USERNAME = "root"
PASSWORD = "123456"
DATABASE = "db2"
# 如果驱动程序是mysqlclient,那么以下前缀就是:mysql+mysqldb
# 如果驱动程序是pymysql,那么以下前缀就是:mysql+pymysql
SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8mb4"
SQLALCHEMY_TRACK_MODIFICATIONS = False
上述代码中,字典 naming_convention 是一个固定的写法,主要是用来给表约束做一些命名约定的,使得后期 alembic 在生成迁移脚本时,生成的约束名不是随机的,而是有命名规范的。
在 User 模型中,我们使用 Mapped[类型名] 语法来让开发者和 IDE 识别该属性的类型,为开发提供便捷性;另外,凡是使用了 mapped_column 创建的属性都将被映射到表中成为字段,并且该字段有什么配置,都可以在 mapped_column 中添加相关参数,比如字段类型(Integer)、是否可以为空(String(50))、是否为主键(primary_key=True)、是否自增(autoincrement=True)等。
之后,我们调用 db.create_all() 方法,就会将该模型迁移到数据库中生成一张表,这种方式在字段发生改变时无法自动同步到数据库中,所以仅作为一种临时解决方案,后续会使用 flask-migrate 来实现迁移。
常用字段类型和mapped_column参数
字段类型
上述 User 模型中,我们使用了 db.Integer 和 db.String,除了这两种数据类型外,还有以下数据类型:
| 类型 | 描述 |
|---|---|
| Integer | 整形。范围与数据库一致。 |
| SmallInteger | 小整形。范围与数据库一致。 |
| BigInteger | 长整形。范围与数据库一致。 |
| Decimal | 定点类型。可以指定总长度和小数点后位数。 |
| Boolean | 布尔类型。 |
| Date | 日期类型。存储Python中的datetime.date对象。 |
| DateTime | 日期时间类型。存储Python中的datetime.datetime对象。 |
| Time | 时间类型。存储Python中的datetime.time对象。 |
| Interval | 时间间隔。存储Python中的datetime.timedelta对象。 |
| String | 字符串类型。使用时需要指定长度。 |
| Text | 文本类型。常用于字符串长度不可控的情况。 |
| Enum | 枚举类型。 |
| PickleType | 存储经过Pickle后的对象。 |
| LargeBinary | 存储二进制数据。 |
| JSON | 存储JSON类型的数据。 |
还有很多其他的数据类型,读者可以在PyCharm中导入 from sqlalchemy import String,然后按 Ctrl+鼠标左键 进入项目源代码中查看到更多的类型。
mapped_column参数
另外,mapped_column 方法有以下常用参数:
| 参数 | 描述 |
|---|---|
| name | 字段在数据库表中的名称。如果没有设置,则使用此属性名作为字段名称。 |
| type_ | 字段类型。 |
| autoincrement | 自动增长。 |
| default | 默认值。 |
| index | 如果设置为True,则将此字段设置为索引。 |
| nullable | 是否为空。 |
| onupdate | 在修改对象的时候,会自动使用这个属性指定的值。 |
| primary_key | 主键。 |
| unique | 如果设置为True,则此字段的值必须唯一。 |
| comment | 在创建表时的注释。 |
模型迁移
Flask-Migrate介绍
到目前为止,我们定义好模型后,是通过 db.create_all() 的形式将 ORM 模型映射到数据库中的,这种方式非常局限,他只能识别到新增了模型后映射到数据库中,对于模型中字段的修改,类型的修改,他无法识别到。因此在实际开发中,都不会使用 db.create_all 来做 ORM 模型迁移,而是借助到一个第三方插件 Flask-Migrate 来实现。Flask-Migrate 是基于 alembic 实现的,alembic 是专门用来做给 SQLAlchemy 的 ORM 模型做迁移的。要使用 Flask-Migrate,我们首先需要通过 pip 安装。
bash
pip install flask-migrate
alembic会随着flask-migrate安装而安装。在完成flask-migrate安装后,来讲解一下如何使用。
1、创建迁移对象
使用 Flask-Migrate 实现模型迁移,需要通过类 Migrate 来实现。示例代码
python
from flask import Flask, request, redirect, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import config
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import MetaData, Integer, String
from flask_migrate import Migrate
app = Flask(__name__)
app.config.from_object(config)
........
db = SQLAlchemy(app=app, model_class=Base)
migrate = Migrate(app, db)
上述代码中,我们使用了 flask_migrate 中的 Migrate 类,使用该类创建对象时传入了 app 和 db
2、初始化迁移环境
在创建完迁移对象后,我们需要初始化一下迁移环境。方法是在当前项目的根路径下执行命令创建迁移环境。