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

作者选择了自由开源基金作为为捐赠而写计划的一部分来接受捐赠。

介绍

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

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

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

先决条件

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

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

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

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

  1. sudo -iu postgres psql

您将获得一个PostgreSQL提示符,您可以在其中设置您的要求。

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

  1. CREATE DATABASE flask_db;

注意:每个Postgres语句必须以分号结束,所以如果你遇到问题,请确保你的命令以分号结束。

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

  1. CREATE USER sammy WITH PASSWORD 'password';

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

  1. GRANT ALL PRIVILEGES ON DATABASE flask_db TO sammy;

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

  1. \l

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

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

  1. \q

现在Postgres已设置完成,你可以使用psycopg2库连接并管理其数据库信息。接下来,你将在Flask包旁边安装这个库。

步骤 2 — 安装 Flask 和 psycopg2

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

在激活虚拟环境的情况下,使用pip安装Flask和psycopg2库:

  1. 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的新文件。

  1. nano init_db.py

这个文件将会连接到flask_db数据库,创建一个名为books的表,并使用样本数据填充表格。将以下代码添加到文件中:

flask_app/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'])

# 打开一个游标以执行数据库操作
cur = conn.cursor()

# 执行命令:这将创建一个新表
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);'
                                 )

# 将数据插入表中

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数据库的连接。您指定了主机,这在这种情况下是本地主机。您将数据库名称传递给database参数。

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

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

您使用游标的execute()方法来删除books表,如果它已经存在。这样可以避免另一个名为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() 方法将两本书插入表中,《双城记》,作者是查尔斯·狄更斯,以及 《安娜·卡列尼娜》,作者是列夫·托尔斯泰。 您使用 %s 占位符将值传递给 SQL 语句。 psycopg2 在后台处理插入,以防止 SQL 注入攻击

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

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

  1. export DB_USERNAME="sammy"
  2. export DB_PASSWORD="password"

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

  1. python init_db.py

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

登录到一个交互式的Postgres会话中,检查新的books表。

  1. sudo -iu postgres psql

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

  1. \c flask_db

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

  1. SELECT title, author FROM books;

您将会看到如下输出:

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

使用\q退出交互式会话。

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

步骤 4 — 显示书籍

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

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

  1. nano app.py

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

flask_app/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)

保存并关闭文件。

在这里,您导入os模块,psycopg2库,以及Flask类和flask包中的render_template()函数。您创建了一个名为app的Flask应用实例。

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

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

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

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

  1. mkdir templates
  2. 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文件中引用的模板:

  1. 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循环,以遍历books列表中的每一本书。您显示了书的ID,这是使用book[0]来获取的第一项。然后您显示了书的标题、作者、页数、评论以及添加书的日期。

当您在激活了虚拟环境的flask_app目录中时,通过设置FLASK_APP环境变量,告诉Flask应用程序(在这种情况下是app.py)。然后将FLASK_ENV环境变量设置为development以在开发模式下运行应用程序,并获得调试器的访问权限。有关Flask调试器的更多信息,请参见如何在Flask应用程序中处理错误。使用以下命令执行此操作:

  1. export FLASK_APP=app
  2. export FLASK_ENV=development

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

  1. export DB_USERNAME="sammy"
  2. export DB_PASSWORD="password"

接下来,运行应用程序:

  1. flask run

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

http://127.0.0.1:5000/

您将在首次初始化时看到您添加到数据库的书籍。

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

步骤5 — 添加新书

在这一步中,您将为向数据库添加新书和评论创建一个新路由。

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

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

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

  1. nano app.py

为处理 Web 表单,您需要从 flask 包中导入一些内容:

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

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

flask_app/app.py

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

# ...

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

flask_app/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 模板:

  1. 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 路由中的标题数据。

您为作者设置了一个文本字段,为书籍页数设置了一个数字字段,并为书评设置了一个文本区域。

最后,您在表单末尾有一个 提交 按钮。

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

http://127.0.0.1:5000/create

您将看到一个 添加新书籍 页面,其中包含一个用于书名的输入字段,一个用于作者的输入字段,一个用于书籍页数的输入字段,一个用于书评的文本区域,以及一个 提交 按钮。

如果您填写表单并提交它,发送一个POST请求到服务器,但什么也不会发生,因为您没有在/create路由上处理POST请求。

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

  1. nano app.py

编辑/create路由如下:

flask_app/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

  1. 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>

保存并关闭文件。

在导航栏中添加一个指向创建页面的新链接

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

现在您有一个包含网页表单的页面,用于添加新的书评。有关网页表单的更多信息,请参阅如何在Flask应用程序中使用Web表单。对于管理网页表单的更高级和更安全的方法,请参阅如何使用Flask-WTF验证Web表单

结论

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

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

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-a-postgresql-database-in-a-flask-application