如何在Flask应用程序中使用PostgreSQL数据库

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。

介绍

在 Web 应用程序中,您通常需要一个数据库,它是一个有组织的数据集合。 您使用数据库来存储和维护可以有效检索和操作的持久数据。 例如,在社交媒体应用程序中,您有一个数据库,其中用户数据(个人信息、帖子、评论、关注者)以一种可以有效操作的方式存储。 您可以根据不同的要求和条件,将数据添加到数据库、检索、修改或删除数据库。 在 Web 应用程序中,这些要求可能是用户添加新帖子、删除帖子或删除他们的帐户,这可能会或可能不会删除他们的帖子。 您为操作数据而执行的操作将取决于应用程序中的特定功能。 例如,您可能不希望用户添加没有标题的帖子。

Flask 是一个轻量级的 Python Web 框架,它提供了有用的工具和功能,用于用 Python 语言创建 Web 应用程序。 PostgreSQL 或 Postgres 是一个关系数据库管理系统,它提供了 SQL 查询语言的实现。 它符合标准并具有许多高级功能,例如可靠的事务和无读锁的并发性。

在本教程中,您将构建一个小型书评 Web 应用程序,演示如何使用 psycopg2 库,这是一个 PostgreSQL 数据库适配器,允许您在 Python 中与您的 PostgreSQL 数据库进行交互。 您将在 Flask 中使用它来执行基本任务,例如连接到数据库服务器、创建表、向表中插入数据以及从表中检索数据。

先决条件

第 1 步 — 创建 PostgreSQL 数据库和用户

在这一步中,您将为 Flask 应用程序创建一个名为 flask_db 的数据库和一个名为 sammy 的数据库用户。

在 Postgres 安装期间,创建了一个名为 postgres 的操作系统用户,以对应于 postgres PostgreSQL 管理用户。 您需要使用此用户来执行管理任务。 您可以使用 sudo 并使用 -iu 选项传入用户名。

使用以下命令登录到交互式 Postgres 会话:

sudo -iu postgres psql

您将收到一个 PostgreSQL 提示,您可以在其中设置您的要求。

首先,为您的项目创建一个数据库:

CREATE DATABASE flask_db;

注意: 每个 Postgres 语句都必须以分号结尾,因此如果遇到问题,请确保命令以分号结尾。


接下来,为我们的项目创建一个数据库用户。 确保选择安全密码:

CREATE USER sammy WITH PASSWORD 'password';

然后给这个新用户访问权限来管理你的新数据库:

GRANT ALL PRIVILEGES ON DATABASE flask_db TO sammy;

要确认数据库已创建,请键入以下命令获取数据库列表:

\l

您将在数据库列表中看到 flask_db

完成后,键入以下命令退出 PostgreSQL 提示:

\q

Postgres 现在已设置好,您可以使用 psycopg2 库通过 Python 连接和管理其数据库信息。 接下来,您将与 Flask 包一起安装这个库。

第 2 步 — 安装 Flask 和 psycopg2

在这一步中,您将安装 Flask 和 psycopg2 库,以便您可以使用 Python 与数据库进行交互。

激活虚拟环境后,使用 pip 安装 Flask 和 psycopg2 库:

pip install Flask psycopg2-binary

安装成功完成后,您将在输出末尾看到类似于以下内容的行:

Output
Successfully installed Flask-2.0.2 Jinja2-3.0.3 MarkupSafe-2.0.1 Werkzeug-2.0.2 click-8.0.3 itsdangerous-2.0.1 psycopg2-binary-2.9.2

您现在已在虚拟环境中安装了所需的软件包。 接下来,您将连接并设置您的数据库。

第 3 步 — 设置数据库

在这一步中,您将在 flask_app 项目目录中创建一个 Python 文件以连接到 flask_db 数据库,创建一个用于存储书籍的表,并在其中插入一些带有评论的书籍。

首先激活您的编程环境,在您的 flask_app 目录中打开一个名为 init_db.py 的新文件。

nano init_db.py

该文件将打开与 flask_db 数据库的连接,创建一个名为 books 的表,并使用示例数据填充该表。 向其中添加以下代码:

