教程:构建简单的博客#

在本教程中,我们将构建一个简单的博客,其条目存储在数据库中。然后,我们将渲染这些帖子在服务器上并直接向用户提供 HTML。

本教程旨在作为在 Quart 中构建服务器渲染网站的入门介绍。如果你想跳到最后,代码位于 Github 上。

1:创建项目#

我们需要为我们的博客服务器创建一个项目,我喜欢使用 Poetry 来完成此操作。Poetry 通过 pip(或通过 Brew)安装。

pip install poetry

然后,我们可以使用 Poetry 创建一个新的博客项目

poetry new --src blog

现在可以在 blog 目录中开发我们的项目,所有后续命令都应在运行 blog 目录中执行。

2:添加依赖项#

要开始,我们只需要 Quart 来构建博客服务器,我们可以通过运行以下命令将其安装为项目的依赖项。

poetry add quart

Poetry 将通过运行以下命令来确保此依赖项存在且路径正确。

poetry install

3:创建应用程序#

我们需要一个 Quart 应用程序作为我们的 Web 服务器,这是通过以下添加到 src/blog/__init__.py 中创建的。

src/blog/__init__.py#
from quart import Quart

app = Quart(__name__)

def run() -> None:
    app.run()

为了使应用程序易于运行,我们可以通过以下添加到 pyproject.toml 中,从诗歌脚本调用 run 方法。

pyproject.toml#
[tool.poetry.scripts]
start = "blog:run"

这允许以下命令启动应用程序

poetry run start

4:创建数据库#

有许多数据库管理系统可供选择,具体取决于需求和要求。在这种情况下,我们只需要最简单的系统,Python 的标准库包含 SQLite,使其成为最容易的。

要初始化数据库,我们需要以下 SQL 来创建正确的表,并将其添加到 src/blog/schema.sql

src/blog/schema.sql#
DROP TABLE IF EXISTS post;
CREATE TABLE post (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  'text' TEXT NOT NULL
);

接下来,我们需要能够在命令中创建数据库,我们可以通过将命令代码添加到 src/blog/__init__.py 中来实现。

src/blog/__init__.py#
from pathlib import Path
from sqlite3 import dbapi2 as sqlite3

app.config.update({
  "DATABASE": app.root_path / "blog.db",
})

def _connect_db():
    engine = sqlite3.connect(app.config["DATABASE"])
    engine.row_factory = sqlite3.Row
    return engine

def init_db():
    db = _connect_db()
    with open(app.root_path / "schema.sql", mode="r") as file_:
        db.cursor().executescript(file_.read())
    db.commit()

接下来,我们需要更新 pyproject.toml 中的诗歌脚本,如下所示。

pyproject.toml#
[tool.poetry.scripts]
init_db = "blog:init_db"
start = "blog:run"

现在,我们可以运行以下命令来创建和更新数据库。

poetry run init_db

警告

运行此命令将擦除任何现有数据。

5:在数据库中显示帖子#

现在,我们可以显示数据库中存在的帖子。为此,我们首先需要一个模板来将帖子渲染为 HTML。这如下所示,应添加到 src/blog/templates/posts.html

src/blog/templates/posts.html#
<main>
  {% for post in posts %}
    <article>
      <h2>{{ post.title }}</h2>
      <p>{{ post.text|safe }}</p>
    </article>
  {% else %}
    <p>No posts available</p>
  {% endfor %}
</main>

现在,我们需要一个路由来查询数据库,检索消息并渲染模板。如以下代码所示,应将其添加到 src/blog/__init__.py

src/blog/__init__.py#
from quart import render_template, g

def _get_db():
    if not hasattr(g, "sqlite_db"):
        g.sqlite_db = _connect_db()
    return g.sqlite_db

@app.get("/")
async def posts():
    db = _get_db()
    cur = db.execute(
        """SELECT title, text
             FROM post
         ORDER BY id DESC""",
    )
    posts = cur.fetchall()
    return await render_template("posts.html", posts=posts)

6:创建新帖子#

要创建博客帖子,我们首先需要一个表单,用户可以在其中输入帖子详细信息。这是通过以下模板代码完成的,应将其添加到 src/blog/templates/create.html

src/blog/templates/create.html#
<form method="POST" style="display: flex; flex-direction: column; gap: 8px; max-width:400px">
  <label>Title: <input type="text" size="30" name="title" /></label>
  <label>Text: <textarea name="text" rows="5" cols="40"></textarea></label>
  <button type="submit">Create</button>
</form>

样式确保表单的元素以垂直方式排列,并带有间隙和合理的最大宽度。

要允许访问者创建博客帖子,我们需要接受此表单在浏览器中生成的 POST 请求。为此,以下内容应添加到 src/blog/__init__.py

src/blog/__init__.py#
from quart import redirect, request, url_for

@app.route("/create/", methods=["GET", "POST"])
async def create():
    if request.method == "POST":
        db = _get_db()
        form = await request.form
        db.execute(
            "INSERT INTO post (title, text) VALUES (?, ?)",
            [form["title"], form["text"]],
        )
        db.commit()
        return redirect(url_for("posts"))
    else:
        return await render_template("create.html")

此路由处理程序将针对 GET 请求(例如,通过浏览器中的导航)渲染创建表单。但是,对于 POST 请求,它将提取表单数据以创建博客帖子,然后将用户重定向到包含帖子的页面。

7:测试#

要测试我们的应用程序,我们需要检查是否可以创建博客帖子,以及创建后是否在帖子页面上显示。首先,我们需要创建一个用于测试的临时数据库,我们可以使用放置在 tests/conftest.py 中的 pytest 夹具来实现。

tests/conftest.py#
import pytest

from blog import app, init_db

@pytest.fixture(autouse=True)
def configure_db(tmpdir):
    app.config['DATABASE'] = str(tmpdir.join('blog.db'))
    init_db()

此夹具将在我们的测试之前自动运行,从而设置一个我们可以在测试中使用的数据库。

要测试创建和显示,我们可以将以下内容添加到 tests/test_blog.py

tests/test_blog.py#
from blog import app

async def test_create_post():
    test_client = app.test_client()
    response = await test_client.post("/create/", form={"title": "Post", "text": "Text"})
    assert response.status_code == 302
    response = await test_client.get("/")
    text = await response.get_data()
    assert b"<h2>Post</h2>" in text
    assert b"<p>Text</p>" in text

由于测试是一个异步函数,我们需要通过运行以下命令安装 pytest-asyncio

poetry add --dev pytest-asyncio

安装完成后,需要通过以下添加到 pyproject.toml 中来配置。

[tool.pytest.ini_options]
asyncio_mode = "auto"

最后,我们可以通过以下命令运行测试。

poetry run pytest tests/

如果您在 Quart 示例文件夹中运行此命令,则需要添加 -c pyproject.toml 选项以防止 pytest 使用 Quart pytest 配置。

8:总结#

我们构建了一个简单的数据库支持的博客服务器。这应该是构建任何类型的服务器渲染应用程序的良好起点。