如何在 Flask 應用程序中使用 PostgreSQL 數據庫

作者選擇了自由開源基金作為撰寫以捐贈計劃的一部分收到捐款。

介紹

在網絡應用程序中,通常需要使用數據庫,這是一個組織良好的數據集。您使用數據庫來存儲和維護可以有效檢索和操作的持久性數據。例如,在社交媒體應用程序中,您有一個數據庫,用於存儲用戶數據(個人信息、帖子、評論、關注者),以一種可以有效操作的方式存儲。您可以根據不同的要求和條件向數據庫中添加數據,檢索它,修改它或刪除它。在 Web 應用程序中,這些要求可能是用戶添加新帖子、刪除帖子或刪除他們的帳戶,這可能會刪除他們的帖子,也可能不會。您執行的操作來操作數據將取決於應用程序中的具體功能。例如,您可能不希望用戶添加沒有標題的帖子。

Flask 是一個輕量級的 Python 網頁框架,提供了在 Python 語言中創建 Web 應用程序所需的有用工具和功能。PostgreSQL,或稱 Postgres,是一個關聯式數據庫管理系統,提供了對 SQL 查詢語言的實現。它符合標準,具有許多高級功能,如可靠的事務和無需讀取鎖的並發。

在本教程中,您將構建一個小型的書評 Web 應用程序,演示如何使用 psycopg2 函式庫,這是一個 PostgreSQL 數據庫適配器,允許您在 Python 中與 PostgreSQL 數據庫進行交互。您將與 Flask 一起使用它來執行基本任務,如連接到數據庫服務器,創建表,將數據插入表中,以及從表中檢索數據。

先決條件

  • 本地 Python 3 編程環境。請參照系列文章 如何安裝和設置本地 Python 3 編程環境 中的教程進行操作。在本教程中,專案目錄稱為 flask_app

  • 對基本的 Flask 概念有一定的了解,比如路由、視圖函數和模板。如果你對 Flask 不太熟悉,可以查看《使用 Flask 和 Python 創建你的第一個 Web 應用》和《在 Flask 應用中使用模板》。

  • 對基本的 HTML 概念有一定的了解。你可以閱讀我們的《如何使用 HTML 構建網站》教程系列來了解背景知識。

  • 在您的本地机器上安装了PostgreSQL,并且可以访问PostgreSQL提示符。请按照在Ubuntu 20.04上安装和使用PostgreSQL设置您的PostgreSQL数据库。

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

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

在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库通过Python连接和管理其数据库信息。接下来,您将安装此库以及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 資料庫,創建一個用於存儲書籍的表,並將一些帶有評論的書籍插入其中。

首先,啟動您的程式開發環境,打開一個名為 init_db.py 的新檔案,位於您的 flask_app 目錄中。

  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的表格,其中包含以下列:

  • id:一個serial類型的ID,這是一個自動遞增的整數。此列表示您使用PRIMARY KEY關鍵字指定的主鍵。數據庫將為每個條目分配一個唯一值。
  • title:書籍的標題,類型為varchar,這是一種帶有限制的可變長字符類型。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 後,在您的 flask_app 目錄中打開一個名為 app.py 的文件進行編輯:

  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> 标题,它也充当标题。

你在這一行中使用了一個Jinja迴圈{% for book in books %}來遍歷books列表中的每一本書。你顯示了書的ID,即第一個項目,使用book[0]。然後顯示書的標題、作者、頁數、評論以及書籍添加的日期。

當你位於flask_app目錄並啟用了你的虛擬環境時,告訴Flask關於應用程序(在這種情況下是app.py),使用FLASK_APP環境變量。然後將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 — 新增書籍

在這一步中,您將創建一個新的路由,用於將新書籍和評論添加到數據庫中。

您將添加一個帶有網頁表單的頁面,用戶在其中輸入書籍標題、作者、頁數和書籍評論。

保持開發服務器運行,並打開一個新的終端窗口。

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

  1. nano app.py

為了處理網頁表單,您需要從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 的模板文件。稍後您將編輯此路由以處理用戶填寫並提交添加新書籍的網絡表單的 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>

保存並關閉文件。

在這裡,您可以將一個新的<a>鏈接添加到導航欄,指向創建頁面。

刷新您的索引頁,您將在導航欄中看到新的鏈接。

現在,您有一個包含網頁表單的頁面,用於添加新的書評。有關網頁表單的更多信息,請參見如何在Flask應用程序中使用Web表單。對於更高級和更安全的管理網頁表單的方法,請參見如何使用Flask-WTF驗證Web表單

結論

您已經為書評構建了一個小型Web應用程序,該應用程序與PostgreSQL數據庫通信。您的Flask應用程序具有基本的數據庫功能,例如將新數據添加到數據庫中,檢索數據並在頁面上顯示它。

如果您想閱讀更多關於Flask的信息,請查看Flask系列中的其他教程

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