优化开发和生产环境:Pydantic 教程,第 2 部分

已发表: 2022-07-22

开发人员可能是他们自己最大的敌人。 我见过无数工程师在与其生产环境不匹配的系统上开发的例子。 这种不协调会导致额外的工作,并且直到开发过程的后期才发现系统错误。 调整这些设置最终将简化持续部署。 考虑到这一点,我们将在 Django 开发环境中创建一个示例应用程序,通过 Docker、pydantic 和 conda 进行简化。

典型的开发环境使用:

  • 本地存储库;
  • 一个基于 Docker 的 PostgreSQL 数据库; 和
  • 一个 conda 环境(用于管理 Python 依赖项)。

Pydantic 和 Django 适用于简单和复杂的项目。 以下步骤展示了一个简单的解决方案,重点介绍了如何镜像我们的环境。

Git 存储库配置

在我们开始编写代码或安装开发系统之前,让我们创建一个本地 Git 存储库:

 mkdir hello-visitor cd hello-visitor git init

我们将从存储库根目录中的基本 Python .gitignore文件开始。 在本教程中,我们将在添加我们不希望 Git 跟踪的文件之前添加到该文件中。

使用 Docker 配置 Django PostgreSQL

Django 需要一个关系数据库,并且默认情况下使用 SQLite。 我们通常避免将 SQLite 用于关键任务数据存储,因为它不能很好地处理并发用户访问。 大多数开发人员选择更典型的生产数据库,例如 PostgreSQL。 无论如何,我们应该使用相同的数据库进行开发和生产。 此架构任务是十二要素应用程序的一部分。

幸运的是,使用 Docker 和 Docker Compose 操作本地 PostgreSQL 实例轻而易举。

为了避免污染我们的根目录,我们将把与 Docker 相关的文件放在单独的子目录中。 我们将首先创建一个 Docker Compose 文件来部署 PostgreSQL:

 # docker-services/docker-compose.yml version: "3.9" services: db: image: "postgres:13.4" env_file: .env volumes: - hello-visitor-postgres:/var/lib/postgresql/data ports: - ${POSTGRES_PORT}:5432 volumes: hello-visitor-postgres:

接下来,我们将创建一个docker-compose环境文件来配置我们的 PostgreSQL 容器:

 # docker-services/.env POSTGRES_USER=postgres POSTGRES_PASSWORD=MyDBPassword123 # The 'maintenance' database POSTGRES_DB=postgres # The port exposed to localhost POSTGRES_PORT=5432

现在已定义和配置数据库服务器。 让我们在后台启动我们的容器:

 sudo docker compose --project-directory docker-services/ up -d

重要的是要注意前面命令中 sudo 的使用。 除非在我们的开发环境中遵循特定步骤,否则它将是必需的。

数据库创建

让我们使用标准工具套件 pgAdmin4 连接和配置 PostgreSQL。 我们将使用与之前在环境变量中配置的登录凭据相同的登录凭据。

现在让我们创建一个名为hello_visitor的新数据库:

浏览器中的 pgAdmin4 屏幕,显示“创建数据库”对话框中的“常规”选项卡。数据库文本字段包含值 hello_visitor,所有者字段显示 postgres 用户,注释字段为空白。

有了我们的数据库,我们就可以安装我们的编程环境了。

通过 Miniconda 进行 Python 环境管理

我们现在需要设置一个隔离的 Python 环境和所需的依赖项。 为了简化设置和维护,我们选择了 Miniconda。

让我们创建并激活我们的 conda 环境:

 conda create --name hello-visitor python=3.9 conda activate hello-visitor

现在,我们将创建一个文件hello-visitor/requirements.txt ,枚举我们的 Python 依赖项:

 django # PostgreSQL database adapter: psycopg2 # Pushes .env key-value pairs into environment variables: python-dotenv pydantic # Utility library to read database connection information: dj-database-url # Static file caching: whitenoise # Python WSGI HTTP Server: gunicorn

接下来,我们将要求 Python 安装这些依赖项:

 cd hello-visitor pip install -r requirements.txt

现在应该安装我们的依赖项,为应用程序开发工作做准备。

姜戈脚手架

我们将首先运行django-admin ,然后运行它生成的文件manage.py来搭建我们的项目和应用程序:

 # From the `hello-visitor` directory mkdir src cd src # Generate starter code for our Django project. django-admin startproject hello_visitor . # Generate starter code for our Django app. python manage.py startapp homepage

接下来,我们需要配置 Django 来加载我们的项目。 settings.py文件需要调整INSTALLED_APPS数组来注册我们新创建的homepage应用程序:

 # src/hello_visitor/settings.py # ... INSTALLED_APPS = [ "homepage.apps.HomepageConfig", "django.contrib.admin", # ... ] # ...

应用程序设置配置

