简介:《Django专业网站构建指南》是一本面向已有Django基础、希望掌握生产级项目开发流程的开发者。书中围绕真实项目环境,系统讲解从初始配置到部署全过程,重点涵盖Docker容器化部署、PostgreSQL数据库接入、自定义用户模型、基于电子邮件的用户认证、全面测试策略以及环境变量与安全性能优化等主题。全书以单本PDF形式提供,文件总数1个,压缩包大小25.89MB,便于下载后离线阅读。目前已有61人学习,适合作为从“玩具项目”过渡到“生产级应用”的进阶参考。读者可通过详细目录与章节安排,掌握Django社区推荐的项目结构,学会用Docker管理开发环境,配置PostgreSQL数据层,并建立覆盖注册、登录、静态资源管理等环节的完整测试意识,是一份贴近工程实践的指南。
1. 用这本书把 Django 从「能跑」改造成「能上线」:它到底值不值得啃
如果你已经跟着教程写过几个 Django 项目,比如博客、投票应用或者简单的 CRUD,你会发现一个尴尬的断层:本地跑得飞起的项目,一放到服务器上就各种翻车——DEBUG=False之后静态文件全丢、数据库还是 SQLite、用户密码直接明文存、代码结构和官方教程里的 demo 一模一样。这本《Django for Professionals》就是冲着这个断层来的:它不讲 Django 基础语法,只讲一件事——如何用生产级的工程实践来构建 Django 网站。内容覆盖 Docker 容器化、PostgreSQL 迁移、自定义用户模型、django-allauth 邮箱认证、环境变量管理、Stripe 支付集成、搜索、性能分析、安全部署清单等 17 个章节。适合的人群很明确:已经写过 Django 应用但没部署过生产环境的开发者,以及想在简历上写出「熟悉生产级 Django 工程实践」的求职者。这本书没有一句废话,每一章都在往「上线」这个目标推进。
2. Docker 与 PostgreSQL:把开发环境做成不可变基座
2.1 为什么虚拟环境不够用:容器与 venv 的实质区别
很多初学者会把 Docker 和venv混为一谈,其实两者解决问题的层次完全不同。虚拟环境只隔离 Python 依赖包,它不隔离 Python 解释器版本、系统库、PostgreSQL 实例、Redis 缓存这些更底层的东西。你在本地用 Python 3.7 +venv跑通的项目,推到服务器上如果系统是 Ubuntu 22.04 自带的 Python 3.10,代码可能直接跑不起来——语法兼容性问题、依赖编译失败、系统库缺失,每一样都能消耗你一整天。
Docker 容器解决的是「整个运行时环境的一致性」。它把操作系统层、Python 版本、依赖包、启动命令全部打包成一个镜像,无论在谁的机器上docker run,得到的都是完全相同的环境。这就是「Build once, run anywhere」的含义。这本书把 Docker 放在第一章不是随便排的,因为后续所有章节——PostgreSQL、用户模型、静态资源、测试——都在这个容器化基座上展开。
2.2 Dockerfile 与 docker-compose 的最小生产配置
书里采用的方案是Dockerfile+docker-compose.yml双文件配合。Dockerfile负责定义应用镜像,docker-compose.yml负责编排多个服务(web 应用、数据库)之间的依赖关系。一个典型的配置长这样:
# Dockerfile FROM python:3.8-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]这段配置的逻辑是:基于官方的 Python 3.8 精简镜像,预定义两个环境变量来避免 Python 生成.pyc文件和输出缓冲卡顿,然后先拷贝requirements.txt并安装依赖,再拷贝项目代码。这里有个细节值得注意:先拷贝依赖文件再拷贝代码,是为了利用 Docker 的层缓存——只要requirements.txt没变,后面每次构建都会直接复用依赖安装层,构建速度可以提升数倍。
# docker-compose.yml version: '3.8' services: web: build: . command: python manage.py runserver 0.0.0.0:8000 volumes: - .:/app ports: - "8000:8000" depends_on: - db db: image: postgres:12 environment: - POSTGRES_USER=django_user - POSTGRES_PASSWORD=django_password - POSTGRES_DB=django_db volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:depends_on是关键参数:它确保db服务先启动,web 服务再启动,避免出现 Django 连接数据库报connection refused的尴尬。volumes里的.:/app把本地代码挂载进容器,这样你改代码容器里立即生效,不用每次重新构建镜像。很多新手在这一步会踩一个坑:直接用postgres:latest标签,结果数据库大版本升级后数据目录格式不兼容,数据全丢。生产环境一定要锁版本号。
2.3 从 SQLite 切到 PostgreSQL:settings 与 psycopg 适配
Django 项目默认使用 SQLite,但生产环境几乎不会用它——并发写入能力差、不支持并行访问、数据类型功能有限。书里在第二章就把数据库切成了 PostgreSQL,涉及到几个关键改动。
首先是settings.py里的DATABASES配置:
# settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'django_db', 'USER': 'django_user', 'PASSWORD': 'django_password', 'HOST': 'db', 'PORT': '5432', } }其中HOST设置成db而不是localhost,是因为在 docker-compose 网络里,服务名就是主机名——容器间通过服务名通信。这时候需要安装psycopg2-binary这个 PostgreSQL 驱动,否则 Django 会报No module named 'psycopg2'。装好依赖之后,改动数据库配置只是第一步,你还需要重新执行makemigrations和migrate,让所有表结构在 PostgreSQL 里重新生成。
提示:
psycopg2-binary适合本地开发和生产部署,但如果你的生产服务器对安全要求高,建议改用psycopg2源码安装,因为二进制包存在潜在的编译后门风险。
3. 自定义用户模型与 django-allauth:认证流程的完整闭环
3.1 为什么必须自定义 User 模型
这本书反复强调一个 Django 社区的铁律:项目一开始就必须创建自定义用户模型,哪怕你暂时不需要额外字段。原因是 Django 内置的User模型一旦执行过第一次migrate,就无法再替换——你想给用户加个avatar字段或者改成用邮箱登录,只能删库重来,这在生产环境是灾难性的。书里在书店项目的初始阶段就创建了一个继承AbstractUser的自定义模型:
# accounts/models.py from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): email = models.EmailField(unique=True) def __str__(self): return self.email同时在settings.py里必须配置:
AUTH_USER_MODEL = 'accounts.CustomUser'这个配置必须在第一次migrate之前写好,否则后续想改就只能重建数据库。很多人在这里有一个认知误区:以为只有做社交网站才需要自定义用户模型,其实哪怕你的站点只有一个管理员,也应该自定义——因为你无法预测未来会不会加字段,而换模型成本极高。
3.2 注册、登录、登出的视图与模板实现
书里不直接用 Django 内置的auth视图,而是自己写视图函数并配合模板,这样可控性更强。核心登录逻辑是一个典型的POST/GET判断:
# accounts/views.py from django.contrib.auth import login, logout from django.contrib.auth.forms import AuthenticationForm from django.shortcuts import render, redirect def login_view(request): if request.method == 'POST': form = AuthenticationForm(request, data=request.POST) if form.is_valid(): user = form.get_user() login(request, user) return redirect('home') else: form = AuthenticationForm() return render(request, 'accounts/login.html', {'form': form})AuthenticationForm是 Django 内置的登录表单,它内部完成了用户名密码校验和用户激活状态检查。登录成功后用redirect('home')跳转时,注意要在urls.py里给首页的 URL 命名name='home',否则会抛出NoReverseMatch错误。登出视图更简单:
def logout_view(request): logout(request) return redirect('home')登出只需要调用logout()函数清掉 session 即可,不需要处理任何表单数据。
3.3 django-allauth 实现邮箱验证登录
书里把邮箱验证交给django-allauth这个第三方库,因为它已经封装好了邮件确认、密码重置、第三方 OAuth 等一整套流程,自己从零写可能要几周时间。核心配置如下:
# settings.py INSTALLED_APPS = [ # ... 'django.contrib.sites', 'allauth', 'allauth.account', 'allauth.socialaccount', ] AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', 'allauth.account.auth_backends.AuthenticationBackend', ] SITE_ID = 1 ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_AUTHENTICATION_METHOD = 'email' ACCOUNT_EMAIL_VERIFICATION = 'mandatory' ACCOUNT_LOGOUT_REDIRECT = '/'这里的关键配置是ACCOUNT_EMAIL_VERIFICATION = 'mandatory',它会强制用户点击邮件里的确认链接后才允许登录。配合ACCOUNT_AUTHENTICATION_METHOD = 'email',用户登录时只需要输入邮箱和密码,不再需要填用户名——这是现代 Web 应用的主流交互。测试阶段邮件不会真的发出去,Django 会把邮件打印到控制台,配置如下:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'这行配置让你在本地开发时不用配 SMTP 服务器就能看到邮件内容,验证完流程后切到真实邮件服务即可。
3.4 测试策略:为认证流程写单元测试
书里专门花了一整节讲测试,因为认证这块是业务安全的核心。一个标准的用户注册测试用例长这样:
# accounts/tests.py from django.test import TestCase from django.contrib.auth import get_user_model class CustomUserTests(TestCase): def test_create_user(self): User = get_user_model() user = User.objects.create_user( username='testuser', email='test@example.com', password='testpass123' ) self.assertEqual(user.email, 'test@example.com') self.assertTrue(user.is_active) self.assertFalse(user.is_staff) self.assertFalse(user.is_superuser)这里有个专业操作:测试代码里使用get_user_model()而不是直接from django.contrib.auth.models import User,因为前者会自动返回你在settings.py里配置的AUTH_USER_MODEL。如果你直接用User,即使配了自定义模型,测试里用的还是内置的,查不出来问题。
4. 环境变量、静态资源与文件上传:配置管理的三个硬门槛
4.1 .env 文件与 SECRET_KEY 的隔离方案
直接写在settings.py里的SECRET_KEY、数据库密码、API 密钥是生产环境的大忌——只要代码库泄露,密钥就跟着泄露,攻击者可以直接伪造 session 或者访问数据库。书里的做法是引入.env文件配合environ库来管理环境变量。安装django-environ后配置如下:
# settings.py import environ env = environ.Env() environ.Env.read_env() SECRET_KEY = env('SECRET_KEY') DEBUG = env.bool('DEBUG', default=False) ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])对应的.env文件内容:
SECRET_KEY=your-secret-key-here DEBUG=True ALLOWED_HOSTS=localhost,127.0.0.1.env文件必须写进.gitignore,保证不会提交到代码仓库。每条环境变量都有明确的默认值策略:DEBUG默认False是安全默认,防止有人忘了设置环境变量导致生产环境开着调试模式——这是 Django 安全检查清单里的头号高危项。
4.2 STATIC_URL、STATICFILES_DIRS 与 collectstatic 的联动
静态文件管理是无数项目上线时翻车的重灾区。开发环境下 Django 自己处理静态文件,一旦DEBUG=False,Django 就不再提供静态文件服务,必须由 Nginx 之类的 Web 服务器来托管。书里介绍的配置方案是:
# settings.py STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / 'static'] STATIC_ROOT = BASE_DIR / 'staticfiles'三个变量各司其职:STATIC_URL是浏览器访问静态文件的 URL 前缀;STATICFILES_DIRS告诉 Django 项目里哪些目录存放源静态文件;STATIC_ROOT是执行collectstatic时把所有静态文件聚拢到一起的输出目录。部署命令如下:
python manage.py collectstatic --noinput--noinput参数避免命令在覆盖文件时交互式询问。执行完之后,staticfiles目录下的所有文件由 Nginx 直接通过/static/路径提供服务,不再经过 Django。
4.3 MEDIA 文件与图片上传的处理边界
用户上传的图片属于媒体文件,和项目源码里的静态文件是两类东西。配置上有对应的一组变量:
MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media'开发环境下还需要在urls.py里手动挂载媒体文件服务:
from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # ... ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)生产环境下MEDIA_ROOT目录不用collectstatic收集,它始终独立存在。图片字段的定义方式:
class Book(models.Model): title = models.CharField(max_length=200) cover = models.ImageField(upload_to='covers/')upload_to='covers/'表示上传的图片会保存到MEDIA_ROOT/covers/目录下。这里有一个坑:模板里输出<img src="{{ book.cover.url }}">时,Django 会返回/media/covers/xxx.jpg这个 URL 路径,但如果MEDIA_URL配错了,图片就会 404。而且上传路径按日期分目录是好习惯,可以用upload_to='covers/%Y/%m/%d/'自动按年月日归档。
注意:
ImageField需要 Pillow 库支持,没有安装会在迁移时报ModuleNotFoundError: No module named 'PIL',这也是一个高频踩坑点。
5. 全书避坑指南:性能、搜索与支付集成的常见问题排查
5.1 查询性能翻车:N+1 查询与 select_related 的正确姿势
书里用django-debug-toolbar来分析页面 SQL 查询次数,很直观地暴露了一个常见问题:页面上显示 10 本书,每本书的作者关联查询需要额外执行一次 SQL,总查询次数变成 1 + 10 = 11 次。这种现象叫 N+1 查询问题。
# 翻车写法 books = Book.objects.all() for book in books: print(book.author.username) # 每本书触发一次新的 SQL 查询 # 修复写法 books = Book.objects.select_related('author').all() for book in books: print(book.author.username) # 只执行 1 次 LEFT JOIN 查询select_related适用于ForeignKey和OneToOneField这类正向关系,它通过 SQL 的JOIN一次性把关联对象查出来。如果是ManyToManyField或者反向关系,就要用prefetch_related——它的运行机制完全不同,是对每个关联表单独查询后再在 Python 内存里做匹配。搞混这两个方法的适用场景,照样会翻车,因为prefetch_related不支持ForeignKey正向关联。
5.2 搜索功能用错 ORM:filter 与 Q 对象
实现搜索功能时,很多人的第一反应是Book.objects.filter(title__contains=query),这在数据量小的时候没问题,但一旦生产数据上万条,LIKE '%关键字%'这种模糊查询会全表扫描,性能急剧下降。而且遇到用户同时搜索书名和作者名时,filter默认是 AND 关系,无法实现「匹配任一字段即可」的效果。
from django.db.models import Q def search_books(request): query = request.GET.get('q') books = Book.objects.filter( Q(title__icontains=query) | Q(author__icontains=query) ) return render(request, 'books/search_results.html', {'books': books})Q对象就是 Django 里的「条件对象」,它可以组合|(OR)和&(AND)逻辑,把多个 ORM 查询条件灵活拼装。上面这段代码用__icontains表示不区分大小写的包含匹配,配合|运算符实现了「标题或作者任意匹配即命中」的需求。搜索结果页的模板里接收books这个查询集,遍历显示即可。
5.3 Stripe 支付集成:测试密钥与真实密钥的切换陷阱
书里第 14 章用 Stripe 实现订单支付,这里最常见的坑是弄混publishable key(可发布密钥,用于前端)和secret key(机密密钥,用于后端)。前端引入 Stripe.js 用的是 publishable key,后端创建付款订单用的是 secret key,两者都写进.env文件分别管理。创建订单的核心代码:
import stripe from django.conf import settings stripe.api_key = settings.STRIPE_SECRET_KEY def create_checkout_session(request): session = stripe.checkout.Session.create( payment_method_types=['card'], line_items=[{ 'price_data': { 'currency': 'usd', 'product_data': {'name': 'Book'}, 'unit_amount': 2000, }, 'quantity': 1, }], mode='payment', success_url='http://localhost:8000/success/', cancel_url='http://localhost:8000/cancel/', ) return redirect(session.url, code=303)测试阶段用的是sk_test_开头的测试密钥,上线前必须换成sk_live_开头的真实密钥。这个过程容易出两个问题:一是测试环境跑通了直接上线忘换密钥,导致所有订单走测试模式,不产生真实扣款;二是success_url在本地用的localhost,上线后忘了改成正式域名,Stripe 回调时用户被 404 弹回来。这两个问题我在接手项目时都踩过,浪费了不少排查时间。
5.4 缓存配置:Django 自带缓存还是 Redis?
书里第 16 章介绍性能优化时,缓存是绕不开的环节。很多人上来就想用 Redis 做缓存,但书里建议先评估业务复杂度——如果只是缓存几个固定页面的内容,Django 内置的缓存框架配合LocMemCache就够了:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-snowflake', } }LocMemCache是进程内缓存,零依赖、零配置,适合单机部署的小型站点。但如果应用要横向扩展跑多个进程,每个进程的缓存互不共享,就必须换 Redis 这类集中式缓存了,否则会出现同一个用户在不同请求下看到不同缓存状态的诡异问题。
6. 上线前过一遍 Django 安全部署清单:用 20 分钟换一次半夜惊醒
书里第 17 章给出的部署清单是我见过最实用的,它其实是一个安全自检清单——照着检查一遍,能堵掉 90% 的常见漏洞。这份清单的核心检查项包括:
| 检查项 | 生产环境必须满足 |
|---|---|
DEBUG | 必须是False |
ALLOWED_HOSTS | 必须配置为正式域名列表 |
SECRET_KEY | 必须来自环境变量,不硬编码 |
| 数据库密码 | 必须来自环境变量,不硬编码 |
CONN_MAX_AGE | 建议设置连接复用减少开销 |
| 密码哈希算法 | 确认使用Argon2或PBKDF2 |
| HTTPS | 必须全站开启 |
| Session Cookie | SESSION_COOKIE_SECURE = True |
| CSRF Cookie | CSRF_COOKIE_SECURE = True |
SECURE_SSL_REDIRECT | 必须开启 HTTP 到 HTTPS 跳转 |
我每次上线新项目,都会在部署前按这个表格逐行核对。这里有两条检查是翻车重点:一是ALLOWED_HOSTS,开发时经常配成['localhost', '127.0.0.1'],上线忘了加正式域名,结果用户访问时 Django 直接抛DisallowedHost错误——这是 Django 故意做的一层防护,防止 HTTP Host 头攻击。二是 session 和 CSRF 的 Secure 标记,如果漏掉,在 HTTPS 站点上 cookie 仍能通过 HTTP 传输,中第三方截获的风险。
逐个验证过部署清单之后,再执行一个相对冷门但很有效的验证方法:开一个全新的空数据库,在服务器上从零跑一遍migrate和collectstatic。我当初第一次部署时,就是因为本地数据库里残留了几条以往实验的数据,migrate一直报错,换到空库才跑顺。从那以后我每次部署都强制走一遍空库流程,确认迁移脚本、静态文件、环境变量这三件事在裸环境里都站得住,再让业务数据进场。
最后提一个多数人容易忽略的小技巧:上线后的第一周,把django-debug-toolbar临时打开,在测试环境里跑一遍核心用户路径——注册、登录、下单、上传——然后看每条请求的 SQL 查询次数和耗时。你不用追求极致的性能数据,只需确认没有出现 N+1 查询和重复查询,就能极大地避免流量高峰时数据库被拖垮。这本书从头到尾都在教这个思路:生产环境的问题不是代码能不能跑,而是流量来了、数据多了、并发上来了,它还能不能站得住。如果你不想在半夜被报警电话吵醒,就在上线前把这份清单当作业做完。希望帮到你。
这本书的资源我已经下载并整理好了,包含全书的章节 PDF、Docker 配置模板和项目源码,你可以直接照着书中的章节顺序,从 Docker 容器化开始一步步复现整个书店项目。
本文还有配套的精品资源,点击获取