烧瓶应用程序/init_db.py

import os
import psycopg2

conn = psycopg2.connect(
        host="localhost",
        database="flask_db",
        user=os.environ['DB_USERNAME'],
        password=os.environ['DB_PASSWORD'])

# Open a cursor to perform database operations
cur = conn.cursor()

# Execute a command: this creates a new table
cur.execute('DROP TABLE IF EXISTS books;')
cur.execute('CREATE TABLE books (id serial PRIMARY KEY,'
                                 'title varchar (150) NOT NULL,'
                                 'author varchar (50) NOT NULL,'
                                 'pages_num integer NOT NULL,'
                                 'review text,'
                                 'date_added date DEFAULT CURRENT_TIMESTAMP);'
                                 )

# Insert data into the table

cur.execute('INSERT INTO books (title, author, pages_num, review)'
            'VALUES (%s, %s, %s, %s)',
            ('A Tale of Two Cities',
             'Charles Dickens',
             489,
             'A great classic!')
            )


cur.execute('INSERT INTO books (title, author, pages_num, review)'
            'VALUES (%s, %s, %s, %s)',
            ('Anna Karenina',
             'Leo Tolstoy',
             864,
             'Another great classic!')
            )

conn.commit()

cur.close()
conn.close()

保存并关闭文件。

在此文件中,您首先导入 os 模块,该模块将用于访问环境变量,您将在其中存储数据库用户名和密码,以便它们在源代码中不可见。

您导入 psycopg2 库。 然后使用 psycopg2.connect() 函数打开与 flask_db 数据库的连接。 您指定主机,在本例中为 localhost。 您将数据库名称传递给 database 参数。

您通过 os.environ 对象提供您的用户名和密码,这使您可以访问您在编程环境中设置的环境变量。 您将数据库用户名存储在名为 DB_USERNAME 的环境变量中,并将密码存储在名为 DB_PASSWORD 的环境变量中。 这允许您将用户名和密码存储在源代码之外,这样当源代码保存在源代码控制中或上传到 Internet 上的服务器时,您的敏感信息就不会泄露。 即使攻击者可以访问您的源代码,他们也无法访问数据库。

您使用 connection.cursor() 方法创建一个名为 cur 的游标,该方法允许 Python 代码在数据库会话中执行 PostgreSQL 命令。

如果 books 表已经存在,您可以使用光标的 execute() 方法删除它。 这避免了存在另一个名为 books 的表的可能性,这可能会导致令人困惑的行为(例如,如果它具有不同的列)。 这里不是这样,因为你还没有创建表,所以不会执行 SQL 命令。 请注意,每当您执行此 init_db.py 文件时,这将删除所有现有数据。 出于我们的目的,您将只执行一次此文件以启动数据库,但您可能希望再次执行它以删除您插入的任何数据并再次从初始样本数据开始。

然后使用 CREATE TABLE books 创建一个名为 books 的表,其中包含以下列:

  • idserial类型的ID,为自增整数。 此列表示您使用 PRIMARY KEY 关键字指定的 主键 。 数据库将为每个条目分配一个唯一值给这个键。
  • titlevarchar类型的书名,是一种有限制的变长字符类型。 varchar (150) 表示标题最长可达 150 个字符。 NOT NULL 表示该列不能为空。
  • author:书的作者,不超过50个字符。 NOT NULL 表示该列不能为空。
  • pages_num:一个整数,代表书的页数。 NOT NULL 表示该列不能为空。
  • review:书评。 text 类型表示评论可以是任意长度的文本。
  • date_added:书籍添加到表格的日期。 DEFAULT 将该列的默认值设置为 CURRENT_TIMESTAMP,即图书添加到数据库的时间。 就像 id 一样,您不需要为此列指定值,因为它会自动填充。

创建表格后,您使用光标的 execute() 方法将两本书插入表格,查尔斯狄更斯的 A Tale of Two Cities 和 Leo 的 Anna Karenina托尔斯泰。 您使用 %s 占位符将值传递给 SQL 语句。 psycopg2 以防止 SQL 注入攻击 的方式在后台处理插入。

