跳转至

第4章:图书馆 API

我们的图书馆网站目前由一个显示数据库中所有书籍的单个页面组成。为了将其转变为 Web API,我们将安装 Django REST Framework 并创建一个新的 URL,作为输出所有可用书籍的 API 端点。如果你回忆一下第2章,Web API 不会输出带有 HTML、CSS 和 JavaScript 的传统网页。相反,它只是纯数据(通常为 JSON 格式)和 accompanying HTTP 动词,用于指定允许哪些用户操作。在这种情况下,API 用户只能读取内容,他们无法以任何方式更新它,尽管我们将在后面的章节中学习如何做到这一点。

Django REST Framework

正如我们在第1章中看到的,添加 Django REST Framework 就像安装任何其他第三方应用程序一样。如果本地服务器仍在运行,请确保使用 Control+c 退出。然后在命令行中输入以下内容。

Shell

(.venv) > python -m pip install djangorestframework~=3.13.0

我们必须在 django_project/settings.py 文件中正式通知 Django 新安装。向下滚动到 INSTALLED_APPS 部分并添加 rest_framework。我喜欢区分第三方应用程序和本地应用程序,因为在大多数项目中,应用程序的数量增长很快。


配置 INSTALLED_APPS

Code

# django_project/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    # 3rd party
    "rest_framework",  # new
    # Local
    "books.apps.BooksConfig",
]

最终,我们的 Web API 将暴露一个列出所有书籍的 JSON 端点。为此,我们需要一个新的 URL 路由、一个新的视图和一个新的序列化器文件(稍后会详细介绍)。

有多种方法可以组织这些文件。许多专业的 Django 开发人员只会将 API 逻辑包含在相关应用程序中,同时将 URL 放在 /api/ 前缀下。不过就目前而言,为了使 API 逻辑与传统 Django 逻辑清晰分开,我们将为我们的项目创建一个专用的 apis 应用程序。

让我们现在使用 startapp 命令来做到这一点。请记住,应用程序应该始终使用复数名称,因为 Django 会自动在管理员和其他位置添加 s。

Shell

(.venv) > python manage.py startapp apis

然后将其添加到我们的"Local"部分中的 INSTALLED_APPS


更新 INSTALLED_APPS

Code

# django_project/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    # 3rd party
    "rest_framework",
    # Local
    "books.apps.BooksConfig",
    "apis.apps.ApisConfig",  # new
]

apis 应用程序将没有自己的数据库模型,因此无需创建迁移文件并运行 migrate 来更新数据库。事实上,数据库模型是我们完全不需要触及的一个领域,因为这个新的 Web API 旨在暴露现有数据,而不是创建新数据。

URLs 配置

让我们从 URL 配置开始。添加 API 端点就像配置传统的 Django URL 路由一样。在项目级别的 django_project/urls.py 文件中包含 apis 应用程序并配置其 URL 路由,即 api/

Code

# django_project/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("apis.urls")),  # new
    path("", include("books.urls")),
]

然后使用您的文本编辑器创建一个名为 apis/urls.py 的新文件。该文件将导入一个名为 BookAPIView 的未来视图,并将其设置为 "" 的 URL 路由,因此它将出现在 api/。与往常一样,我们也会为其添加一个名称 book_list,这在以后我们想要引用这个特定路由时会有帮助。

Code

# apis/urls.py
from django.urls import path
from .views import BookAPIView

urlpatterns = [
    path("", BookAPIView.as_view(), name="book_list"),
]

全部设置完成。

视图(Views)

在传统的 Django 中,视图用于自定义要发送到模板的数据。Django REST Framework 的视图类似,不同的是最终结果是 JSON 格式的序列化数据,而不是网页的内容!Django REST Framework 的视图依赖于模型、URL 和一个名为序列化器的新文件,我们将在下一节中看到。

Django REST Framework 为常见用例提供了通用视图,我们将在这里使用 ListAPIView 来显示所有书籍。

创建视图