使用第一部分中显示的 pydantic 和 Django 设置方法,我们需要为我们的开发系统创建一个环境变量文件。 我们将当前设置移动到此文件中,如下所示:

  1. 创建文件src/.env来保存我们的开发环境设置。
  2. src/hello_visitor/settings.py复制设置并将它们添加到src/.env
  3. settings.py文件中删除那些复制的行。
  4. 确保数据库连接字符串使用我们之前配置的相同凭据。

我们的环境文件src/.env应该如下所示:

 DATABASE_URL=postgres://postgres:MyDBPassword123@localhost:5432/hello_visitor DATABASE_SSL=False SECRET_KEY="django-insecure-sackl&7(1hc3+%#*4e=)^q3qiw!hnnui*-^($o8t@2^^qqs=%i" DEBUG=True DEBUG_TEMPLATES=True USE_SSL=False ALLOWED_HOSTS='[ "localhost", "127.0.0.1", "0.0.0.0" ]'

我们将配置 Django 以使用 pydantic 从我们的环境变量中读取设置,代码片段如下:

 # src/hello_visitor/settings.py import os from pathlib import Path from pydantic import ( BaseSettings, PostgresDsn, EmailStr, HttpUrl, ) import dj_database_url # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent class SettingsFromEnvironment(BaseSettings): """Defines environment variables with their types and optional defaults""" # PostgreSQL DATABASE_URL: PostgresDsn DATABASE_SSL: bool = True # Django SECRET_KEY: str DEBUG: bool = False DEBUG_TEMPLATES: bool = False USE_SSL: bool = False ALLOWED_HOSTS: list class Config: """Defines configuration for pydantic environment loading""" env_file = str(BASE_DIR / ".env") case_sensitive = True config = SettingsFromEnvironment() os.environ["DATABASE_URL"] = config.DATABASE_URL DATABASES = { "default": dj_database_url.config(conn_max_age=600, ssl_require=config.DATABASE_SSL) } SECRET_KEY = config.SECRET_KEY DEBUG = config.DEBUG DEBUG_TEMPLATES = config.DEBUG_TEMPLATES USE_SSL = config.USE_SSL ALLOWED_HOSTS = config.ALLOWED_HOSTS # ...

如果您在完成之前的编辑后遇到任何问题,请将我们精心制作的settings.py文件与我们源代码存储库中的版本进行比较。

模型创建

我们的应用程序跟踪并显示主页访问者数量。 我们需要一个模型来保存该计数,然后使用 Django 的对象关系映射器 (ORM) 通过数据迁移来初始化单个数据库行。

首先,我们将创建我们的VisitCounter模型:

 # hello-visitor/src/homepage/models.py """Defines the models""" from django.db import models class VisitCounter(models.Model): """ORM for VisitCounter""" count = models.IntegerField() @staticmethod def insert_visit_counter(): """Populates database with one visit counter. Call from a data migration.""" visit_counter = VisitCounter(count=0) visit_counter.save() def __str__(self): return f"VisitCounter - number of visits: {self.count}"

接下来,我们将触发迁移以创建我们的数据库表:

 # in the `src` folder python manage.py makemigrations python manage.py migrate

要验证homepage_visitcounter表是否存在,我们可以在 pgAdmin4 中查看数据库。

接下来,我们需要在homepage_visitcounter表中放入一个初始值。 让我们使用 Django 脚手架创建一个单独的迁移文件来完成此任务:

 # from the 'src' directory python manage.py makemigrations --empty homepage

我们将调整创建的迁移文件以使用我们在本节开头定义的VisitCounter.insert_visit_counter方法:

 # src/homepage/migrations/0002_auto_-------_----.py # Note: The dashes are dependent on execution time. from django.db import migrations from ..models import VisitCounter def insert_default_items(apps, _schema_editor): """Populates database with one visit counter.""" # To learn about apps, see: # https://docs.djangoproject.com/en/3.2/topics/migrations/#data-migrations VisitCounter.insert_visit_counter() class Migration(migrations.Migration): """Runs a data migration.""" dependencies = [ ("homepage", "0001_initial"), ] operations = [ migrations.RunPython(insert_default_items), ]

现在我们准备为homepage应用程序执行修改后的迁移:

 # from the 'src' directory python manage.py migrate homepage

让我们通过查看表的内容来验证迁移是否正确执行:

浏览器中的 pgAdmin4 屏幕显示查询“SELECT * FROM public.homepage_visitcounter ORDER BY id ASC”。数据输出选项卡显示该表中有一行。代理键 id 字段值为 1,count 字段值为 0。

我们看到我们的homepage_visitcounter表存在并且已经填充了初始访问计数为 0。随着我们的数据库平方,我们将专注于创建我们的 UI。

创建和配置我们的视图

我们需要实现 UI 的两个主要部分:视图和模板。

我们创建homepage视图以增加访问者计数,将其保存到数据库中,并将该计数传递给模板以进行显示:

 # src/homepage/views.py from django.shortcuts import get_object_or_404, render from .models import VisitCounter def index(request): """View for the main page of the app.""" visit_counter = get_object_or_404(VisitCounter, pk=1) visit_counter.count += 1 visit_counter.save() context = {"visit_counter": visit_counter} return render(request, "homepage/index.html", context)