完成将书籍数据插入到表中后,使用 connection.commit() 方法提交事务并将更改应用到数据库。 然后用 cur.close() 关闭光标,然后用 conn.close() 关闭连接来清理。

对于要建立的数据库连接,通过运行以下命令设置 DB_USERNAMEDB_PASSWORD 环境变量。 请记住使用您自己的用户名和密码:

export DB_USERNAME="sammy"
export DB_PASSWORD="password"

现在,使用 python 命令在终端中运行您的 init_db.py 文件:

python init_db.py

一旦文件完成执行且没有错误,一个新的 books 表将被添加到您的 flask_db 数据库中。

登录到交互式 Postgres 会话以查看新的 books 表。

sudo -iu postgres psql

使用 \c 命令连接到 flask_db 数据库:

\c flask_db

然后使用 SELECT 语句从 books 表中获取书籍的标题和作者:

SELECT title, author FROM books;

您将看到如下输出:

        title         |      author
----------------------+------------------
 A Tale of Two Cities | Charles Dickens
 Anna Karenina        | Leo Tolstoy

使用 \q 退出交互会话。

接下来,您将创建一个小型 Flask 应用程序,连接到数据库,检索您插入到数据库中的两篇书评,并将它们显示在索引页面上。

第 4 步 - 展示书籍

在这一步中,您将创建一个带有索引页面的 Flask 应用程序,该页面检索数据库中的书籍并显示它们。

激活编程环境并安装 Flask 后,在 flask_app 目录中打开一个名为 app.py 的文件进行编辑:

nano app.py

该文件将设置您的数据库连接并创建一个单一的 Flask 路由来使用该连接。 将以下代码添加到文件中:

烧瓶应用程序/app.py

import os
import psycopg2
from flask import Flask, render_template

app = Flask(__name__)

def get_db_connection():
    conn = psycopg2.connect(host='localhost',
                            database='flask_db',
                            user=os.environ['DB_USERNAME'],
                            password=os.environ['DB_PASSWORD'])
    return conn


@app.route('/')
def index():
    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute('SELECT * FROM books;')
    books = cur.fetchall()
    cur.close()
    conn.close()
    return render_template('index.html', books=books)

保存并关闭文件。

在这里,您从 flask 包中导入 os 模块、psycopg2 库和 Flask 类和 render_template()。 你创建了一个名为 app 的 Flask 应用程序实例。

您定义了一个名为 get_db_connection() 的函数,它使用您存储在 DB_USERNAMEDB_PASSWORD 环境变量中的用户名和密码打开到 flask_db 数据库的连接。 该函数返回您将用于访问数据库的 conn 连接对象。

然后使用 app.route() 装饰器创建一个主 / 路由和一个 index() 视图函数。 在 index() 视图函数中,使用 get_db_connection() 函数打开数据库连接,创建游标,然后执行 SELECT * FROM books; SQL 语句来获取在数据库。 您使用 fetchall() 方法将数据保存在名为 books 的变量中。 然后关闭光标和连接。 最后,您返回对 render_template() 函数的调用,以呈现一个名为 index.html 的模板文件,并将您从数据库中的 books 变量中获取的书籍列表传递给它。

要在索引页面上显示数据库中的书籍,您将首先创建一个基本模板,该模板将包含其他模板也将使用的所有基本 HTML 代码,以避免代码重复。 然后,您将创建在 index() 函数中呈现的 index.html 模板文件。 要了解有关模板的更多信息,请参阅 如何在 Flask 应用程序中使用模板

创建一个 templates 目录,然后打开一个名为 base.html 的新模板:

mkdir templates
nano templates/base.html

base.html 文件中添加以下代码:

flask_app/templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %}- FlaskApp</title>
    <style>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }

        .book {
            padding: 20px;
            margin: 10px;
            background-color: #f7f4f4;
        }

        .review {
                margin-left: 50px;
                font-size: 20px;
        }

    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

保存并关闭文件。