为避免混淆,一些开发人员会将 API 视图文件称为 apiviews.pyapi.py。就个人而言,在专用的 apis 应用程序中工作时,我发现将 Django REST Framework 的视图文件称为 views.py 并不会造成混淆,但在这个问题上意见各不相同。

使用您的文本编辑器更新 apis/views.py 文件,使其如下所示:

Code

# apis/views.py
from rest_framework import generics
from books.models import Book
from .serializers import BookSerializer

class BookAPIView(generics.ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

在顶行,我们导入了 Django REST Framework 的通用视图类、来自 books 应用程序的 Book 模型以及来自 apis 应用程序的序列化器。我们将在下一节中创建这里使用的序列化器 BookSerializer

然后我们创建一个名为 BookAPIView 的视图类,它使用 ListAPIView 创建一个只读端点,用于所有书籍实例。有许多通用视图可用,我们将在后面的章节中进一步探索它们。

我们的视图中只需要两个步骤:指定查询集(即所有可用的书籍),然后指定序列化器类(即 BookSerializer)。

序列化器(Serializers)

我们现在到了最后一步!到目前为止,我们已经为 API 创建了一个 urls.py 文件和一个 views.py 文件。最后——也是最重要的——操作是创建我们的序列化器。

序列化器将复杂的数据(如查询集和模型实例)转换为易于通过互联网使用的格式,通常是 JSON。也可以"反序列化"数据, literally 是相反的过程,即首先验证 JSON 数据,然后将其转换为字典。

Django REST Framework 的真正优点在于其序列化器,它为我们抽象了大部分复杂性。我们将在后面的章节中更深入地介绍序列化和 JSON,但现在的目标是演示使用 Django REST Framework 创建序列化器是多么容易。

在您的文本编辑器中,创建一个名为 apis/serializers.py 的新文件,并按如下方式更新:

Code

# apis/serializers.py
from rest_framework import serializers
from books.models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ("title", "subtitle", "author", "isbn")

在顶行,我们导入了 Django REST Framework 的序列化器类和来自 books 应用程序的 Book 模型。接下来,我们将 Django REST Framework 的 ModelSerializer 扩展为一个 BookSerializer 类,该类指定了我们的数据库模型 Book 以及我们要暴露的数据库字段:titlesubtitleauthorisbn

就这样!我们完成了。通过创建一个新的 URL 路由、一个新的视图和一个序列化器类,我们为我们的图书馆网站创建了一个 API 端点,该端点将以列表格式显示所有现有的书籍。

可浏览的 API

原始 JSON 数据对人类来说并不是特别友好。幸运的是,Django REST Framework 附带了一个内置的可浏览 API,它显示了与给定端点关联的内容和 HTTP 动词。要查看它的实际效果,请使用 runserver 命令启动本地 Web 服务器。

Shell

(.venv) > python manage.py runserver

我们知道 API 端点的位置是 [[http://127.0.0.1:8000/api/,因此在您的](http://127.0.0.1:8000/api/,因此在您的](http://127.0.0.1:8000/api/,因此在您的](http://127.0.0.1:8000/api/`,因此在您的)) Web 浏览器中导航到那里。

Book API

看看那个!Django REST Framework 默认提供这种可视化。它显示页面的 HTTP 状态码(200 表示 OK),指定 Content-Type 是 JSON,并以格式化的方式显示我们单个书籍条目的信息。

如果您单击右上角的"Get"按钮并在下拉列表顶部选择"json",您将看到原始 API 端点的样子。

Book API JSON

不是很吸引人,是吗?数据完全没有格式化,我们也看不到任何关于 HTTP 状态或可允许动词的额外信息。我想你同意 Django REST Framework 的版本更有吸引力。

专业开发人员通常使用第三方工具(如 Postman 或,如果在 Mac 上,Paw)来测试和消耗 API。但就本书的目的而言,内置的可浏览 API 已经足够了。

测试

Django 中的测试依赖于 Python 内置的 unittest 模块和几个有用的 Django 特定扩展。最值得注意的是,Django 带有一个测试客户端,我们可以用它来模拟 GET 或 POST 请求,检查 Web 请求中的重定向链,并检查是否正在使用给定的 Django 模板并具有正确的模板上下文数据。

Django REST Framework 提供了几个额外的辅助类,扩展了 Django 现有的测试框架。其中之一是用于测试从我们的数据库检索 API 数据的 APIClient,它是 Django 默认 Client 的扩展。

由于我们已经在 books/tests.py 中有用于 Book 模型的测试,我们可以专注于测试 API 端点,具体来说就是它使用我们期望的 URL,具有正确的 200 状态码,并包含正确的内容。

使用您的文本编辑器打开 apis/tests.py 文件,并填写以下代码,我们将在下面进行审查。

Code

# apis/tests.py
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from books.models import Book

class APITests(APITestCase):
    @classmethod
    def setUpTestData(cls):
        cls.book = Book.objects.create(
            title="Django for APIs",
            subtitle="Build web APIs with Python and Django",
            author="William S. Vincent",
            isbn="9781735467221",
        )

    def test_api_listview(self):
        response = self.client.get(reverse("book_list"))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(Book.objects.count(), 1)
        self.assertContains(response, self.book)

在顶部,我们导入了 Django 的 reverse 以及来自 Django REST Framework 的 statusAPITestCase。我们还导入了我们的 Book 模型,但请注意,由于我们在 apis 应用程序中,我们必须指定 books 的应用程序名称来导入它。

我们在一个名为 APITests 的新类中扩展了 APITestCase,该类首先配置测试数据。然后我们运行四个不同的检查。首先,我们检查是否使用了名为"book_list"的 URL。其次,我们确认 HTTP 状态码匹配 200。第三,我们检查数据库中是否有一个条目。最后,我们确认响应包含我们创建的书籍对象的所有数据。

确保停止本地服务器并运行测试以确认它通过。

Shell

(.venv) > python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.009s
OK
Destroying test database for alias 'default'...

请注意,输出描述了三个测试通过,因为我们在 books/tests.py 中有两个,在这里有一个。在具有数百甚至数千个测试的较大网站中,性能可能成为问题,有时您可能希望在运行完整网站测试套件之前只检查给定应用程序中的测试。要做到这一点,只需将您希望检查的应用程序名称添加到 python manage.py test 的末尾。

Shell

(.venv) > python manage.py test apis
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.005s
OK
Destroying test database for alias 'default'...

部署

部署 Web API 与部署传统网站几乎相同。我们将在本书中使用 Heroku,因为它提供免费层,并且是广泛使用的平台即服务(PaaS),消除了部署中固有的大部分复杂性。

如果这是您第一次使用 Heroku,您可以在其网站上注册一个免费帐户。填写注册表单后,等待验证电子邮件以确认您的帐户。

安装 Heroku CLI

我们将使用 Heroku 的命令行界面(CLI),以便我们可以从命令行部署。目前,我们在图书馆项目的虚拟环境中操作,但我们希望 Heroku 全局可用,即在我们的机器上的任何地方。一个简单的做法是打开一个新的命令行选项卡(Windows 上是 Control+t,Mac 上是 Command+t),它不在虚拟环境中运行。在这里安装的任何东西都将是全局的。

在 Windows 上,请参阅 Heroku CLI 页面以正确安装 32 位或 64 位版本。在 Mac 上,使用包管理器 Homebrew 进行安装。如果您的机器上还没有 Homebrew,请将 Homebrew 网站上的长命令复制并粘贴到命令行中,然后按 Return。它看起来像这样:

Shell

% /bin/bash -c "$(curl -fsSL [https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh](https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh))"

接下来,将以下内容复制并粘贴到命令行中,然后按 Return 安装 Heroku CLI。

Shell

% brew tap heroku/brew && brew install heroku

如果您使用的是新的 M1 芯片 Apple 计算机,您可能会收到类似"Bad CPU type in executable"的错误。安装 Rosetta 2 将解决问题。

安装完成后,您可以关闭新的命令行选项卡并返回到初始选项卡,其中 pages 虚拟环境处于活动状态。

要验证安装是否正确,请运行 heroku --version。应该输出已安装的 Heroku CLI 的当前版本。

Shell

(.venv) > heroku --version
heroku/7.59.2 darwin-x64 node-v12.21.0

如果您在 Windows 的 VSCode 上看到关于"the term 'heroku' is not recognized..."的错误消息,这很可能是权限问题。尝试直接打开 PowerShell 应用程序并执行 heroku --version。它应该正常工作。不幸的是,VSCode 终端 Shell 有时会有一些细微的问题。

如果您收到关于 Heroku 版本过时的"警告",请尝试运行 heroku update 以安装最新版本。

一旦您看到了安装的 Heroku 版本,请键入命令 heroku login 并使用您刚刚设置的 Heroku 的电子邮件和密码。

Shell

(.venv) > heroku login
Enter your Heroku credentials:
Email: will@wsvincent.com
Password: *********************************
Logged in as will@wsvincent.com

您可能需要在 Heroku 网站上验证您的凭据,但一旦终端 Shell 确认您的登录,您就可以继续了。

静态文件

静态文件在 Django 项目中正确部署有些棘手,但好消息是 Django API 的过程基本相同。尽管目前我们没有任何自己的静态文件,但 Django 管理员和 Django REST Framework 可浏览 API 中包含了静态文件,因此为了正确部署这些文件,我们必须配置所有静态文件。

首先,我们需要创建一个专用的静态目录。

Shell

(.venv) > mkdir static

Git 不会跟踪空目录,因此添加一个 .keep 文件很重要,以便静态目录包含在源代码控制中。现在使用您的文本编辑器执行此操作。

然后,我们将安装 WhiteNoise 包,因为 Django 本身不支持在生产环境中提供静态文件。

Shell

(.venv) > python -m pip install whitenoise==6.0.0

WhiteNoise 必须添加到 django_project/settings.py 中的以下位置:

  • INSTALLED_APPS 中,django.contrib.staticfiles 上方的 whitenoise
  • CommonMiddleware 上方的 WhiteNoiseMiddleware
  • 指向 WhiteNoise 的 STATICFILES_STORAGE 配置

Code

# django_project/settings.py
INSTALLED_APPS = [
    ...
    "whitenoise.runserver_nostatic",  # new
    "django.contrib.staticfiles",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",  # new
    ...
]

STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR / "static"]  # new
STATIC_ROOT = BASE_DIR / "staticfiles"  # new
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"  # new

最后一步是首次运行 collectstatic 命令,将所有静态文件目录和文件编译为一个适合部署的自包含单元。

Shell

(.venv) > python manage.py collectstatic

全部设置完成。现在我们的静态文件已正确配置,我们以后不必太担心它们!

部署清单

对于基本部署,我们的部署清单上有五个项目:

  • 安装 Gunicorn 作为生产 Web 服务器
  • 创建 requirements.txt 文件
  • 创建 runtime.txt 文件
  • 更新 ALLOWED_HOSTS 配置
  • 为 Heroku 创建 Procfile

Django 的内置 Web 服务器适用于本地测试,但在生产环境中应使用 Gunicorn 或 uWSGI。由于 Gunicorn 更容易使用,它将是我们的选择。通过 Pip 安装它。

Shell

(.venv) > python -m pip install gunicorn~=20.1.0

在上一章中,我们创建了一个 requirements.txt 文件,但从那时起,我们在虚拟环境中安装了 Django REST Framework 和 Gunicorn。当前文件中都没有反映。很简单,只需再次运行命令并使用 > 运算符更新它。

Shell

(.venv) > python -m pip freeze > requirements.txt

第三步是在根目录中创建一个 runtime.txt 文件,旁边是 requirements.txt,它指定要在 Heroku 上运行的 Python 版本。如果未明确设置,当前设置为 python-3.9.10 运行时,但会随时间变化。

由于我们使用的是 Python 3.10,我们必须创建一个专用的 runtime.txt 文件来使用它。在您的文本编辑器中,在项目级别创建这个新的 runtime.txt 文件,意味着它与 manage.py 文件在同一目录中。在撰写本文时,最新版本是 3.10.2。确保全部小写!

runtime.txt

python-3.10.2

第四步是更新 ALLOWED_HOSTS。默认情况下,它设置为接受所有主机,但我们希望限制对实时网站和 API 的访问。我们希望能够在本地使用 localhost127.0.0.1,我们也知道任何 Heroku 站点都将以 .herokuapp.com 结尾。将所有三个主机添加到我们的 ALLOWED_HOSTS 配置中。

Code

# django_project/settings.py
ALLOWED_HOSTS = [".herokuapp.com", "localhost", "127.0.0.1"]

您文本编辑器中的最后一步是在项目根目录中创建一个名为 Procfile 的新文件,旁边是 manage.py 文件。这是一个专门为 Heroku 提供的文件,它提供了运行我们网站的说明。我们告诉它使用 Gunicorn 作为 Web 服务器,在 django_project.wsgi 中查找 WSGI 配置,并输出日志文件,这是一个可选但有帮助的额外配置。

Procfile

web: gunicorn django_project.wsgi --log-file -

我们都准备好了。将我们的新更改添加到 Git 并提交。

Shell

(.venv) > git status
(.venv) > git add -A
(.venv) > git commit -m "New updates for Heroku deployment"

GitHub

还建议将您的代码存储在托管提供商上,如 GitHub、GitLab 或 BitBucket。GitHub 非常流行,并提供慷慨的免费层,因此我们将在本书中使用它。您可以在网站上创建一个免费帐户。

设置完成后,创建一个名为 library 的新存储库,并确保选择"Private"单选按钮。然后单击"Create repository"按钮。在下一页上,向下滚动到显示"...or push an existing repository from the command line."的地方。将那里的两个命令复制并粘贴到您的终端中。

它应该如下所示,尽管用户名不是 wsvincent,而是您的 GitHub 用户名。

Shell

(.venv) > git remote add origin [https://github.com/wsvincent/library.git](https://github.com/wsvincent/library.git)
(.venv) > git push -u origin main

Heroku 部署

最后一步是在 Heroku 上创建一个新项目并将我们的代码推送到其中。您应该已经通过本章前面的命令行登录到 Heroku。

您可以运行 heroku create,Heroku 将为您的项目随机分配一个名称,或者您可以指定一个自定义名称,但它必须在所有 Heroku 中唯一!所以越长越好。我称之为 wsvincent-library。为您的 GitHub 用户名添加前缀是确保您可以指定 Heroku 项目名称的好方法,不过您也可以稍后随时更改它。

Shell

(.venv) > heroku create wsvincent-library
Creating ￿ wsvincent-library... done
[https://wsvincent-library.herokuapp.com/](https://wsvincent-library.herokuapp.com/) | [https://git.heroku.com/wsvincent-library.git](https://git.heroku.com/wsvincent-library.git)

然后我们将代码推送到 Heroku 本身,并添加一个 Web 进程,以便 dyno 正在运行。

Shell

(.venv) > git push heroku main
(.venv) > heroku ps:scale web=1

您的新应用程序的 URL 将在命令行输出中,或者您可以运行 heroku open 来找到它。

这是我的图书馆主页。

Library Homepage

还有 /api/ 处的 API 端点。

Library API

部署是一个复杂的话题,我们在这里故意采取了一些捷径。但目标是演练一个非常基本的 Django 网站和 API,展示如何从零开始创建它。

结论

我们在本章中涵盖了很多材料,所以如果现在感觉有点混乱,请不要担心。我们将 Django REST Framework 添加到我们现有的图书馆网站,并为我们的书籍创建了一个 API 端点。然后我们添加了测试,并将我们的项目部署到 Heroku。

Web API 可以做的远不止简单地从您的数据库列出信息。在下一章中,我们将构建并部署我们自己的 Todo API 后端,可以被任何前端轻松使用。