Flask开发框架入门教程(1)

Flask框架是什么

Flask是一个使用Python编写的轻量级Web应用框架。其特点如下:

  1. 轻量级:Flask框架的核心构成简单,但具有很强的扩展性和兼容性。由于其“微”框架的特性,开发者可以使用Python语言快速实现一个网站或Web服务。
  2. WSGI工具箱和模板引擎:Flask基于Werkzeug WSGI工具箱和Jinja2模板引擎,分别集成自这两个模块包,是Flask框架的核心。
  3. 扩展性:Flask虽然本身功能较少,但提供了很好的扩展机制,各类需求基本都有对应的官方或第三方扩展可以实现,甚至可以自己动手实现。例如,ORM、窗体验证工具、文件上传、各种开放式身份验证技术等,都可以通过Flask的扩展来实现。
  4. 灵活性:Flask是一个轻量级的可定制框架,允许使用Python语言编写代码,相较于其他同类型框架更为灵活、轻便、安全且容易上手。
  5. 授权方式:Flask使用BSD授权。

总的来说,Flask是一个功能强大、灵活且易于使用的Web应用框架,特别适合小型到中型的应用程序开发。如需更多信息,建议访问信息技术论坛或请教编程专家。

前置条件

准备好python环境

安装flask

pip install flask

Flask 是典型的微框架,作为 Web 框架来说,它仅保留了核心功能:请求响应处理模板渲染。这两类功能分别由 Werkzeug(WSGI 工具库)完成和 Jinja(模板渲染库)完成,因为 Flask 包装了这两个依赖 

示例程序

from flask import Flask
app=Flask(__name__)
@app.route('/')
def hello_world():
    return 'Hello world!'
if __name__ == '__main__':
    app.run()

Flask 会使用内置的开发服务器来运行程序。这个服务器默认监听本地机的 5000 端口 

内置的开发服务器只能用于开发时使用,部署上线的时候要换用性能更好的生产服务器

运行后,访问http://127.0.0.1:5000 

 如上一个完整的Web服务了。示例中的第一行代码用于引入Flask类并在随后对其进行了实例化;之后通过该实例的route装饰器来为Flask服务绑定路由和处理函数;处理函数中返回的是给客户端的具体信息;最后通过Flask实例的run方法启动Web服务。

整个请求的处理过程如下所示:

  1. 当用户在浏览器地址栏访问这个地址,在这里即 http://localhost:5000/
  2. 服务器解析请求,发现请求 URL 匹配的 URL 规则是 /,因此调用对应的处理函数 hello_world()
  3. 获取 hello_world() 函数的返回值,处理后返回给客户端(浏览器)
  4. 浏览器接受响应,将其显示在窗口上

提示 在 Web 程序的语境下,虽然客户端可能有多种类型,但在本书里通常是指浏览器。

视图函数

上面例子中hello_world()函数是视图函数,它的返回是一个文本。

这里的返回也可以是html页面

 return '<h1>Hello Totoro!</h1><img src="http://helloflask.com/totoro.gif">'

 

开启调试模式

设置debug为True,这样在修改程序后,程序会自动重启。

app.run(debug=True)

 

路由绑定

Flask中路由绑定是通过route装饰器实现的,该装饰器所实现的功能就是把指定的URL和被装饰的函数进行绑定,当用户访问该URL时就会自动触发对应的绑定函数。

默认情况下,route装饰器只需要接收一个URL参数,此时绑定的URL只能处理HTTP的GET请求

如果想要增加对HTTP请求方法的支持,就需要在调用route装饰器时显式地设置methods参数。

@app.route('/', methods=['GET', 'POST'])
def hello_world():
    return 'Hello, World!'

绑定多个路由

一个视图函数,可以绑定多个路由

@app.route('/')
@app.route('/index')
@app.route('/home')
def hello():
    return 'Welcome to My Watchlist!'

动态路由

动态变量