此基本模板包含您需要在其他模板中重用的所有 HTML 样板。 title 块将被替换为每个页面设置标题,content 块将被替换为每个页面的内容。 导航栏有两个链接,一个用于索引页面,您使用 url_for() 帮助函数链接到 index() 视图函数,另一个用于 关于 页面如果您选择在您的应用程序中包含一个。

接下来,打开一个名为 index.html 的模板。 这是您在 app.py 文件中引用的模板:

nano templates/index.html

向其中添加以下代码:

flask_app/templates/index.html

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Books {% endblock %}</h1>
    {% for book in books %}
        <div class='book'>
            <h3>#{{ book[0] }} - {{ book[1] }} BY {{ book[2] }}</h3>
            <i><p>({{ book[3] }} pages)</p></i>
            <p class='review'>{{ book[4] }}</p>
            <i><p>Added {{ book[5] }}</p></i>
        </div>
    {% endfor %}
{% endblock %}

保存并关闭文件。

在此文件中,您扩展基本模板,并替换 content 块的内容。 您使用的 <h1> 标题也用作标题。

您在 {% for book in books %} 行中使用 Jinja for loop 浏览 books 列表中的每本书。 您显示图书 ID,这是使用 book[0] 的第一项。 然后显示书名、作者、页数、评论和添加书的日期。

在激活虚拟环境的 flask_app 目录中,使用 FLASK_APP 环境变量告诉 Flask 应用程序(在本例中为 app.py)。 然后将 FLASK_ENV 环境变量设置为 development 以在开发模式下运行应用程序并访问调试器。 有关 Flask 调试器的更多信息,请参阅 如何处理 Flask 应用程序中的错误 。 使用以下命令执行此操作:

export FLASK_APP=app
export FLASK_ENV=development

如果您尚未设置 DB_USERNAMEDB_PASSWORD 环境变量,请确保设置:

export DB_USERNAME="sammy"
export DB_PASSWORD="password"

接下来,运行应用程序:

flask run

在开发服务器运行的情况下,使用浏览器访问以下 URL:

http://127.0.0.1:5000/

您将在第一次启动时看到您添加到数据库中的书籍。

您已经在索引页面上显示了数据库中的书籍。 您现在需要允许用户添加新书。 您将在下一步中添加用于添加书籍的新路径。

第 5 步 — 添加新书

在此步骤中,您将创建一条新路径,用于将新书籍和评论添加到数据库中。

您将添加一个带有 Web 表单的页面,用户可以在其中输入书名、书作者、页数和书评。

让开发服务器保持运行并打开一个新的终端窗口。

首先,打开您的 app.py 文件:

nano app.py

为了处理 web 表单,你需要从 flask 包中导入一些东西:

  • 访问提交数据的全局 request 对象。
  • url_for() 函数用于生成 URL。
  • redirect() 函数在将书籍添加到数据库后将用户重定向到索引页面。

将这些导入添加到文件的第一行:

烧瓶应用程序/app.py

from flask import Flask, render_template, request, url_for, redirect

# ...

然后在app.py文件末尾添加如下路由:

烧瓶应用程序/app.py

# ...


@app.route('/create/', methods=('GET', 'POST'))
def create():
    return render_template('create.html')

保存并关闭文件。

在此路由中,您将元组 ('GET', 'POST') 传递给 methods 参数以允许 GET 和 POST 请求。 GET 请求用于从服务器检索数据。 POST 请求用于将数据发布到特定路由。 默认情况下,只允许 GET 请求。 当用户第一次使用 GET 请求请求 /create 路由时,将呈现一个名为 create.html 的模板文件。 您稍后将编辑此路由以处理用户填写和提交 Web 表单以添加新书时的 POST 请求。

打开新的 create.html 模板:

nano templates/create.html

向其中添加以下代码:

flask_app/templates/create.html

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Add a New Book {% endblock %}</h1>
    <form method="post">
        <p>
            <label for="title">Title</label>
            <input type="text" name="title"
                   placeholder="Book title">
            </input>
        </p>

        <p>
            <label for="author">Author</label>
            <input type="text" name="author"
                   placeholder="Book author">
            </input>
        </p>

        <p>
            <label for="pages_num">Number of pages</label>
            <input type="number" name="pages_num"
                   placeholder="Number of pages">
            </input>
        </p>
        <p>
        <label for="review">Review</label>
        <br>
        <textarea name="review"
                  placeholder="Review"
                  rows="15"
                  cols="60"
                  ></textarea>
        </p>
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
{% endblock %}

