如何在 Python 3 中使用 Flask 制作 Web 应用程序

作为Write for DOnations计划的一部分,作者选择了免费和开源基金来接受捐赠

介绍

Flask是一个小而轻的 Python Web 框架,它提供了有用的工具和功能,使在 Python 中创建 Web 应用程序变得更加容易。它为开发人员提供了灵活性,并且对于新开发人员来说是一个更易于访问的框架,因为您只需使用单个 Python 文件即可快速构建 Web 应用程序。Flask 也是可扩展的,在开始之前不会强制使用特定的目录结构或需要复杂的样板代码。

作为本教程的一部分,您将使用Bootstrap 工具包来设计应用程序的样式,使其更具视觉吸引力。Bootstrap 将帮助您将响应式网页整合到您的 Web 应用程序中,使其在移动浏览器上也能很好地运行,而无需编写您自己的 HTML、CSS 和 JavaScript 代码来实现这些目标。该工具包将使您能够专注于学习 Flask 的工作原理。

Flask 使用Jinja 模板引擎使用熟悉的 Python 概念(例如变量、循环、列表等)动态构建 HTML 页面。您将在此项目中使用这些模板。

在本教程中,您将使用Python 3 中的Flask 和SQLite构建一个小型网络博客。该应用程序的用户可以查看数据库中的所有帖子,并单击帖子标题查看其内容,并能够添加将新帖子添加到数据库并编辑或删除现有帖子。

先决条件

在开始遵循本指南之前,您需要:

第 1 步 – 安装 Flask

在此步骤中,您将激活 Python 环境并使用pip包安装程序安装 Flask

如果您尚未激活编程环境,请确保您位于项目目录 ( flask_blog) 中并使用以下命令激活环境:

  • source env/bin/activate

一旦您的编程环境被激活,您的提示现在将有一个env前缀,可能如下所示:

此前缀表示环境env当前处于活动状态,根据您在创建过程中的命名方式,它可能有另一个名称。

注意:您可以使用版本控制系统Git来有效管理和跟踪项目的开发过程。要了解如何使用 Git,您可能需要查看我们的Git 安装使用和分支介绍文章。

如果您正在使用 Git,最好忽略文件中新创建的env目录,.gitignore以避免跟踪与项目无关的文件。

现在,您将安装 Python 包并将您的项目代码与主要的 Python 系统安装隔离开来。您将使用pip和 来执行此操作python

要安装 Flask,请运行以下命令:

  • pip install flask

安装完成后,运行以下命令确认安装:

  • python -c "import flask; print(flask.__version__)"

您可以使用带有执行 Python 代码选项python命令行界面-c接下来导入flask包,import flask;然后打印通过flask.__version__变量提供的 Flask 版本

输出将是类似于以下内容的版本号:

Output
1.1.2

您已经创建了项目文件夹、虚拟环境并安装了 Flask。您现在已准备好继续设置您的基本应用程序。

步骤 2 — 创建基础应用程序

现在您已经设置了编程环境,您将开始使用 Flask。在这一步中,您将在 Python 文件中创建一个小型 Web 应用程序并运行它以启动服务器,这将在浏览器上显示一些信息。

在您的flask_blog目录中,打开一个名为hello.py编辑、使用nano或您喜欢的文本编辑器的文件:

  • nano hello.py

hello.py文件将作为如何处理 HTTP 请求的最小示例。在其中,您将导入Flaskobject,并创建一个返回 HTTP 响应的函数。在里面写入以下代码hello.py

烧瓶博客/你好.py
from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello():
    return 'Hello, World!'

在前面的代码块中,您首先Flaskflask包中导入对象然后使用它创建名为 的 Flask 应用程序实例app您传递__name__保存当前 Python 模块名称的特殊变量它用于告诉实例它所在的位置——你需要它是因为 Flask 在幕后设置了一些路径。

创建app实例后,您可以使用它来处理传入的 Web 请求并向用户发送响应。@app.route将常规 Python 函数转换为 Flask视图函数装饰器,它将函数的返回值转换为 HTTP 响应以供 HTTP 客户端(例如 Web 浏览器)显示。您传递值表示这个函数会为网址的web请求作出回应,这是主要的URL。'/'@app.route()/

hello()视图函数返回字符串'Hello, World!'作为响应。

保存并关闭文件。

要运行您的 Web 应用程序,您首先要hello.py使用FLASK_APP环境变量告诉 Flask 在哪里可以找到应用程序(在您的情况下文件)

  • 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,并:5000为端口号。

打开浏览器并输入 URL http://127.0.0.1:5000/,您将收到字符串Hello, World!作为响应,这确认您的应用程序已成功运行。