另外一些场景下,可能希望URL的内容是可变的,且希望请求处理函数根据URL中变化的内容返回不同的响应结果,此时就需要用到Flask的动态路由配置功能,在路由中使用动态变量

@app.route('/user/<name>')
def hello(name):
    return f'Hello {name}'
if __name__ == '__main__':
    app.run(debug=True)

 访问127.0.0.1:5000/user/ddbb

 

变量预处理

Flask 支持在 URL 规则字符串里对变量设置处理器,对变量进行预处理。比如 /user/<int:number> 会将 URL 中的 number 部分转换成整型。 

@app.route('/hello/<string:name>')
def hello_world(name):
    return f"hello {name}"

用不同的访问参数,会输出不同的结果

> curl http://127.0.0.1:5000/hello/python
hello python
> curl http://127.0.0.1:5000/hello/world
hello world
> curl http://127.0.0.1:5000/hello/
URL Not Found
> curl http://127.0.0.1:5000/hello/python/test
URL Not Found

 模板渲染

包含变量和运算逻辑的 HTML 或其他格式的文本叫做模板执行这些变量替换和逻辑计算工作的过程被称为渲染,这个工作由模板渲染引擎——Jinja2 来完成。

按照默认的设置,Flask 会从程序实例所在模块同级目录的 templates 文件夹中寻找模板,我们的程序目前存储在项目根目录的 app.py 文件里,所以我们要在项目根目录创建这个文件夹

模板文件中包括了静态的HTML内容和模板语法。其中静态的HTML内容不会被改变,而模板语法则会根据上下文内容被转换为相应的HTML内容,即模板语法部分的内容是可以控制的动态内容,因此在功能上实现了动态HTML内容的效果。

模板基本语法

在社交网站上,每个人都有一个主页,借助 Jinja2 就可以写出一个通用的模板:

<h1>{{ username }}的个人主页</h1>
{% if bio %}
    <p>{{ bio }}</p>  {# 这里的缩进只是为了可读性,不是必须的 #}
{% else %}
    <p>自我介绍为空。</p>
{% endif %}  {# 大部分 Jinja 语句都需要声明关闭 #}

Jinja2 的语法和 Python 大致相同,你在后面会陆续接触到一些常见的用法。在模板里,你需要添加特定的定界符将 Jinja2 语句和变量标记出来,下面是三种常用的定界符:

  • {{ ... }} 用来标记变量。
  • {% ... %} 用来标记语句,比如 if 语句,for 语句等。
  • {# ... #} 用来写注释。

假设有一个名为test.html的模板文件,其内容如下:

<h1>Hello {{ name }}</h1>

该模板文件中动态的模板语法为{{ name }},表示这个占位符将会替换为name变量的值。与此同时,还需要在Flask启动文件的同一目录中新建一个templates目录,并将test.html保存在该目录下。Flask启动文件的内容如下:

from flask import Flask, render_template
app =Flask(__name__)
@app.route('/hello/<name>')
def hello(name):
    return render_template('test.html', name=name)

在 templates 目录下创建一个 index.html 文件,作为主页模板。主页需要显示电影条目列表和个人信息 

 示例中引入了flask.render_template函数,它是Flask对Jinja2模板引擎的封装函数。调用该函数并指定模板文件名和所需变量作为参数,就可以自动渲染出动态的响应内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{{ name }}'s Watchlist</title>
</head>
<body>
    <h2>{{ name }}'s Watchlist</h2>
    {# 使用 length 过滤器获取 movies 变量的长度 #}
    <p>{{ movies|length }} Titles</p>
    <ul>
        {% for movie in movies %}  {# 迭代 movies 变量 #}
        <li>{{ movie.title }} - {{ movie.year }}</li>  {# 等同于 movie['title'] #}
        {% endfor %}  {# 使用 endfor 标签结束 for 语句 #}
    </ul>
    <footer>
        <small>&copy; 2018 <a href="http://helloflask.com/book/3">HelloFlask</a></small>
    </footer>
</body>
</html>

为了方便对变量进行处理,Jinja2 提供了一些过滤器,语法形式如下:

{{ 变量|过滤器 }}

左侧是变量,右侧是过滤器名。比如,上面的模板里使用 length 过滤器来获取 movies 的长度,类似 Python 里的 len() 函数。

提示 访问 Template Designer Documentation — Jinja Documentation (3.0.x) 查看所有可用的过滤器。

为了模拟页面渲染,我们需要先创建一些虚拟数据,用来填充页面内容:

app.py:定义虚拟数据

name = 'Grey Li'
movies = [
    {'title': 'My Neighbor Totoro', 'year': '1988'},
    {'title': 'Dead Poets Society', 'year': '1989'},
    {'title': 'A Perfect World', 'year': '1993'},
    {'title': 'Leon', 'year': '1994'},
    {'title': 'Mahjong', 'year': '1996'},
    {'title': 'Swallowtail Butterfly', 'year': '1996'},
    {'title': 'King of Comedy', 'year': '1999'},
    {'title': 'Devils on the Doorstep', 'year': '1999'},
    {'title': 'WALL-E', 'year': '2008'},
    {'title': 'The Pork of Music', 'year': '2012'},
]

from flask import Flask, render_template

# ...

@app.route('/')
def index():
    return render_template('index.html', name=name, movies=movies)

将数据传给模板index.html,调用render_template函数渲染。 

在传入 render_template() 函数的关键字参数中,左边的 movies 是模板中使用的变量名称,右边的 movies 则是该变量指向的实际对象。这里传入模板的 name 是字符串,movies 是列表,但能够在模板里使用的不只这两种 Python 数据结构,你也可以传入元组、字典、函数等。

render_template() 函数在调用时会识别并执行 index.html 里所有的 Jinja2 语句,返回渲染好的模板内容。在返回的页面中,变量会被替换为实际的值(包括定界符),语句(及定界符)则会在执行后被移除(注释也会一并移除)。

访问,可以看到数据已被渲染至模板。

最后,如果想改变渲染模板场景下的状态码和相关的头信息,则可以通过显式创建一个Flask响应对象来实现。

from flask import Flask, render_template, make_response
app = Flask(__name__)
@app.route('/hello/<name>')
def hello(name):
    rep = make_response(render_template('test.html', name=name))
    rep.status_code = 201
    rep.headers['Content-Type'] = 'text/plain'
    return rep

请求处理

求处理函数进行业务开发。请求函数的处理通常分为以下三大步骤。

(1)请求参数的解析。

(2)业务逻辑的处理。

(3)处理结果的返回。首先,在请求参数的解析方面,

Flask提供了多种获取接口来对应不同的请求方法和数据格式 

对于GET请求的处理函数,其只能通过request.args属性来获取HTTP客户端传递的请求参数数据。 

请求参数的解析

获取get请求参数

from flask import Flask,request
app=Flask(__name__)
@app.route('/',methods=['GET'])
def args_example():
    get_args=request.args
    name=get_args.get('name')
    return name

if __name__ == '__main__':
    app.run(debug=True)

这里不能直接浏览器访问,可以用postman,返回name参数的值。 

 获取json数据

@app.route('/json',methods=['POST'])
def json_example():
    json_data=request.json #获取json数据
    name=json_data.get('name')
    return name

 

 获取文件参数

@app.route('/file',methods=['POST'])
def file_example():
    files=request.files #获取文件参数对象
    upload_file=files.get('upload')
    upload_file.save('new_test.txt')#保存文件到指定用户名

    return 'success'

 在业务处理完成之后,请求处理函数最终还是需要返回一个结果给HTTP客户端,以表示当次请求服务器端已经处理结束,并在响应中返回处理的具体结果。Flask中提供了多种类型的响应内容返回方式,具体如下。

请求结果的返回

 返回纯文本内容

@app.route('/', methods=['GET'])
def hello_world():
    return "hello world!"

该方式返回的响应状态码默认为200,响应内容的类型为text/html。当然也可以在返回内容时指定状态码和响应内容的类型。例如下面示例将会返回一个状态码为201、类型为text/plain的“Hello World!”响应内容: 

@app.route('/', methods=['GET'])
def hello_world():
    return "Hello World!", 201, {"Content-Type": "text/plain"}

返回JSON内容

如果想要返回JSON类型的响应内容,可以通过flask.jsonify函数来实现。它会自动把支持JSON序列化的数据类型转换为JSON字符串,并设置响应内容的类型为application/json

from flask import Flask, jsonify
app =Flask(__name__)
@app.route('/', methods=['GET'])
def hello_world():
    return jsonify({"name": "python"})

返回HTML内容

返回HTML内容则是Flask支持的最完整的功能。最简单的方式与返回纯文本一样,只要直接返回HTML内容即可

@app.route('/', methods=['GET'])
def hello_world():
    return "<h1>Hello World!</h1>"

如果HTML内容过长,在代码中直接展示出来不够易读,那么可以把HTML内容存放在单独的文件中,再通过open函数读取内容并返回

@app.route('/', methods=['GET'])
def hello_world():
    return open('test.html', 'r', encoding='utf-8').read()

静态文件

Flask中除了可以返回HTML等文本内容之外,内置的Web服务器还支持静态文件的访问。

静态文件(static files)和我们的模板概念相反,指的是内容不需要动态生成的文件。比如图片、CSS 文件和 JavaScript 脚本等。

方法为在Flask启动文件的同一目录下创建一个static文件夹,并把静态文件存放在该文件夹下。假设Flask项目的目录结构如下

 如果要访问示例中的test.png和test2.png,则具体的访问地址分别为:

http:/127.0.0.1:5000/static/test.png
http:/127.0.0.1:5000/static/foo/test2.png

任意类型的文件都可以作为静态资源,只要存放在static目录下,就可以通过上述URL路径规则进行访问。

在 Flask 中,我们需要创建一个 static 文件夹来保存静态文件,它应该和程序模块、templates 文件夹在同一目录层级,所以我们在项目根目录创建它:

$ mkdir static

生成静态文件 URL

在 HTML 文件里,引入这些静态文件需要给出资源所在的 URL。为了更加灵活,这些文件的 URL 可以通过 Flask 提供的 url_for() 函数来生成。

在第 2 章的最后,我们学习过 url_for() 函数的用法,传入端点值(视图函数的名称)和参数,它会返回对应的 URL。对于静态文件,需要传入的端点值是 static,同时使用 filename 参数来传入相对于 static 文件夹的文件路径。

假如我们在 static 文件夹的根目录下面放了一个 foo.jpg 文件,下面的调用可以获取它的 URL:

<img src="{{ url_for('static', filename='foo.jpg') }}">

花括号部分的调用会返回 /static/foo.jpg

提示 在 Python 脚本里,url_for() 函数需要从 flask 包中导入,而在模板中则可以直接使用,因为 Flask 把一些常用的函数和对象添加到了模板上下文(环境)里。

访问就可以看到图片

添加 Favicon

Favicon(favourite icon) 是显示在标签页和书签栏的网站头像。你需要准备一个 ICO、PNG 或 GIF 格式的图片,大小一般为 16×16、32×32、48×48 或 64×64 像素。把这个图片放到 static 目录下,然后像下面这样在 HTML 模板里引入它:

templates/index.html:引入 Favicon

<head>
    ...
    <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>

保存后刷新页面,即可在浏览器标签页上看到这个图片。

添加CSS

在 static 目录下创建一个 CSS 文件 style.css,index.html中引入css

为对应的元素设置 class 属性值,以便和对应的 CSS 定义关联起来

<h2>
    <img alt="Avatar" class="avatar" src="{{ url_for('static', filename='images/avatar.png') }}">
    {{ name }}'s Watchlist
</h2>
...
<ul class="movie-list">
    ...
</ul>
<img alt="Walking Totoro" class="totoro" src="{{ url_for('static', filename='images/totoro.gif') }}">

进阶提示

  • 如果你对 CSS 很头疼,可以借助前端框架来完善页面样式,比如 BootstrapSemantic-UIFoundation 等。它们提供了大量的 CSS 定义和动态效果,使用起来非常简单。
  • 扩展 Bootstrap-Flask 可以简化在 Flask 项目里使用 Bootstrap 的步骤。

大部分程序都需要保存数据,所以不可避免要使用数据库。用来操作数据库的数据库管理系统(DBMS)有很多选择,对于不同类型的程序,不同的使用场景,都会有不同的选择。在这个教程中,我们选择了属于关系型数据库管理系统(RDBMS)的 SQLite,它基于文件,不需要单独启动数据库服务器,适合在开发时使用,或是在数据库操作简单、访问量低的程序中使用。

数据库

使用 SQLAlchemy 操作数据库

为了简化数据库操作,我们将使用 SQLAlchemy——一个 Python 数据库工具(ORM,即对象关系映射)。借助 SQLAlchemy,你可以通过定义 Python 类来表示数据库里的一张表(类属性表示表中的字段 / 列),通过对这个类进行各种操作来代替写 SQL 语句。这个类我们称之为模型类,类中的属性我们将称之为字段。

Flask 有大量的第三方扩展,这些扩展可以简化和第三方库的集成工作。我们下面将使用一个叫做 Flask-SQLAlchemy 的扩展来集成 SQLAlchemy。

首先安装它:

(env) $ pip install flask-sqlalchemy==2.5.1 sqlalchemy==1.4.47

提示 Flask-SQLAlchemy 3.x / SQLAlchemy 2.x 版本有一些大的变化,这里分别固定安装 2.5.1 和 1.4.47 版本。后续教程改写后会删除这里的版本限制。

大部分扩展都需要执行一个“初始化”操作。你需要导入扩展类,实例化并传入 Flask 程序实例:

from flask_sqlalchemy import SQLAlchemy  # 导入扩展类

app = Flask(__name__)

db = SQLAlchemy(app)  # 初始化扩展,传入程序实例 app

设置数据库 URI

为了设置 Flask、扩展或是我们程序本身的一些行为,我们需要设置和定义一些配置变量。Flask 提供了一个统一的接口来写入和获取这些配置变量:Flask.config 字典。配置变量的名称必须使用大写,写入配置的语句一般会放到扩展类实例化语句之前。

下面写入了一个 SQLALCHEMY_DATABASE_URI 变量来告诉 SQLAlchemy 数据库连接地址:

import os

# ...

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:' + os.path.join(app.root_path, 'data.db')

注意 这个配置变量的最后一个单词是 URI,而不是 URL。

对于这个变量值,不同的 DBMS 有不同的格式,对于 SQLite 来说,这个值的格式如下:

sqlite:数据库文件的绝对地址

数据库文件一般放到项目根目录即可,app.root_path 返回程序实例所在模块的路径(目前来说,即项目根目录),我们使用它来构建文件路径。数据库文件的名称和后缀你可以自由定义,一般会使用 .db、.sqlite 和 .sqlite3 作为后缀。

另外,如果你使用 Windows 系统,上面的 URI 前缀部分只需要写入三个斜线(即 sqlite:///)。在本书的示例程序代码里,做了一些兼容性处理,另外还新设置了一个配置变量,实际的代码如下:

app.py:数据库配置

import os
import sys

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

WIN = sys.platform.startswith('win')
if WIN:  # 如果是 Windows 系统,使用三个斜线
    prefix = 'sqlite:///'
else:  # 否则使用四个斜线
    prefix = 'sqlite:'

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(app.root_path, 'data.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # 关闭对模型修改的监控
# 在扩展类实例化前加载配置
db = SQLAlchemy(app)

如果你固定在某一个操作系统上进行开发,部署时也使用相同的操作系统,那么可以不用这么做,直接根据你的需要写出前缀即可。

提示 你可以访问 Flask 文档的配置页面查看 Flask 内置的配置变量;同样的,在 Flask-SQLAlchemy 文档的配置页面可以看到 Flask-SQLAlchemy 提供的配置变量。

创建数据库模型

在 Watchlist 程序里,目前我们有两类数据要保存:用户信息和电影条目信息。下面分别创建了两个模型类来表示这两张表:

app.py:创建数据库模型

class User(db.Model):  # 表名将会是 user(自动生成,小写处理)
    id = db.Column(db.Integer, primary_key=True)  # 主键
    name = db.Column(db.String(20))  # 名字


class Movie(db.Model):  # 表名将会是 movie
    id = db.Column(db.Integer, primary_key=True)  # 主键
    title = db.Column(db.String(60))  # 电影标题
    year = db.Column(db.String(4))  # 电影年份

模型类的编写有一些限制:

  • 模型类要声明继承 db.Model
  • 每一个类属性(字段)要实例化 db.Column,传入的参数为字段的类型,下面的表格列出了常用的字段类。
  • 在 db.Column() 中添加额外的选项(参数)可以对字段进行设置。比如,primary_key 设置当前字段是否为主键。除此之外,常用的选项还有 nullable(布尔值,是否允许为空值)、index(布尔值,是否设置索引)、unique(布尔值,是否允许重复值)、default(设置默认值)等。

常用的字段类型如下表所示:

字段类说明
db.Integer整型
db.String (size)字符串,size 为最大长度,比如 db.String(20)
db.Text长文本
db.DateTime时间日期,Python datetime 对象
db.Float浮点数
db.Boolean布尔值

创建数据库表

模型类创建后,还不能对数据库进行操作,因为我们还没有创建表和数据库文件。下面在 Python Shell 中创建了它们:

(env) $ flask shell
>>> from app import db
>>> db.create_all()

打开文件管理器,你会发现项目根目录下出现了新创建的数据库文件 data.db。

这个文件不需要提交到 Git 仓库,我们在 .gitignore 文件最后添加一行新规则:

*.db

如果你改动了模型类,想重新生成表模式,那么需要先使用 db.drop_all() 删除表,然后重新创建:

>>> db.drop_all()
>>> db.create_all()

注意这会一并删除所有数据,如果你想在不破坏数据库内的数据的前提下变更表的结构,需要使用数据库迁移工具,比如集成了 Alembic 的 Flask-Migrate 扩展。

提示 上面打开 Python Shell 使用的是 flask shell命令,而不是 python。使用这个命令启动的 Python Shell 激活了“程序上下文”,它包含一些特殊变量,这对于某些操作是必须的(比如上面的 db.create_all()调用)。请记住,后续的 Python Shell 都会使用这个命令打开。

和 flask shell类似,我们可以编写一个自定义命令来自动执行创建数据库表操作:

app.py:自定义命令 initdb

import click


@app.cli.command()  # 注册为命令,可以传入 name 参数来自定义命令
@click.option('--drop', is_flag=True, help='Create after drop.')  # 设置选项
def initdb(drop):
    """Initialize the database."""
    if drop:  # 判断是否输入了选项
        db.drop_all()
    db.create_all()
    click.echo('Initialized database.')  # 输出提示信息

默认情况下,如果没有指定,函数名称就是命令的名字(注意函数名中的下划线会被转换为连接线),现在执行 flask initdb 命令就可以创建数据库表:

(env) $ flask initdb

使用 --drop 选项可以删除表后重新创建:

(env) $ flask initdb --drop

创建、读取、更新、删除

在前面打开的 Python Shell 里,我们来测试一下常见的数据库操作。你可以跟着示例代码来操作,也可以自由练习。

创建

下面的操作演示了如何向数据库中添加记录:

>>> from app import User, Movie  # 导入模型类
>>> user = User(name='Grey Li')  # 创建一个 User 记录
>>> m1 = Movie(title='Leon', year='1994')  # 创建一个 Movie 记录
>>> m2 = Movie(title='Mahjong', year='1996')  # 再创建一个 Movie 记录
>>> db.session.add(user)  # 把新创建的记录添加到数据库会话
>>> db.session.add(m1)
>>> db.session.add(m2)
>>> db.session.commit()  # 提交数据库会话,只需要在最后调用一次即可

提示 在实例化模型类的时候,我们并没有传入 id 字段(主键),因为 SQLAlchemy 会自动处理这个字段。

最后一行 db.session.commit() 很重要,只有调用了这一行才会真正把记录提交进数据库,前面的 db.session.add() 调用是将改动添加进数据库会话(一个临时区域)中。

读取

通过对模型类的 query 属性调用可选的过滤方法和查询方法,我们就可以获取到对应的单个或多个记录(记录以模型类实例的形式表示)。查询语句的格式如下:

<模型类>.query.<过滤方法(可选)>.<查询方法>

下面是一些常用的过滤方法:

过滤方法说明
filter()使用指定的规则过滤记录,返回新产生的查询对象
filter_by()使用指定规则过滤记录(以关键字表达式的形式),返回新产生的查询对象
order_by()根据指定条件对记录进行排序,返回新产生的查询对象
group_by()根据指定条件对记录进行分组,返回新产生的查询对象

下面是一些常用的查询方法:

查询方法说明
all()返回包含所有查询记录的列表
first()返回查询的第一条记录,如果未找到,则返回 None
get(id)传入主键值作为参数,返回指定主键值的记录,如果未找到,则返回 None
count()返回查询结果的数量
first_or_404()返回查询的第一条记录,如果未找到,则返回 404 错误响应
get_or_404(id)传入主键值作为参数,返回指定主键值的记录,如果未找到,则返回 404 错误响应
paginate()返回一个 Pagination 对象,可以对记录进行分页处理

下面的操作演示了如何从数据库中读取记录,并进行简单的查询:

注意需要先导入。

>>> from app import Movie  # 导入模型类
>>> movie = Movie.query.first()  # 获取 Movie 模型的第一个记录(返回模型类实例)
>>> movie.title  # 对返回的模型类实例调用属性即可获取记录的各字段数据
'Leon'
>>> movie.year
'1994'
>>> Movie.query.all()  # 获取 Movie 模型的所有记录,返回包含多个模型类实例的列表
[<Movie 1>, <Movie 2>]
>>> Movie.query.count()  # 获取 Movie 模型所有记录的数量
2
>>> Movie.query.get(1)  # 获取主键值为 1 的记录
<Movie 1>
>>> Movie.query.filter_by(title='Mahjong').first()  # 获取 title 字段值为 Mahjong 的记录
<Movie 2>
>>> Movie.query.filter(Movie.title=='Mahjong').first()  # 等同于上面的查询,但使用不同的过滤方法
<Movie 2>

提示 我们在说 Movie 模型的时候,实际指的是数据库中的 movie 表。表的实际名称是模型类的小写形式(自动生成),如果你想自己指定表名,可以定义 __tablename__ 属性。

对于最基础的 filter() 过滤方法,SQLAlchemy 支持丰富的查询操作符,具体可以访问文档相关页面查看。除此之外,还有更多的查询方法、过滤方法和数据库函数可以使用,具体可以访问文档的 Query API 部分查看。

更新

下面的操作更新了 Movie 模型中主键为 2 的记录:

>>> movie = Movie.query.get(2)
>>> movie.title = 'WALL-E'  # 直接对实例属性赋予新的值即可
>>> movie.year = '2008'
>>> db.session.commit()  # 注意仍然需要调用这一行来提交改动

删除

下面的操作删除了 Movie 模型中主键为 1 的记录:

>>> movie = Movie.query.get(1)
>>> db.session.delete(movie)  # 使用 db.session.delete() 方法删除记录,传入模型实例
>>> db.session.commit()  # 提交改动

在程序里操作数据库

经过上面的一番练习,我们可以在 Watchlist 里进行实际的数据库操作了。

在主页视图读取数据库记录

因为设置了数据库,负责显示主页的 index 可以从数据库里读取真实的数据:

@app.route('/')
def index():
    user = User.query.first()  # 读取用户记录
    movies = Movie.query.all()  # 读取所有电影记录
    return render_template('index.html', user=user, movies=movies)

在 index 视图中,原来传入模板的 name 变量被 user 实例取代,模板 index.html 中的两处 name 变量也要相应的更新为 user.name 属性:

{{ user.name }}'s Watchlist

生成虚拟数据

因为有了数据库,我们可以编写一个命令函数把虚拟数据添加到数据库里。下面是用来生成虚拟数据的命令函数:

app.py:创建自定义命令 forge

import click


@app.cli.command()
def forge():
    """Generate fake data."""
    db.create_all()

    # 全局的两个变量移动到这个函数内
    name = 'Grey Li'
    movies = [
        {'title': 'My Neighbor Totoro', 'year': '1988'},
        {'title': 'Dead Poets Society', 'year': '1989'},
        {'title': 'A Perfect World', 'year': '1993'},
        {'title': 'Leon', 'year': '1994'},
        {'title': 'Mahjong', 'year': '1996'},
        {'title': 'Swallowtail Butterfly', 'year': '1996'},
        {'title': 'King of Comedy', 'year': '1999'},
        {'title': 'Devils on the Doorstep', 'year': '1999'},
        {'title': 'WALL-E', 'year': '2008'},
        {'title': 'The Pork of Music', 'year': '2012'},
    ]

    user = User(name=name)
    db.session.add(user)
    for m in movies:
        movie = Movie(title=m['title'], year=m['year'])
        db.session.add(movie)

    db.session.commit()
    click.echo('Done.')

现在执行 flask forge 命令就会把所有虚拟数据添加到数据库里:

(env) $ flask forge

这里需要注意的是,在cmd命令行下执行flask forge,不是在python命令行下执行,在命令行执行会报错SyntaxError: invalid syntax

本章小结

本章我们学习了使用 SQLAlchemy 操作数据库,后面你会慢慢熟悉相关的操作。结束前,让我们提交代码:

$ git add .
$ git commit -m "Add database support with Flask-SQLAlchemy"
$ git push

提示 你可以在 GitHub 上查看本书示例程序的对应 commit:4d2442a

进阶提示

  • 在生产环境,你可以更换更合适的 DBMS,因为 SQLAlchemy 支持多种 SQL 数据库引擎,通常只需要改动非常少的代码。
  • 我们的程序只有一个用户,所以没有将 User 表和 Movie 表建立关联。访问 Flask-SQLAlchemy 文档的“声明模型”章节可以看到相关内容。
  • 阅读 SQLAlchemy 官方文档和教程详细了解它的用法。注意我们在这里使用 Flask-SQLAlchemy 来集成它,所以用法和单独使用 SQLAlchemy 有一些不同。作为参考,你可以同时阅读 Flask-SQLAlchemy 官方文档
  • 如果你是《Flask Web 开发实战》的读者,第 5 章详细介绍了 SQLAlchemy 和 Flask-Migrate 的使用,第 8 章和第 9 章引入了更复杂的模型关系和查询方法。

参考教程

第 2 章:Hello, Flask! - Flask 入门教程 (helloflask.com)

推荐书籍

Flask Web 开发实战 - HelloFlask

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值