如何在Python3中使用Flask制作Web应用程序
作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。
介绍
Flask 是一个小巧轻便的 Python Web 框架,它提供了有用的工具和功能,使在 Python 中创建 Web 应用程序更加容易。 它为开发人员提供了灵活性,并且对于新开发人员来说是一个更易于访问的框架,因为您只需使用单个 Python 文件即可快速构建 Web 应用程序。 Flask 也是可扩展的,在开始之前不会强制使用特定的目录结构或需要复杂的样板代码。
作为本教程的一部分,您将使用 Bootstrap 工具包 为您的应用程序设置样式,使其更具视觉吸引力。 Bootstrap 将帮助您将响应式网页合并到您的 Web 应用程序中,使其在移动浏览器上也能正常工作,而无需编写您自己的 HTML、CSS 和 JavaScript 代码来实现这些目标。 该工具包将让您专注于学习 Flask 的工作原理。
Flask 使用 Jinja 模板引擎 使用熟悉的 Python 概念(如变量、循环、列表等)动态构建 HTML 页面。 您将使用这些模板作为该项目的一部分。
在本教程中,您将在 Python 3 中使用 Flask 和 SQLite 构建一个小型网络博客。 该应用程序的用户可以查看数据库中的所有帖子并单击帖子的标题以查看其内容,并能够将新帖子添加到数据库并编辑或删除现有帖子。
先决条件
在开始遵循本指南之前,您需要:
- 本地 Python 3 编程环境,请按照 如何为本地机器安装和设置 Python 3 系列的本地编程环境中的分发教程进行操作。 在本教程中,我们将调用我们的项目目录
flask_blog
。 - 了解 Python 3 的概念,例如 数据类型、 条件语句、for 循环、 函数 和其他此类概念。 如果您不熟悉 Python,请查看我们的 如何在 Python 3 中编码。
第 1 步 — 安装 Flask
在此步骤中,您将激活 Python 环境并使用 pip 包安装程序安装 Flask。
如果您还没有激活您的编程环境,请确保您在您的项目目录中 (flask_blog
) 并使用以下命令来激活环境:
source env/bin/activate
一旦您的编程环境被激活,您的提示现在将有一个 env
前缀,如下所示:
此前缀表示环境 env
当前处于活动状态,它可能有另一个名称,具体取决于您在创建期间如何命名它。
注意:您可以使用Git,一个版本控制系统,有效地管理和跟踪您项目的开发过程。 要了解如何使用 Git,您可能需要查看我们的 Git 安装使用和分支简介 文章。
如果您使用 Git,最好忽略 .gitignore
文件中新创建的 env
目录,以避免跟踪与项目无关的文件。
现在您将安装 Python 包并将您的项目代码与主要的 Python 系统安装隔离开来。 您将使用 pip
和 python
来执行此操作。
要安装 Flask,请运行以下命令:
pip install flask
安装完成后,运行以下命令确认安装:
python -c "import flask; print(flask.__version__)"
您使用带有选项 -c
的 python 命令行界面 来执行 Python 代码。 接下来,您使用 import flask;
导入 flask
包,然后打印通过 flask.__version__
变量提供的 Flask 版本。
输出将是类似于以下的版本号:
Output1.1.2
您已经创建了项目文件夹、虚拟环境并安装了 Flask。 您现在已准备好继续设置您的基础应用程序。
第 2 步 — 创建基础应用程序
现在您已经设置了编程环境,您将开始使用 Flask。 在这一步中,您将在 Python 文件中创建一个小型 Web 应用程序并运行它以启动服务器,该服务器将在浏览器上显示一些信息。
在您的 flask_blog
目录中,打开一个名为 hello.py
的文件进行编辑,使用 nano
或您喜欢的文本编辑器:
nano hello.py
这个 hello.py
文件将作为如何处理 HTTP 请求的最小示例。 在其中,您将导入 Flask 对象,并创建一个返回 HTTP 响应的函数。 在 hello.py
中写入以下代码:
烧瓶博客/hello.py
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello, World!'
在前面的代码块中,您首先从 flask
包中导入 Flask
对象。 然后,您使用它来创建名称为 app
的 Flask 应用程序实例。 您传递包含当前 Python 模块名称的特殊变量 __name__
。 它用于告诉实例它所在的位置——你需要它,因为 Flask 在幕后设置了一些路径。
创建 app
实例后,您可以使用它来处理传入的 Web 请求并向用户发送响应。 @app.route
是一个 装饰器,它将常规 Python 函数转换为 Flask 视图函数,它将函数的返回值转换为 HTTP 响应以由 HTTP 客户端显示,例如网络浏览器。 您将值 '/'
传递给 @app.route()
表示此函数将响应对 URL /
的 Web 请求,这是主 URL。
hello()
视图函数返回字符串 'Hello, World!'
作为响应。
保存并关闭文件。
要运行您的 Web 应用程序,您将首先使用 FLASK_APP
环境变量告诉 Flask 在哪里可以找到应用程序(在您的情况下是 hello.py
文件):
export FLASK_APP=hello
然后使用 FLASK_ENV
环境变量在开发模式下运行它:
export FLASK_ENV=development
最后,使用 flask run
命令运行应用程序:
flask run
一旦应用程序运行,输出将如下所示:
Output * Serving Flask app "hello" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 813-894-335
前面的输出有几条信息,例如:
- 您正在运行的应用程序的名称。
- 运行应用程序的环境。
Debug mode: on
表示 Flask 调试器正在运行。 这在开发时很有用,因为它会在出现问题时为我们提供详细的错误消息,从而使故障排除更加容易。- 应用程序在 URL
http://127.0.0.1:5000/
上本地运行,127.0.0.1
是代表您机器的localhost
的 IP,:5000
是端口号。
打开浏览器并输入 URL http://127.0.0.1:5000/
,您将收到字符串 Hello, World!
作为响应,这确认您的应用程序已成功运行。
Warning Flask 使用简单的 Web 服务器在开发环境中为我们的应用程序提供服务,这也意味着 Flask 调试器正在运行以更容易地捕获错误。 此开发服务器不应在生产部署中使用。 有关更多信息,请参阅 Flask 文档上的 部署选项 页面,您也可以查看此 Flask 部署教程。
您现在可以让开发服务器在终端中运行并打开另一个终端窗口。 进入hello.py
所在的工程文件夹,激活虚拟环境,设置环境变量FLASK_ENV
和FLASK_APP
,继续下一步。 (这些命令在此步骤的前面列出。)
注意:打开新终端时,一定要记得激活虚拟环境并设置环境变量FLASK_ENV
和FLASK_APP
。
虽然 Flask 应用程序的开发服务器已经在运行,但无法使用相同的 flask run
命令运行另一个 Flask 应用程序。 这是因为 flask run
默认使用端口号 5000
,一旦被占用,将无法在其上运行其他应用程序,因此您会收到类似以下错误:
OutputOSError: [Errno 98] Address already in use
要解决这个问题,要么通过 CTRL+C
停止当前正在运行的服务器,然后再次运行 flask run
,或者如果您想同时运行这两个服务器,您可以将不同的端口号传递给-p
参数,例如,要在端口 5001
上运行另一个应用程序,请使用以下命令:
flask run -p 5001
您现在有一个小型 Flask Web 应用程序。 您已经运行了应用程序并在 Web 浏览器上显示了信息。 接下来,您将在应用程序中使用 HTML 文件。
第 3 步 — 使用 HTML 模板
目前,您的应用程序仅显示一条没有任何 HTML 的简单消息。 Web 应用程序主要使用 HTML 为访问者显示信息,因此您现在将在应用程序中合并 HTML 文件,这些文件可以显示在 Web 浏览器上。
Flask 提供了一个 render_template()
辅助函数,允许使用 Jinja 模板引擎。 通过在 .html
文件中编写 HTML 代码以及在 HTML 代码中使用逻辑,这将使管理 HTML 变得更加容易。 您将使用这些 HTML 文件 (templates) 来构建您的所有应用程序页面,例如您将显示当前博客文章的主页、博客文章的页面、用户可以添加新帖子,等等。
在这一步中,您将在一个新文件中创建主 Flask 应用程序。
首先,在您的 flask_blog
目录中,使用 nano
或您喜欢的编辑器来创建和编辑您的 app.py
文件。 这将包含您将用于创建博客应用程序的所有代码:
nano app.py
在这个新文件中,您将导入 Flask
对象,以像之前一样创建一个 Flask 应用程序实例。 您还将导入 render_template() 辅助函数,该函数可让您渲染存在于您即将创建的 templates
文件夹中的 HTML 模板文件。 该文件将有一个单独的视图函数,负责处理对主 /
路由的请求。 添加以下内容:
烧瓶博客/app.py
from flask import Flask, render_template app = Flask(__name__) @app.route('/') def index(): return render_template('index.html')
index()
视图函数返回以 index.html
作为参数调用 render_template()
的结果,这告诉 render_template()
查找名为 index.html
的文件] 在 模板文件夹 中。 文件夹和文件都不存在,如果此时运行应用程序会出错。 尽管如此,您仍将运行它,以便熟悉这个常见的异常。 然后,您将通过创建所需的文件夹和文件来修复它。
保存并退出文件。
使用 CTRL+C
停止运行 hello
应用程序的其他终端中的开发服务器。
在运行应用程序之前,请确保正确指定 FLASK_APP
环境变量的值,因为您不再使用应用程序 hello
:
export FLASK_APP=app flask run
在浏览器中打开 URL http://127.0.0.1:5000/
将导致调试器页面通知您找不到 index.html
模板。 代码中导致此错误的主要行将突出显示。 在这种情况下,它是行 return render_template('index.html')
。
如果单击此行,调试器将显示更多代码,以便您有更多上下文来帮助您解决问题。
要修复此错误,请在 flask_blog
目录中创建一个名为 templates
的目录。 然后在其中打开一个名为 index.html
的文件进行编辑:
mkdir templates nano templates/index.html
接下来,在 index.html
中添加以下 HTML 代码:
flask_blog/templates/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>FlaskBlog</title> </head> <body> <h1>Welcome to FlaskBlog</h1> </body> </html>
保存文件并使用浏览器再次导航到 http://127.0.0.1:5000/
,或刷新页面。 这次浏览器应该在 <h1>
标签中显示文本 Welcome to FlaskBlog
。
除了 templates
文件夹外,Flask Web 应用程序通常还有一个 static
文件夹用于托管应用程序使用的静态文件,例如 CSS 文件、JavaScript 文件和图像。
您可以创建一个 style.css
样式表文件来将 CSS 添加到您的应用程序中。 首先,在您的主 flask_blog
目录中创建一个名为 static
的目录:
mkdir static
然后在 static
目录中创建另一个名为 css
的目录来托管 .css
文件。 这通常用于在专用文件夹中组织静态文件,因此,JavaScript 文件通常位于名为 js
的目录中,图像放在名为 images
的目录中(或 [X203X ]), 等等。 以下命令将在 static
目录中创建 css
目录:
mkdir static/css
然后打开css
目录下的style.css
文件进行编辑:
nano static/css/style.css
将以下 CSS 规则添加到您的 style.css
文件中:
flask_blog/static/css/style.css
h1 { border: 2px #eee solid; color: brown; text-align: center; padding: 10px; }
CSS 代码将添加一个边框,将颜色更改为棕色,使文本居中,并为 <h1>
标签添加一点填充。
保存并关闭文件。
接下来,打开index.html
模板文件进行编辑:
nano templates/index.html
您将在 index.html
模板文件的 <head>
部分中添加指向 style.css
文件的链接:
flask_blog/templates/index.html
. . . <head> <meta charset="UTF-8"> <link rel="stylesheet" href="{{ url_for('static', filename= 'css/style.css') }}"> <title>FlaskBlog</title> </head> . . .
在这里,您使用 url_for() 辅助函数来生成文件的适当位置。 第一个参数指定您要链接到静态文件,第二个参数是静态目录中文件的路径。
保存并关闭文件。
刷新应用程序的索引页面后,您会注意到文本 Welcome to FlaskBlog
现在为棕色,居中并包含在边框内。
您可以使用 CSS 语言来设置应用程序的样式,并使用您自己的设计使其更具吸引力。 但是,如果您不是网页设计师,或者您不熟悉 CSS,那么您可以使用 Bootstrap 工具包,它提供了易于使用的组件来为您的应用程序设置样式。 在这个项目中,我们将使用 Bootstrap。
您可能已经猜到,制作另一个 HTML 模板意味着重复您已经在 index.html
模板中编写的大部分 HTML 代码。 借助 基本模板 文件,您可以避免不必要的代码重复,您的所有 HTML 文件都将从该文件继承。 有关详细信息,请参阅 Jinja 中的模板继承。
要制作基本模板,首先在 templates
目录中创建一个名为 base.html
的文件:
nano templates/base.html
在 base.html
模板中键入以下代码:
flask_blog/templates/base.html
<!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <title>{% block title %} {% endblock %}</title> </head> <body> <nav class="navbar navbar-expand-md navbar-light bg-light"> <a class="navbar-brand" href="{{ url_for('index')}}">FlaskBlog</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item active"> <a class="nav-link" href="#">About</a> </li> </ul> </div> </nav> <div class="container"> {% block content %} {% endblock %} </div> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> </body> </html>
完成编辑后保存并关闭文件。
前面代码块中的大部分代码是标准 HTML 和 Bootstrap 所需的代码。 <meta>
标签为 Web 浏览器提供信息,<link>
标签链接 Bootstrap CSS 文件,<script>
标签是 JavaScript 代码的链接,允许一些额外的 Bootstrap 功能,查看 Bootstrap 文档 了解更多信息。
但是,以下突出显示的部分特定于 Jinja 模板引擎:
{% block title %} {% endblock %}
:一个 block 用作标题的占位符,稍后您将在其他模板中使用它来为应用程序中的每个页面提供自定义标题,而无需重写整个 [X195X ] 部分。模板:Url for('index')
:将返回index()
视图函数的 URL 的函数调用。 这与过去url_for()
调用你用来链接静态 CSS 文件不同,因为它只需要一个参数,即视图函数的名称,并链接到与函数关联的路由而不是静态文件.{% block content %} {% endblock %}
:另一个块将被内容替换,具体取决于将覆盖它的 子模板 (从base.html
继承的模板)。
现在您已经有了一个基本模板,您可以使用继承来利用它。 打开index.html
文件:
nano templates/index.html
然后将其内容替换为以下内容:
flask_blog/templates/index.html
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1> {% endblock %}
在这个新版本的 index.html
模板中,您使用 {% extends %}
标记从 base.html
模板继承。 然后,您可以通过将基本模板中的 content
块替换为前面代码块中 content
块中的内容来扩展它。
这个 content
块包含一个 <h1>
标记,在 title
块内带有文本 Welcome to FlaskBlog
,它反过来替换了原来的 title
块带有文本 Welcome to FlaskBlog
的 base.html
模板。 这样,您可以避免重复两次相同的文本,因为它既可以用作页面的标题,也可以用作从基本模板继承而来的导航栏下方的标题。
模板继承还使您能够重用您在其他模板中拥有的 HTML 代码(在本例中为 base.html
),而无需在每次需要时重复。
保存并关闭文件并刷新浏览器上的索引页面。 您将看到带有导航栏和样式标题的页面。
您已经在 Flask 中使用了 HTML 模板和静态文件。 您还使用 Bootstrap 开始优化页面外观和基本模板以避免代码重复。 在下一步中,您将设置一个存储应用程序数据的数据库。
第 4 步 — 设置数据库
在此步骤中,您将设置一个数据库来存储数据,即您的应用程序的博客文章。 您还将使用一些示例条目填充数据库。
您将使用 SQLite 数据库 文件来存储数据,因为我们将用于与数据库交互的 sqlite3 模块在标准 Python 库中很容易获得。 有关 SQLite 的更多信息,请查看 本教程 。
首先,由于 SQLite 中的数据存储在表和列中,并且由于您的数据主要由博客文章组成,因此您首先需要创建一个名为 posts
的表,其中包含必要的列。 您将创建一个 .sql
文件,其中包含 SQL 命令以创建具有几列的 posts
表。 然后,您将使用此文件来创建数据库。
在 flask_blog
目录中打开一个名为 schema.sql
的文件:
nano schema.sql
在此文件中键入以下 SQL 命令:
flask_blog/schema.sql
DROP TABLE IF EXISTS posts; CREATE TABLE posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, title TEXT NOT NULL, content TEXT NOT NULL );
保存并关闭文件。
第一个 SQL 命令是 DROP TABLE IF EXISTS posts;
,这会删除任何已经存在的名为 posts
的表,这样您就不会感到困惑。 请注意,无论何时使用这些 SQL 命令,这都会删除数据库中的所有内容,因此请确保在完成本教程并试验最终结果之前不要在 Web 应用程序中编写任何重要内容。 接下来,使用 CREATE TABLE posts
创建具有以下列的 posts
表:
id
:表示主键的整数,数据库将为每个条目(即博客文章)分配一个唯一值。created
:博文的创建时间。NOT NULL
表示该列不应该为空,DEFAULT
的值为CURRENT_TIMESTAMP
的值,即帖子被添加到数据库的时间。 就像id
一样,您不需要为此列指定值,因为它会自动填充。title
:帖子标题。content
:帖子内容。
现在您在 schema.sql
文件中有一个 SQL 模式,您将使用它来使用 Python 文件创建数据库,该文件将生成 SQLite .db
数据库文件。 使用您喜欢的编辑器在 flask_blog
目录中打开一个名为 init_db.py
的文件:
nano init_db.py
然后添加以下代码。
烧瓶博客/init_db.py
import sqlite3 connection = sqlite3.connect('database.db') with open('schema.sql') as f: connection.executescript(f.read()) cur = connection.cursor() cur.execute("INSERT INTO posts (title, content) VALUES (?, ?)", ('First Post', 'Content for the first post') ) cur.execute("INSERT INTO posts (title, content) VALUES (?, ?)", ('Second Post', 'Content for the second post') ) connection.commit() connection.close()
您首先导入 sqlite3
模块,然后打开与名为 database.db
的数据库文件的连接,该文件将在您运行 Python 文件后创建。 然后使用 open()
函数打开 schema.sql
文件。 接下来,您使用 executescript() 方法执行其内容,该方法一次执行多个 SQL 语句,这将创建 posts
表。 你创建了一个 Cursor 对象,它允许你使用它的 execute() 方法来执行两个 INSERT
SQL 语句来添加两个博客文章到你的 [X163X ] 桌子。 最后,您提交更改并关闭连接。
保存并关闭文件,然后在终端中使用 python
命令运行它:
python init_db.py
一旦文件完成执行,一个名为 database.db
的新文件将出现在您的 flask_blog
目录中。 这意味着您已成功设置数据库。
在下一步中,您将检索插入到数据库中的帖子并将它们显示在应用程序的主页中。
第 5 步 - 显示所有帖子
现在您已经设置了数据库,您现在可以修改 index()
视图函数来显示您在数据库中的所有帖子。
打开app.py
文件进行如下修改:
nano app.py
对于您的第一次修改,您将在文件顶部导入 sqlite3
模块:
烧瓶博客/app.py
import sqlite3 from flask import Flask, render_template . . .
接下来,您将创建一个创建数据库连接并将其返回的函数。 在导入后直接添加:
烧瓶博客/app.py
. . . from flask import Flask, render_template def get_db_connection(): conn = sqlite3.connect('database.db') conn.row_factory = sqlite3.Row return conn . . .
此 get_db_connection()
函数打开到 database.db
数据库文件的连接,然后将 row_factory 属性设置为 sqlite3.Row
以便您可以基于名称访问列。 这意味着数据库连接将返回行为类似于常规 Python 字典的行。 最后,该函数返回您将用于访问数据库的 conn
连接对象。
定义 get_db_connection()
函数后,将 index()
函数修改为如下所示:
烧瓶博客/app.py
. . . @app.route('/') def index(): conn = get_db_connection() posts = conn.execute('SELECT * FROM posts').fetchall() conn.close() return render_template('index.html', posts=posts)
在这个新版本的 index()
函数中,您首先使用之前定义的 get_db_connection()
函数打开一个数据库连接。 然后执行 SQL 查询以从 posts
表中选择所有条目。 您实现 fetchall() 方法来获取查询结果的所有行,这将返回您在上一步中插入数据库的帖子列表。
您使用 close()
方法关闭数据库连接并返回渲染 index.html
模板的结果。 您还将 posts
对象作为参数传递,其中包含您从数据库中获得的结果,这将允许您访问 index.html
模板中的博客文章。
完成这些修改后,保存并关闭 app.py
文件。
现在您已经将从数据库中获取的帖子传递到 index.html
模板,您可以使用 for 循环 在索引页面上显示每个帖子。
打开index.html
文件:
nano templates/index.html
然后,将其修改为如下所示:
flask_blog/templates/index.html
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1> {% for post in posts %} <a href="#"> <h2>{{ post['title'] }}</h2> </a> <span class="badge badge-primary">{{ post['created'] }}</span> <hr> {% endfor %} {% endblock %}
在这里,语法 {% for post in posts %}
是 Jinja for
循环,它类似于 Python for
循环,只是它必须稍后用 {% endfor %}
关闭句法。 您可以使用此语法循环遍历 posts
列表中由 return render_template('index.html', posts=posts)
行中的 index()
函数传递的每个项目。 在这个 for
循环中,您在 <a>
标签内的 <h2>
标题中显示帖子标题(稍后您将使用此标签单独链接到每个帖子)。
您使用文字变量分隔符 (模板:...
) 显示标题。 请记住,post
将是一个类似字典的对象,因此您可以使用 post['title']
访问帖子标题。 您还可以使用相同的方法显示帖子创建日期。
完成文件编辑后,保存并关闭它。 然后导航到浏览器中的索引页面。 您将在页面上看到您添加到数据库中的两个帖子。
现在您已经修改了 index()
视图函数以在应用程序主页上显示数据库中的所有帖子,您将继续在单个页面中显示每个帖子并允许用户链接到每个个人帖子。
第 6 步 - 显示单个帖子
在这一步中,您将创建一个新的 Flask 路由,其中包含一个视图函数和一个新的 HTML 模板,以通过其 ID 显示单个博客文章。
在此步骤结束时,URL http://127.0.0.1:5000/1
将成为显示第一篇文章的页面(因为它的 ID 为 1
)。 http://127.0.0.1:5000/ID
URL 将显示带有相关 ID
编号的帖子(如果存在)。
打开app.py
进行编辑:
nano app.py
由于您需要在此项目稍后的多个位置通过其 ID 从数据库中获取博客文章,因此您将创建一个名为 get_post()
的独立函数。 您可以通过传递一个 ID 来调用它并接收与提供的 ID 关联的博客文章,或者如果博客文章不存在,则让 Flask 以 404 Not Found
消息响应。
要响应 404
页面,您需要从 Werkzeug 库中导入 abort() 函数,该库与 Flask 一起安装,位于顶部文件:
烧瓶博客/app.py
import sqlite3 from flask import Flask, render_template from werkzeug.exceptions import abort . . .
然后,在您在上一步中创建的 get_db_connection()
函数之后添加 get_post()
函数:
烧瓶博客/app.py
. . . def get_db_connection(): conn = sqlite3.connect('database.db') conn.row_factory = sqlite3.Row return conn def get_post(post_id): conn = get_db_connection() post = conn.execute('SELECT * FROM posts WHERE id = ?', (post_id,)).fetchone() conn.close() if post is None: abort(404) return post . . .
这个新函数有一个 post_id
参数,用于确定要返回的博客文章。
在函数内部,您使用 get_db_connection()
函数打开数据库连接并执行 SQL 查询以获取与给定 post_id
值关联的博客文章。 添加 fetchone()
方法以获取结果并将其存储在 post
变量中,然后关闭连接。 如果 post
变量的值为 None
,这意味着在数据库中没有找到结果,则使用之前导入的 abort()
函数以 [X170X ] 错误代码,函数将完成执行。 但是,如果找到帖子,则返回 post
变量的值。
接下来,在app.py
文件末尾添加如下视图函数:
烧瓶博客/app.py
. . . @app.route('/<int:post_id>') def post(post_id): post = get_post(post_id) return render_template('post.html', post=post)
在这个新的视图函数中,您添加了一个 变量规则 <int:post_id>
来指定斜线后面的部分 (/
) 是一个正整数(标有 int
转换器),您需要在视图功能中访问。 Flask 识别出这一点并将其值传递给 post()
视图函数的 post_id
关键字参数。 然后,您使用 get_post()
函数获取与指定 ID 关联的博客文章,并将结果存储在 post
变量中,然后将其传递给您的 post.html
模板很快就会创建。
保存app.py
文件并打开一个新的post.html
模板文件进行编辑:
nano templates/post.html
在这个新的 post.html
文件中键入以下代码。 这将类似于 index.html
文件,除了它只会显示单个帖子,此外还会显示帖子的内容:
flask_blog/templates/post.html
{% extends 'base.html' %} {% block content %} <h2>{% block title %} {{ post['title'] }} {% endblock %}</h2> <span class="badge badge-primary">{{ post['created'] }}</span> <p>{{ post['content'] }}</p> {% endblock %}
您添加在 base.html
模板中定义的 title
块,以使页面标题同时反映在 <h2>
标题中显示的帖子标题。
保存并关闭文件。
您现在可以导航到以下 URL 以查看数据库中的两个帖子,以及一个页面,告诉用户未找到请求的博客帖子(因为没有 ID 号为 [ X221X] 到目前为止):
http://127.0.0.1:5000/1 http://127.0.0.1:5000/2 http://127.0.0.1:5000/3
回到索引页面,您将使每个帖子标题链接到其各自的页面。 您将使用 url_for()
函数来执行此操作。 首先打开index.html
模板进行编辑:
nano templates/index.html
然后将 href
属性的值从 #
更改为 {{ url_for('post', post_id=post['id']) }}
,这样 for
循环将如下所示:
flask_blog/templates/index.html
{% for post in posts %} <a href="{{ url_for('post', post_id=post['id']) }}"> <h2>{{ post['title'] }}</h2> </a> <span class="badge badge-primary">{{ post['created'] }}</span> <hr> {% endfor %}
在这里,您将 'post'
作为第一个参数传递给 url_for()
函数。 这是 post()
视图函数的名称,因为它接受 post_id
参数,所以你给它值 post['id']
。 url_for()
函数将根据每个帖子的 ID 返回正确的 URL。
保存并关闭文件。
索引页面上的链接现在将按预期运行。 至此,您现在已经完成了应用程序中负责在数据库中显示博客文章的部分。 接下来,您将向应用程序添加创建、编辑和删除博客文章的功能。
第 7 步 - 修改帖子
现在您已经完成了在 Web 应用程序上显示数据库中存在的博客文章,您需要允许应用程序的用户编写新的博客文章并将它们添加到数据库中,编辑现有的文章,并删除不必要的博客文章。
创建新帖子
到目前为止,您有一个应用程序可以显示数据库中的帖子,但无法添加新帖子,除非您直接连接到 SQLite 数据库并手动添加帖子。 在本节中,您将创建一个页面,您可以在该页面上通过提供标题和内容来创建帖子。
打开app.py
文件进行编辑:
nano app.py
首先,您将从 Flask 框架中导入以下内容:
- 全局 request 对象用于访问将通过 HTML 表单提交的传入请求数据。
- url_for() 函数用于生成 URL。
- flash() 函数在处理请求时闪烁消息。
- redirect() 函数将客户端重定向到不同的位置。
将导入添加到您的文件中,如下所示:
烧瓶博客/app.py
import sqlite3 from flask import Flask, render_template, request, url_for, flash, redirect from werkzeug.exceptions import abort . . .
flash()
函数将闪过的消息存储在客户端的浏览器会话中,这需要设置一个密钥。 此密钥用于保护会话,这允许 Flask 记住从一个请求到另一个请求的信息,例如从新的帖子页面移动到索引页面。 用户可以访问存储在会话中的信息,但不能修改它,除非他们拥有密钥,因此您绝不能允许任何人访问您的密钥。 有关详细信息,请参阅 Flask 会话文档 。
要设置 密钥 ,您将通过 app.config
对象将 SECRET_KEY
配置添加到您的应用程序。 在定义 index()
视图函数之前,将其直接添加到 app
定义之后:
烧瓶博客/app.py
. . . app = Flask(__name__) app.config['SECRET_KEY'] = 'your secret key' @app.route('/') def index(): conn = get_db_connection() posts = conn.execute('SELECT * FROM posts').fetchall() conn.close() return render_template('index.html', posts=posts) . . .
请记住,密钥应该是一个长的随机字符串。
设置密钥后,您将创建一个视图函数,该函数将呈现一个模板,该模板显示一个您可以填写以创建新博客文章的表单。 在文件底部添加这个新函数:
烧瓶博客/app.py
. . . @app.route('/create', methods=('GET', 'POST')) def create(): return render_template('create.html')
这将创建一个接受 GET 和 POST 请求的 /create
路由。 默认情况下接受 GET 请求。 为了也接受 POST 请求,这些请求在提交表单时由浏览器发送,您需要将带有接受的请求类型的 tuple 传递给 @app.route()
的 methods
参数] 装饰师。
保存并关闭文件。
要创建模板,请在 templates
文件夹中打开一个名为 create.html
的文件:
nano templates/create.html
在这个新文件中添加以下代码:
flask_blog/templates/create.html
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Create a New Post {% endblock %}</h1> <form method="post"> <div class="form-group"> <label for="title">Title</label> <input type="text" name="title" placeholder="Post title" class="form-control" value="{{ request.form['title'] }}"></input> </div> <div class="form-group"> <label for="content">Content</label> <textarea name="content" placeholder="Post content" class="form-control">{{ request.form['content'] }}</textarea> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> {% endblock %}
大部分代码是标准 HTML。 它将显示帖子标题的输入框、帖子内容的文本区域和提交表单的按钮。
帖子标题输入的值为{{ request.form['title'] }}
,文本区域的值为{{ request.form['content'] }}
,这样做是为了在出现问题时您输入的数据不会丢失。 例如,如果您写了一篇很长的帖子而忘记给它一个标题,则会显示一条消息,通知您标题是必需的。 这不会丢失您写的帖子,因为它将存储在您可以在模板中访问的 request
全局对象中。
现在,随着开发服务器的运行,使用浏览器导航到 /create
路由:
http://127.0.0.1:5000/create
您将看到一个 Create a New Post 页面,其中包含一个标题和内容框。
此表单向您的 create()
视图函数提交 POST 请求。 但是,该函数中还没有处理 POST 请求的代码,因此在填写表单并提交后没有任何反应。
您将在提交表单时处理传入的 POST 请求。 您将在 create()
视图函数中执行此操作。 您可以通过检查 request.method
的值来单独处理 POST 请求。 当它的值设置为 'POST'
时,这意味着该请求是一个 POST 请求,然后您将继续提取提交的数据,对其进行验证,并将其插入到您的数据库中。
打开app.py
文件进行编辑:
nano app.py
将 create()
视图函数修改为如下所示:
烧瓶博客/app.py
. . . @app.route('/create', methods=('GET', 'POST')) def create(): if request.method == 'POST': title = request.form['title'] content = request.form['content'] if not title: flash('Title is required!') else: conn = get_db_connection() conn.execute('INSERT INTO posts (title, content) VALUES (?, ?)', (title, content)) conn.commit() conn.close() return redirect(url_for('index')) return render_template('create.html')
在 if
语句中,您通过比较 request.method == 'POST'
确保仅当请求是 POST 请求时才执行其后面的代码。
然后,您从 request.form
对象中提取提交的标题和内容,使您可以访问请求中的表单数据。 如果未提供标题,则将满足条件 if not title
,向用户显示一条消息,通知他们需要标题。 另一方面,如果提供了标题,则打开与 get_db_connection()
函数的连接并将标题和收到的内容插入到 posts
表中。
然后,您将更改提交到数据库并关闭连接。 将博客文章添加到数据库后,您使用 redirect()
函数将客户端重定向到索引页面,将 url_for()
函数生成的 URL 传递给其值为 'index'
为一个论点。
保存并关闭文件。
现在,使用您的网络浏览器导航到 /create
路线:
http://127.0.0.1:5000/create
用您选择的标题和一些内容填写表格。 提交表单后,您将看到索引页面上列出的新帖子。
最后,您将显示闪烁的消息并在 base.html
模板中添加指向导航栏的链接,以便轻松访问这个新页面。 打开模板文件:
nano templates/base.html
通过在 <nav>
标记内的 About
链接之后添加新的 <li>
标记来编辑文件。 然后在 content
块正上方添加一个新的 for
循环,以在导航栏下方显示闪烁的消息。 这些消息在 Flask 提供的特殊 get_flashed_messages()
函数中可用:
flask_blog/templates/base.html
<nav class="navbar navbar-expand-md navbar-light bg-light"> <a class="navbar-brand" href="{{ url_for('index')}}">FlaskBlog</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="#">About</a> </li> <li class="nav-item"> <a class="nav-link" href="{{url_for('create')}}">New Post</a> </li> </ul> </div> </nav> <div class="container"> {% for message in get_flashed_messages() %} <div class="alert alert-danger">{{ message }}</div> {% endfor %} {% block content %} {% endblock %} </div>
保存并关闭文件。 导航栏现在将有一个链接到 /create
路线的 New Post
项目。
编辑帖子
要使博客保持最新状态,您需要能够编辑现有帖子。 本节将指导您在应用程序中创建新页面以简化编辑帖子的过程。
首先,您将向 app.py
文件添加一条新路线。 它的视图函数将接收需要编辑的帖子的 ID,URL 的格式为 /post_id/edit
,其中 post_id
变量是帖子的 ID。 打开app.py
文件进行编辑:
nano app.py
接下来在文件末尾添加如下edit()
视图函数。 编辑现有帖子类似于创建新帖子,因此此视图功能将类似于 create()
视图功能:
烧瓶博客/app.py
. . . @app.route('/<int:id>/edit', methods=('GET', 'POST')) def edit(id): post = get_post(id) if request.method == 'POST': title = request.form['title'] content = request.form['content'] if not title: flash('Title is required!') else: conn = get_db_connection() conn.execute('UPDATE posts SET title = ?, content = ?' ' WHERE id = ?', (title, content, id)) conn.commit() conn.close() return redirect(url_for('index')) return render_template('edit.html', post=post)
您编辑的帖子由 URL 确定,Flask 将通过 id
参数将 ID 号传递给 edit()
函数。 您将此值添加到 get_post()
函数以从数据库中获取与提供的 ID 关联的帖子。 新数据将出现在 POST 请求中,该请求在 if request.method == 'POST'
条件内处理。
就像创建新帖子时一样,您首先从 request.form
对象中提取数据,然后如果标题为空值,则闪烁一条消息,否则,您打开一个数据库连接。 然后通过设置新标题和新内容来更新 posts
表,其中数据库中帖子的 ID 等于 URL 中的 ID。
在 GET 请求的情况下,您渲染一个 edit.html
模板,该模板传入 post
变量,该变量保存 get_post()
函数的返回值。 您将使用它在编辑页面上显示现有标题和内容。
保存并关闭文件,然后创建一个新的 edit.html
模板:
nano templates/edit.html
在这个新文件中写入以下代码:
flask_blog/templates/edit.html
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Edit "{{ post['title'] }}" {% endblock %}</h1> <form method="post"> <div class="form-group"> <label for="title">Title</label> <input type="text" name="title" placeholder="Post title" class="form-control" value="{{ request.form['title'] or post['title'] }}"> </input> </div> <div class="form-group"> <label for="content">Content</label> <textarea name="content" placeholder="Post content" class="form-control">{{ request.form['content'] or post['content'] }}</textarea> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> <hr> {% endblock %}
保存并关闭文件。
除了 {{ request.form['title'] or post['title'] }}
和 {{ request.form['content'] or post['content'] }}
语法之外,此代码遵循相同的模式。 这将显示存储在请求中的数据(如果存在),否则显示来自 post
变量的数据,该变量已传递给包含当前数据库数据的模板。
现在,导航到以下 URL 以编辑第一篇文章:
http://127.0.0.1:5000/1/edit
你会看到一个 Edit “First Post” 页面。
编辑帖子并提交表单,然后确保帖子已更新。
您现在需要为索引页面上的每个帖子添加一个指向编辑页面的链接。 打开index.html
模板文件:
nano templates/index.html
编辑该文件,使其与以下内容完全相同:
flask_blog/templates/index.html
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Welcome to FlaskBlog {% endblock %}</h1> {% for post in posts %} <a href="{{ url_for('post', post_id=post['id']) }}"> <h2>{{ post['title'] }}</h2> </a> <span class="badge badge-primary">{{ post['created'] }}</span> <a href="{{ url_for('edit', id=post['id']) }}"> <span class="badge badge-warning">Edit</span> </a> <hr> {% endfor %} {% endblock %}
保存并关闭文件。
在这里,您添加一个 <a>
标签以链接到 edit()
视图函数,传入 post['id']
值以链接到每个帖子的编辑页面与 [X152X ] 关联。
删除帖子
有时帖子不再需要公开可用,这就是为什么删除帖子的功能至关重要的原因。 在此步骤中,您将向应用程序添加删除功能。
首先,您将添加一个接受 POST 请求的新 /ID/delete
路由,类似于 edit()
视图函数。 您的新 delete()
视图函数将收到要从 URL 中删除的帖子的 ID。 打开app.py
文件:
nano app.py
在文件底部添加如下视图函数:
烧瓶博客/app.py
# .... @app.route('/<int:id>/delete', methods=('POST',)) def delete(id): post = get_post(id) conn = get_db_connection() conn.execute('DELETE FROM posts WHERE id = ?', (id,)) conn.commit() conn.close() flash('"{}" was successfully deleted!'.format(post['title'])) return redirect(url_for('index'))
此视图函数只接受 POST 请求。 这意味着导航到浏览器上的 /ID/delete
路由将返回错误,因为 Web 浏览器默认使用 GET 请求。
但是,您可以通过发送 POST 请求的表单访问此路由,该请求传递您要删除的帖子的 ID。 该函数将接收 ID 值并使用它通过 get_post()
函数从数据库中获取帖子。
然后打开数据库连接并执行 DELETE FROM
SQL 命令删除帖子。 您将更改提交到数据库并关闭连接,同时闪烁一条消息以通知用户该帖子已成功删除并将他们重定向到索引页面。
请注意,您不会渲染模板文件,这是因为您只需将 Delete
按钮添加到编辑页面。
打开edit.html
模板文件:
nano templates/edit.html
然后在 <hr>
标签之后和 {% endblock %}
行之前添加以下 <form>
标签:
flask_blog/templates/edit.html
<hr> <form action="{{ url_for('delete', id=post['id']) }}" method="POST"> <input type="submit" value="Delete Post" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this post?')"> </form> {% endblock %}
在提交请求之前,您使用 confirm() 方法显示确认消息。
现在再次导航到博客文章的编辑页面并尝试将其删除:
http://127.0.0.1:5000/1/edit
在此步骤结束时,您的项目的源代码将类似于 此页面上的代码 。
有了这个,您的应用程序的用户现在可以编写新的博客文章并将它们添加到数据库、编辑和删除现有的文章。
结论
本教程介绍了 Flask Python 框架的基本概念。 您学习了如何制作小型 Web 应用程序,在开发服务器中运行它,并允许用户通过 URL 参数和 Web 表单提供自定义数据。 您还使用了 Jinja 模板引擎 来重用 HTML 文件并在其中使用逻辑。 在本教程结束时,您现在拥有一个功能齐全的网络博客,它与 SQLite 数据库 交互,以使用 Python 语言和 SQL 查询创建、显示、编辑和删除博客文章。 如果您想了解有关使用 Flask 和 SQLite 的更多信息,请查看有关 如何使用 Flask 和 SQLite 使用一对多数据库关系的教程。
您可以通过添加用户身份验证来进一步开发此应用程序,以便只有注册用户才能创建和修改博客文章,您还可以为每个博客文章添加评论和标签,并添加文件上传以使用户能够在文章中包含图像。 有关详细信息,请参阅 Flask 文档。
Flask 有许多社区制作的 Flask 扩展。 以下是您可以考虑使用的扩展列表,以简化您的开发过程:
- Flask-Login:管理用户会话并处理登录和注销以及记住登录用户。
- Flask-SQLAlchemy:简化了 Flask 与 SQLAlchemy 的使用,这是一个 Python SQL 工具包和用于与 SQL 数据库交互的对象关系映射器。
- Flask-Mail:帮助在您的 Flask 应用程序中发送电子邮件消息。