警告Flask 使用一个简单的 Web 服务器在开发环境中为我们的应用程序提供服务,这也意味着 Flask 调试器正在运行以更容易捕获错误。此开发服务器不应用于生产部署。有关更多信息,请参阅Flask 文档上部署选项页面,您也可以查看此Flask 部署教程

您现在可以让开发服务器在终端中运行并打开另一个终端窗口。移动到所在项目文件夹hello.py,激活虚拟环境,设置环境变量FLASK_ENVFLASK_APP,继续下一步。(这些命令已在此步骤的前面列出。)

注意:打开新终端时,一定要记住激活虚拟环境并设置环境变量FLASK_ENVFLASK_APP.

当 Flask 应用程序的开发服务器已经在运行时,不可能使用相同的flask run命令运行另一个 Flask 应用程序这是因为默认情况下flask run使用端口号5000,一旦使用它,就无法在其上运行另一个应用程序,因此您会收到类似于以下内容的错误:

Output
OSError: [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 文件(模板)来构建所有应用程序页面,例如您将显示当前博客文章的主页、博客文章的页面、用户可以添加新博客的页面发帖等等。

在此步骤中,您将在新文件中创建主 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()视图函数返回调用的结果render_template()index.html作为参数,这告诉render_template()查找一个名为index.html模板文件夹文件夹和文件都不存在,如果此时运行应用程序,您将收到错误消息。尽管如此,您还是会运行它,以便您熟悉这个经常遇到的异常。然后,您将通过创建所需的文件夹和文件来修复它。

保存并退出文件。

在运行hello应用程序的其他终端中停止开发服务器CTRL+C

在运行应用程序之前,请确保正确指定FLASK_APP环境变量的值,因为您不再使用该应用程序hello

  • export FLASK_APP=app
  • flask run

http://127.0.0.1:5000/在浏览器中打开 URL将导致调试器页面通知您index.html未找到模板。导致此错误的代码中的主行将突出显示。在这种情况下,它是 line return render_template('index.html')

如果单击此行,调试器将显示更多代码,以便您有更多上下文来帮助您解决问题。

Flask 调试器

要修复此错误,请templates在您的flask_blog目录中创建一个名为目录。然后在里面,打开一个叫作index.html编辑的文件

  • mkdir templates
  • nano templates/index.html

接下来,在里面添加以下 HTML 代码index.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/再次导航,或刷新页面。这次浏览器应该Welcome to FlaskBlog<h1>标签中显示文本

除了templates文件夹之外,Flask Web 应用程序通常还有一个static文件夹用于托管静态文件,例如应用程序使用的 CSS 文件、JavaScript 文件和图像。

您可以创建一个style.css样式表文件来将 CSS 添加到您的应用程序中。首先,static在主flask_blog目录中创建一个名为目录:

  • mkdir static

然后css在该static目录创建另一个目录来托管.css文件。这通常用于在专用文件夹中组织静态文件,因此,JavaScript 文件通常位于名为 的目录中js,图像位于名为images(或img的目录中,等等。以下命令将在css目录中创建static目录:

  • mkdir static/css

然后style.csscss目录中打开一个文件进行编辑:

  • 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

您将模板文件style.css<head>部分中添加指向该文件的链接index.html

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 中的模板继承

要制作基本模板,首先base.html在您的templates目录中创建一个名为的文件

  • 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>标签链接引导CSS文件和<script>标签链接的JavaScript代码,允许一些额外的引导功能,检查出的引导文件为多。

但是,以下突出显示的部分特定于 Jinja 模板引擎:

  • {% block title %} {% endblock %}用作标题占位符的,稍后您将在其他模板中使用它为应用程序中的每个页面提供自定义标题,而无需<head>每次都重写整个部分。
  • {{ 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>带有Welcome to FlaskBlog文本标记,该标记title又将模板中的原始title替换base.html为文本Welcome to FlaskBlog这样,您可以避免重复相同的文本两次,因为它既可以作为页面的标题,也可以作为显示在从基本模板继承的导航栏下方的标题。

模板继承还使您能够重用其他模板中的 HTML 代码(base.html在本例中),而不必在每次需要时重复。

保存并关闭文件并刷新浏览器上的索引页面。您将看到带有导航栏和样式标题的页面。

带有 Bootstrap 的索引页

您已经在 Flask 中使用过 HTML 模板和静态文件。您还使用 Bootstrap 开始优化页面外观和基本模板以避免代码重复。在下一步中,您将设置一个用于存储应用程序数据的数据库。

第 4 步 – 设置数据库

在此步骤中,您将设置一个数据库来存储数据,即应用程序的博客文章。您还将使用一些示例条目填充数据库。

您将使用SQLite 数据库文件来存储数据,因为sqlite3我们将用于与数据库交互模块在标准 Python 库中随时可用。有关 SQLite 的更多信息,请查看本教程

首先,因为 SQLite 中的数据存储在表和列中,并且由于您的数据主要由博客文章组成,所以您首先需要创建一个posts包含必要列的表。您将创建一个.sql包含 SQL 命令文件,以创建包含posts几列的表。然后,您将使用此文件来创建数据库。

打开一个schema.sql在你的flask_blog目录中调用的文件

  • 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数据库文件。打开一个指定的文件init_db.py里面flask_blog用你喜欢的编辑器目录:

  • nano init_db.py

然后添加以下代码。

flask_blog/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()方法来执行两个INSERTSQL 语句,从而将两个博客文章添加到您的posts表中。最后,提交更改并关闭连接。

保存并关闭文件,然后使用以下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 %}是 Jinjafor循环,它类似于 Pythonfor循环,只是它稍后必须用{% endfor %}语法关闭您可以使用此语法循环遍历postsindex()函数在行中传递列表中的每个项目return render_template('index.html', posts=posts)在此for循环中,您在标签<h2>内的标题中显示帖子标题<a>(稍后您将使用此标签分别链接到每个帖子)。

您可以使用文字变量分隔符 ( {{ ... }})显示标题请记住,这post将是一个类似字典的对象,因此您可以使用post['title']. 您还可以使用相同的方法显示帖子创建日期。

完成文件编辑后,保存并关闭它。然后导航到浏览器中的索引页面。您将在页面上看到添加到数据库中的两个帖子。

显示帖子的索引页

现在您已经修改了index()视图函数以在应用程序主页上显示数据库中的所有帖子,您将继续在单个页面中显示每个帖子并允许用户链接到每个单独的帖子。

第 6 步 – 显示单个帖子

在这一步中,您将创建一个新的 Flask 路由,其中​​包含一个视图函数和一个新的 HTML 模板,以通过其 ID 显示单个博客文章。

在这一步结束时,URLhttp://127.0.0.1:5000/1将是一个显示第一篇文章的页面(因为它有 ID 1)。URL 将显示带有关联编号的帖子(如果存在)。http://127.0.0.1:5000/IDID

打开app.py编辑:

  • nano app.py

由于您需要在此项目的稍后位置从数据库中的多个位置通过其 ID 获取博客文章,因此您将创建一个名为get_post(). 您可以通过向它传递一个 ID 来调用它并接收与提供的 ID 相关联的博客文章,或者404 Not Found如果博客文章不存在,则让 Flask 用一条消息响应

要响应404页面,您需要abort()Werkzeug文件顶部库中导入函数,该库与 Flask 一起安装:

烧瓶博客/app.py
import sqlite3
from flask import Flask, render_template
from werkzeug.exceptions import abort

. . .

然后,get_post()get_db_connection()您在上一步中创建函数之后立即添加该函数

烧瓶博客/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()之前导入函数以404错误代码进行响应,该函数将完成执行。但是,如果找到帖子,则返回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_id关键字参数post()然后,您可以使用该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 %}

您添加titlebase.html模板中定义块,以使页面标题反映同时显示在<h2>标题中的帖子标题

保存并关闭文件。

您现在可以导航到以下 URL 以查看您数据库中的两个帖子,以及一个页面,告诉用户未找到请求的博客帖子(因为到目前为止没有 ID 号为 的帖子3):

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 文档

要设置密钥,您将SECRET_KEY通过app.config对象向应用程序添加配置app在定义index()视图函数之前,直接在定义之后添加它

烧瓶博客/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')

这将创建一个/create接受 GET 和 POST 请求路由。默认情况下接受 GET 请求。要同时接受 POST 请求(在提交表单时由浏览器发送),您需要将包含接受的请求类型的元组传递给装饰器methods参数@app.route()

保存并关闭文件。

要创建模板,请create.html在您的templates文件夹中打开一个名为文件:

  • 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()视图函数提交 POST 请求但是,函数中还没有处理 POST 请求的代码,因此在填写表单并提交后没有任何反应。

提交表单时,您将处理传入的 POST 请求。您将在create()视图函数中执行此操作您可以通过检查 的值来单独处理 POST 请求request.method当它的值设置为'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语句中,您确保仅当请求是通过比较的 POST 请求时才执行其后的代码request.method == 'POST'

然后request.form,您对象中提取提交的标题和内容,以便您访问请求中的表单数据。如果未提供标题,if not title则将满足条件,向用户显示一条消息,通知他们需要标题。另一方面,如果提供了标题,则打开与get_db_connection()函数的连接并将标题和收到的内容插入posts表格中。

然后将更改提交到数据库并关闭连接。加入博客文章到数据库后,您可以使用客户端重定向到索引页面redirect()传递给它由生成的URL功能url_for()与价值函数'index'作为参数。

保存并关闭文件。

现在,/create使用您的网络浏览器导航到路线:

http://127.0.0.1:5000/create

用您选择的标题和一些内容填写表格。提交表单后,您将看到索引页面上列出的新帖子。

最后,您将显示闪现的消息并添加指向base.html模板中导航栏的链接,以便轻松访问这个新页面。打开模板文件:

  • nano templates/base.html

通过标签内的链接<li>后面添加新标签来编辑文件然后直接在上方添加一个新循环,以在导航栏下方显示闪烁的消息。这些消息在Flask 提供的特殊功能中可用About<nav>forcontentget_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>

保存并关闭文件。导航栏现在将有一个New Post链接到/create路线的项目。

编辑帖子

要使博客保持最新状态,您需要能够编辑现有帖子。本节将指导您在应用程序中创建一个新页面,以简化编辑帖子的过程。

首先,您将向该app.py文件添加一条新路由它的视图函数将接收需要编辑的帖子的 ID,URL 将采用变量为帖子 ID的格式打开文件进行编辑:/post_id/editpost_idapp.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 号传递给edit()函数id您将此值添加到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'] }}and{{ request.form['content'] or post['content'] }}语法之外,此代码遵循相同的模式这会显示请求中存储的数据(如果存在),否则会显示来自post传递给包含当前数据库数据的模板变量的数据。

现在,导航到以下 URL 以编辑第一篇文章:

http://127.0.0.1:5000/1/edit

您将看到编辑“第一篇文章”页面。

编辑帖子页面

编辑帖子并提交表单,然后确保帖子已更新。

您现在需要为索引页面上的每个帖子添加一个指向编辑页面的链接。打开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']值以链接到每个带有Edit链接的帖子的编辑页面

删除帖子

有时帖子不再需要公开可用,这就是删除帖子的功能至关重要的原因。在此步骤中,您将向应用程序添加删除功能。

首先,您将添加一个接受 POST 请求的新路由,类似于视图函数。您的新视图函数将接收要从 URL 中删除的帖子的 ID。打开文件:/ID/deleteedit()delete()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 请求。这意味着导航到浏览器上路由将返回错误,因为 Web 浏览器默认为 GET 请求。/ID/delete

但是,您可以通过发送 POST 请求并传入您要删除的帖子的 ID 的表单访问此路由。该函数将接收 ID 值并使用它从具有该get_post()函数的数据库中获取帖子

然后打开数据库连接并执行DELETE FROMSQL 命令以删除帖子。您将更改提交到数据库并关闭连接,同时闪烁一条消息以通知用户帖子已成功删除并将其重定向到索引页面。

请注意,您不会呈现模板文件,这是因为您只需Delete向编辑页面添加一个按钮。

打开edit.html模板文件:

  • nano templates/edit.html

然后在<form>标记之后<hr>和该{% endblock %}之前直接添加以下标记

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

At the end of this step, the source code of your project will look like the code on this page.

With this, the users of your application can now write new blog posts and add them to the database, edit, and delete existing posts.

Conclusion

This tutorial introduced essential concepts of the Flask Python framework. You learned how to make a small web application, run it in a development server, and allow the user to provide custom data via URL parameters and web forms. You also used the Jinja template engine to reuse HTML files and use logic in them. At the end of this tutorial, you now have a fully functioning web blog that interacts with an SQLite database to create, display, edit, and delete blog posts using the Python language and SQL queries. If you would like to learn more about working with Flask and SQLite check out this tutorial on How To Use One-to-Many Database Relationships with Flask and SQLite.

You can further develop this application by adding user authentication so that only registered users can create and modify blog posts, you may also add comments and tags for each blog post, and add file uploads to give users the ability to include images in the post. See the Flask documentation for more information.

Flask has many community-made Flask extensions. The following is a list of extensions you might consider using to make your development process easier:

  • Flask-Login: manages the user session and handles logging in and logging out and remembering logged-in users.
  • Flask-SQLAlchemy: simplifies using Flask with SQLAlchemy, a Python SQL toolkit and Object Relational Mapper for interacting with SQL databases.
  • Flask-Mail:帮助您在 Flask 应用程序中发送电子邮件消息。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