优化开发和生产环境: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
的新数据库:
有了我们的数据库,我们就可以安装我们的编程环境了。
通过 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 设置方法,我们需要为我们的开发系统创建一个环境变量文件。 我们将当前设置移动到此文件中,如下所示:
- 创建文件
src/.env
来保存我们的开发环境设置。 - 从
src/hello_visitor/settings.py
复制设置并将它们添加到src/.env
。 - 从
settings.py
文件中删除那些复制的行。 - 确保数据库连接字符串使用我们之前配置的相同凭据。
我们的环境文件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
让我们通过查看表的内容来验证迁移是否正确执行:
我们看到我们的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
时,每次刷新页面时计数都会增加:
我们现在有一个工作应用程序,它会在我们刷新页面时增加其访问次数。
准备部署
本教程涵盖了在与生产相匹配的漂亮 Django 开发环境中创建工作应用程序所需的所有步骤。 在第 3 部分中,我们将介绍将应用程序部署到其生产环境。 还值得探索我们的其他练习,这些练习突出了 Django 和 pydantic 的优点:它们包含在本 pydantic 教程的代码完整存储库中。
Toptal 工程博客对 Stephen Davidson 对本文中提供的代码示例进行审查和 beta 测试表示感谢。