我们的 Django 应用程序需要监听针对homepage的请求。 要配置此设置,我们将添加此文件:

 # src/homepage/urls.py """Defines urls""" from django.urls import path from . import views # The namespace of the apps' URLconf app_name = "homepage" # pylint: disable=invalid-name urlpatterns = [ path("", views.index, name="index"), ]

要为我们的homepage应用程序提供服务,我们必须在不同的urls.py文件中注册它:

 # src/hello_visitor/urls.py from django.contrib import admin from django.urls import include, path urlpatterns = [ path("", include("homepage.urls")), path("admin/", admin.site.urls), ]

我们项目的基本 HTML 模板将存在于一个新文件src/templates/layouts/base.html中:

 <!DOCTYPE html> {% load static %} <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous"> <title>Hello, visitor!</title> <link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/> </head> <body> {% block main %}{% endblock %} <!-- Option 1: Bootstrap Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script> </body> </html>

我们将在新文件src/templates/homepage/index.html中扩展homepage应用程序的基本模板:

 {% extends "layouts/base.html" %} {% block main %} <main> <div class="container py-4"> <div class="p-5 mb-4 bg-dark text-white text-center rounded-3"> <div class="container-fluid py-5"> <h1 class="display-5 fw-bold">Hello, visitor {{ visit_counter.count }}!</h1> </div> </div> </div> </main> {% endblock %}

创建 UI 的最后一步是告诉 Django 在哪里可以找到这些模板。 让我们在settings.py文件中添加一个TEMPLATES['DIRS']字典项:

 # src/hello_visitor/settings.py TEMPLATES = [ { ... 'DIRS': [BASE_DIR / 'templates'], ... }, ]

我们的用户界面现在已经实现,我们几乎可以测试我们的应用程序的功能了。 在我们进行测试之前,我们需要放置环境的最后一部分:静态内容缓存。

我们的静态内容配置

为了避免在我们的开发系统上走架构捷径,我们将配置静态内容缓存来镜像我们的生产环境。

我们会将项目的所有静态文件保存在一个目录src/static中,并指示 Django 在部署之前收集这些文件。

我们将使用 Toptal 的徽标作为我们应用程序的favicon并将其存储为src/static/favicon.ico

 # from `src` folder mkdir static cd static wget https://frontier-assets.toptal.com/83b2f6e0d02cdb3d951a75bd07ee4058.png mv 83b2f6e0d02cdb3d951a75bd07ee4058.png favicon.ico

接下来,我们将配置 Django 来收集静态文件:

 # src/hello_visitor/settings.py # Static files (CSS, JavaScript, images) # a la https://docs.djangoproject.com/en/3.2/howto/static-files/ # # Source location where we'll store our static files STATICFILES_DIRS = [BASE_DIR / "static"] # Build output location where Django collects all static files STATIC_ROOT = BASE_DIR / "staticfiles" STATIC_ROOT.mkdir(exist_ok=True) # URL to use when referring to static files located in STATIC_ROOT. STATIC_URL = "/static/" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"

我们只想将我们的原始静态文件存储在源代码存储库中; 我们不想存储生产优化版本。 让我们用这个简单的行将后者添加到我们的.gitignore中:

 staticfiles

随着我们的源代码存储库正确存储了所需的文件,我们现在需要配置我们的缓存系统以使用这些静态文件。

静态文件缓存

在生产环境中——因此也在我们的开发环境中——我们将使用 WhiteNoise 来更有效地为 Django 应用程序的静态文件提供服务。

我们通过将以下代码段添加到src/hello_visitor/settings.py文件中来将 WhiteNoise 注册为中间件。 注册顺序是严格定义的, WhiteNoiseMiddleware必须紧跟在SecurityMiddleware之后:

 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', # ... ] STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

现在应该在我们的开发环境中配置静态文件缓存,使我们能够运行我们的应用程序。

运行我们的开发服务器

我们有一个完全编码的应用程序,现在可以使用以下命令启动 Django 的嵌入式开发 Web 服务器:

 # in the `src` folder python manage.py runserver

当我们导航到http://localhost:8000时,每次刷新页面时计数都会增加:

一个浏览器窗口,显示我们的 pydantic Django 应用程序的主屏幕,上面写着“你好,访客!”在一行和下一行“1”。

我们现在有一个工作应用程序,它会在我们刷新页面时增加其访问次数。

准备部署

本教程涵盖了在与生产相匹配的漂亮 Django 开发环境中创建工作应用程序所需的所有步骤。 在第 3 部分中,我们将介绍将应用程序部署到其生产环境。 还值得探索我们的其他练习,这些练习突出了 Django 和 pydantic 的优点:它们包含在本 pydantic 教程的代码完整存储库中。


Toptal 工程博客对 Stephen Davidson 对本文中提供的代码示例进行审查和 beta 测试表示感谢。