保存并关闭文件。

您扩展基本模板,将标题设置为标题,并使用 <form> 标记并将属性 method 设置为 post 以指示表单将提交 POST 请求.

您有一个名称为 title 的文本字段,您将使用它来访问 /create 路由中的标题数据。

您有一个用于作者的文本字段、一个用于页数的数字字段和一个用于书评的文本区域。

最后,表单末尾有一个 Submit 按钮。

现在,随着开发服务器的运行,使用浏览器导航到 /create 路由:

http://127.0.0.1:5000/create

您将看到一个 Add a New Book 页面,其中有一个输入字段用于书名,一个用于其作者,一个用于该书的页数,一个用于书评的文本区域,以及一个提交按钮。

如果您填写并提交表单,向服务器发送 POST 请求,则不会发生任何事情,因为您没有处理 /create 路由上的 POST 请求。

打开 app.py 处理用户提交的 POST 请求:

nano app.py

编辑 /create 路线,如下所示:

烧瓶应用程序/app.py

# ...

@app.route('/create/', methods=('GET', 'POST'))
def create():
    if request.method == 'POST':
        title = request.form['title']
        author = request.form['author']
        pages_num = int(request.form['pages_num'])
        review = request.form['review']

        conn = get_db_connection()
        cur = conn.cursor()
        cur.execute('INSERT INTO books (title, author, pages_num, review)'
                    'VALUES (%s, %s, %s, %s)',
                    (title, author, pages_num, review))
        conn.commit()
        cur.close()
        conn.close()
        return redirect(url_for('index'))

    return render_template('create.html')

保存并关闭文件。

您在 if request.method == 'POST' 条件内处理 POST 请求。 您从 request.form 对象中提取标题、作者、页数和用户提交的评论。

您使用 get_db_connection() 函数打开一个数据库,并创建一个游标。 然后执行 INSERT INTO SQL 语句插入标题、作者、页数,并查看提交到 books 表的用户。

您提交事务并关闭游标和连接。

最后,您将用户重定向到索引页面,他们可以在其中看到现有书籍下方新添加的书籍。

在开发服务器运行的情况下,使用浏览器导航到 /create 路由:

http://127.0.0.1:5000/create

用一些数据填写表格并提交。

您将被重定向到索引页面,您将在其中看到您的新书评。

接下来,您将在导航栏中添加指向“创建”页面的链接。 打开base.html

nano templates/base.html

编辑该文件,如下所示:

flask_app/templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %} - FlaskApp</title>
    <style>
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }

        .book {
            padding: 20px;
            margin: 10px;
            background-color: #f7f4f4;
        }

        .review {
                margin-left: 50px;
                font-size: 20px;
        }

    </style>
</head>
<body>
    <nav>
        <a href="{{ url_for('index') }}">FlaskApp</a>
        <a href="{{ url_for('create') }}">Create</a>
        <a href="#">About</a>
    </nav>
    <hr>
    <div class="content">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

保存并关闭文件。

在这里,您将新的 <a> 链接添加到指向“创建”页面的导航栏。

刷新您的索引页面,您将在导航栏中看到新链接。

您现在有一个带有 Web 表单的页面,用于添加新书评。 有关 Web 表单的更多信息,请参阅 如何在 Flask 应用程序中使用 Web 表单。 有关管理 Web 表单的更高级和更安全的方法,请参阅 How To Use and Validate Web Forms with Flask-WTF

结论

您为书评构建了一个与 PostgreSQL 数据库通信的小型 Web 应用程序。 您在 Flask 应用程序中具有基本的数据库功能,例如向数据库添加新数据、检索数据以及在页面上显示它。

如果您想了解更多关于 Flask 的信息,请查看 Flask 系列 中的其他教程。