Django 项目只要 python manage.py runserver 一敲下去、控制台里飘出 “Starting development server at http://127.0.0.1:8000/”,很多新手就感觉“万事大吉”了。但说实话,这恰恰是另一轮工作的起点。无论你是刚 django-admin startproject 拉了个空壳,还是从同事手里接了个跑不起来的老项目,后端进程能启动,只代表 Django 的 WSGI 容器把应用加载了出来,离“能开发、能调试、能交付”还有一大截距离。
这篇文章我把自己这些年做完 Django 项目后端启动之后必做的那些“小准备工作”整理成了一套清单,覆盖配置体检、数据库收尾、接口联调、日志与后台运营准备,以及一些我踩过坑之后的排查心得。适合刚学完 Django 基础、准备做前后端分离项目实战的新手,也适合刚从别人手里接手一个 Django 后端、正准备做二次开发的同学。不是讲框架原理的大而全教程,而是围绕“项目能跑起来之后,下一步具体该干什么”的实操笔记。
1. 项目启动后的第一轮环境体检
1.1 依赖版本核对:别让 pip freeze 骗了你
项目能启动,第一步要确认的其实不是业务代码,而是当前环境里的依赖到底是不是项目真正需要的。我接手过不少 Django 项目,最常见的情况是:runserver 能跑,但一装新功能就崩,一崩就发现是版本不兼容。
你可以在项目根目录做一次摸底,把当前环境实际安装的包全部导出来:
bash复制pip freeze > requirements_current.txt
然后和项目自带的 requirements.txt 做对比:
bash复制diff requirements.txt requirements_current.txt
这里有个特别容易被坑的地方:pip freeze 会把你环境里所有包都列出来,包括那些只有本机装过、压根没写进项目依赖的包。比如你在调试时装了 ipython、django-debug-toolbar,它们也会混进去。所以更推荐用 pipreqs 这类工具按项目代码里的 import 自动生成依赖列表:
bash复制pip install pipreqs
pipreqs ./ --force
它会扫描项目里实际用到的库,比手工去翻 requirements.txt 准确得多。生成完再看一眼版本号,尤其注意 Django、djangorestframework、celery 这三类最容易出兼容问题的包。我的习惯是核心依赖锁大版本,别随便追最新版。很多前后端分离项目实战里,后端接口突然 500,最后查出来是某个依赖升级后行为变了,这种坑完全可以在启动阶段用版本锁定避开。
提示:如果项目里有
Pipfile.lock或poetry.lock,说明团队用的是 pipenv 或 poetry 管理依赖,这时候别直接用pip install -r requirements.txt,先确认锁文件是否更新,否则会把环境搞乱。
1.2 settings.py 里的敏感项检查
开发环境里最常见的“能跑但危险”的配置,就是 DEBUG = True 和硬编码的 SECRET_KEY。Django 的 runserver 对这两项并不敏感,但一旦你把这个后端接到公网或者部署到测试服务器,DEBUG=True 会把完整的堆栈信息、本地路径、数据库配置全部打印到页面上,等于给攻击者送情报。
我做项目后的标准操作是:
python复制# settings.py
import os
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "dev-only-insecure-key")
DEBUG = os.environ.get("DJANGO_DEBUG", "True") == "True"
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "*").split(",")
本地跑时用默认值,测试或生产环境里用环境变量覆盖。别嫌麻烦,这一步晚做一天,后面联调时被跨域、被 Host 校验卡住的可能性就多一天。另外一个高频报错是 ALLOWED_HOSTS 没配好,很多 Django 新手启动时看到 “Invalid HTTP_HOST header” 直接懵掉,其实就是你请求里的域名不在白名单里。开发环境用 "*" 能解燃眉之急,但接前端联调时建议写明确的内网 IP 或 localhost,不然哪天前端打到公网地址上,报错又会把你绕晕。
1.3 环境变量与本地配置分离
把配置里所有可能因环境变化的值都抽出来,是我每次启动项目后最先做的一件事。数据库名、Redis 地址、上传文件存储路径、调用第三方服务的密钥,都要走 os.environ.get() 这层。你可以在项目根目录放一个 .env.example,里面写清楚每个变量是干什么的,然后让本地复制一份 .env。用 python-dotenv 加载:
bash复制pip install python-dotenv
然后在 manage.py 或 settings.py 顶部引入:
python复制from dotenv import load_dotenv
load_dotenv()
这么做的好处不只是安全,更关键的是多人协作时不再出现“我本地能跑,你本地跑不起来”的经典问题。我见过不少团队把数据库密码直接写在 settings.py 里,后来代码传到仓库,整个内网数据库被扫出来,教训挺深刻的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库层面的收尾准备
2.1 迁移之后,初始数据从哪来
Django 项目启动后,第一件正经事就是让数据库结构和模型对上。一句:
bash复制python manage.py makemigrations
python manage.py migrate
能把 app 里所有的模型变化转换成数据表。但很多新手忽略了一个问题:migrate 之后数据库是空的,连最基本的用户、权限、分类这些基础数据都没有。
你可能需要做两件事。
第一,如果想快速生成一批测试用的基础数据,可以用 fixture。先把某个 app 的数据导出成 JSON:
bash复制python manage.py dumpdata app_name.ModelName --indent 4 > app_name/fixtures/initial.json
再把数据导入到新库:
bash复制python manage.py loaddata initial.json
注意 fixture 文件要放在 app 目录下的 fixtures/ 文件夹里,Django 会自动去这里找。这招在前后端分离项目实战里特别实用,前端需要稳定的 mock 数据,你直接给他们导一份 JSON 或者直接在接口层返回,比前端自己造数据更接近真实业务。
第二,如果是某些需要业务逻辑生成的数据,比如初始化管理员角色、绑定默认权限,建议写成一个数据初始化脚本。也就是在 app 里新建一个 management/commands/init_data.py,用 handle() 方法里执行创建逻辑,之后用自定义命令触发:
bash复制python manage.py init_data
把初始数据做成自定义命令而不是一次性在 shell 里敲,是因为项目每次从零搭建、每次测试环境重置,都需要重复执行这些逻辑。写进命令后,一条指令就能搞定,不用翻聊天记录找当时是怎么敲的。
2.2 清理测试残留,确认索引和约束
如果你是从别的环境拉过来的数据库,启动后别急着开发。先做一次“盘点”:看看表里有没有大量测试脏数据,有没有本该唯一却出现重复的字段。用 Django 的 shell 快速查一下表数量和数据量:
bash复制python manage.py shell -c "
from django.db import connection
tables = connection.introspection.table_names()
print(f'共 {len(tables)} 张表')
"
对于核心业务表,建议查一下索引是否生效。很多团队在开发期加字段很随意,根本没考虑过查询效率。等前端联调时一查列表接口要好几秒,才回头补索引,那时候接口已经调到一半了,改动成本特别高。
另外确认下数据库事务隔离级别。Django 默认用的是数据库层面的隔离设置,MySQL 一般是 REPEATABLE READ,PostgreSQL 是 READ COMMITTED。如果你项目里有并发扣库存、订单状态流转这类逻辑,建议启动阶段就和 DBA 或运维确认好隔离级别,别等上线出了脏读再来复盘,代价太大。开发阶段用默认值没问题,但心里要有这根弦。
2.3 缓存和 Redis 连接自检
现在大多数 Django 项目都会用 Redis 做缓存、session、或者 Celery 的 broker。后端启动时,runserver 不会主动告诉你 Redis 有没有连上,只有你第一次访问某个依赖缓存的接口时,才会突然冒出一个连接超时。
所以我在启动准备里一定会做一次缓存自检,最简单的办法:
bash复制python manage.py shell -c "
from django.core.cache import cache
cache.set('startup_check', 'ok', 30)
print(cache.get('startup_check'))
"
如果输出 ok,说明缓存连接正常。如果抛异常,就要检查 Redis 服务是否启动、IP 和端口是否配置正确、密码是否写对。这步五分钟能做完,但能避免联调时反复排查那种“接口偶尔慢、偶尔报 500”的幽灵问题。
3. 接口层的安全与调试准备
3.1 跨域配置与前后端联调
现在做 Django 后端,十有八九是给前后端分离项目提供接口。后端启动后,前端一访问,第一个撞上的就是跨域问题。如果你的浏览器控制台出现:
Access to XMLHttpRequest at 'http://localhost:8000/api/...' from origin 'http://localhost:3000' has been blocked by CORS policy
那就说明后端还没配置跨域。Django 里最成熟的做法是装 django-cors-headers:
bash复制pip install django-cors-headers
然后三步配置:
python复制# settings.py
INSTALLED_APPS = [
# ...
"corsheaders",
]
MIDDLEWARE = [
# 尽量放在最前面
"corsheaders.middleware.CorsMiddleware",
# ...
]
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000",
]
这里有个细节:如果前端会带 Authorization 头、Content-Type: application/json,跨域请求会先发一个 OPTIONS 预检请求。django-cors-headers 默认会处理预检,但如果你自己写了中间件拦截了 OPTIONS,就会出现“明明配了 CORS 还是不通过”的问题。排查时可以先确认后端有没有返回 Access-Control-Allow-Origin 响应头,再往上追中间件顺序。
提示:跨域配置不是越宽越好。
CORS_ALLOW_ALL_ORIGINS = True在开发阶段确实省事,但一旦项目上线,务必改成白名单。配合CSRF_TRUSTED_ORIGINS一起配置,不然接口带 Cookie 的请求还会被 CSRF 校验挡住。
3.2 日志体系不搭好,上线就是盲人摸象
Django 自带的 runserver 会把请求打到控制台,但这远远不够。项目启动后我第一件事就是把 LOGGING 配好,不然等到联调出问题,后端控制台滚几屏日志,你想找一条关键报错要翻半天。
一个够用的日志配置长这样:
python复制# settings.py
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
"style": "{",
},
"simple": {
"format": "{levelname} {asctime} {module} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "simple",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "logs/django.log",
"maxBytes": 5 * 1024 * 1024,
"backupCount": 5,
"formatter": "verbose",
"encoding": "utf-8",
},
},
"root": {
"handlers": ["console", "file"],
"level": "INFO",
},
"loggers": {
"django.request": {
"handlers": ["file"],
"level": "ERROR",
"propagate": False,
},
"api": {
"handlers": ["console", "file"],
"level": "DEBUG",
"propagate": False,
},
},
}
用 RotatingFileHandler 做日志轮转,避免单个日志文件无限膨胀。注意 logs 目录要先建好,Django 不会自动创建文件所在的文件夹,不建的话日志配置会报错,而且这个报错通常在请求才触发,很隐蔽。
如果你项目里用了 DRF(Django REST Framework),建议再给视图层加一个简单的请求日志中间件,记录每个请求的方法、路径、耗时、状态码。这个在联调阶段特别管用,前端说“接口报错了”,你可以直接把中间件打印的耗时和状态码甩过去,省掉来回拉扯的时间。
3.3 自建一个健康检查接口
健康检查接口听起来很高端,其实做起来很简单,但它能帮你少踩特别多坑。后端启动后,我习惯加一个 /healthz/ 接口,返回 JSON:
python复制# urls.py
from django.http import JsonResponse
def healthz(request):
return JsonResponse({"status": "ok"}, status=200)
如果再讲究一点,把数据库连接检查也放进去:
python复制def healthz(request):
from django.db import connection
try:
connection.ensure_connection()
except Exception:
return JsonResponse({"status": "error", "detail": "db unreachable"}, status=500)
return JsonResponse({"status": "ok"}, status=200)
这个接口的作用是:联调时前端先打一下这个接口,后端服务通不通、数据库连没连上,一眼就能定位。后面如果接 Docker 部署,也可以直接用这个接口做容器的存活探针,不需要额外写脚本。
4. 运营侧的常用准备
4.1 超级管理员与 Admin 后台配置
Django 自带的 Admin 后台对内容运营特别友好,后端启动后我会先把超级管理员创建好:
bash复制python manage.py createsuperuser
如果是自动化脚本或测试环境,也可以用命令直接生成:
bash复制python manage.py shell -c "
from django.contrib.auth.models import User
User.objects.create_superuser('admin', 'admin@example.com', 'admin123')
"
然后把核心模型注册到 Admin,让运营可以直接在后台增删改数据,不用每次找后端开接口改数据库。注册很简单,在 app 的 admin.py 里:
python复制from django.contrib import admin
from .models import Order, Customer
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ["order_no", "customer", "status", "created_at"]
list_filter = ["status"]
search_fields = ["order_no"]
list_display 一定要设置,不然后台列表页只显示一行对象名,运营根本没法用。这个步骤看起来和“后端启动”没直接关系,但很多项目开发到一半,产品和运营要看数据,临时配 Admin 很打断节奏。提前把后台准备好,整个协作效率能提升不少。
4.2 定时任务与消息队列的占位
如果你项目里有用到 Celery、django-crontab、django-apscheduler 这类任务调度组件,后端启动后注意检查两件事:定时任务注册了没有、worker 有没有拉起来。
我之前做过一个 Django 项目,启动后忘记启动 Celery worker,结果所有需要异步处理的接口在本地开发时都是“假装成功”,真正发消息、发邮件、生成报表的逻辑根本没执行。排查时表面看接口返回 200,但数据库里就是查不到预期数据,特别容易把人带偏。
检查 Celery 是否正常工作的快速命令:
bash复制celery -A yourproject worker -l info
celery -A yourproject beat -l info
如果项目只是简单需要定时清理临时文件、定期统计报表,不需要引入 Celery 这种重型组件,我用过 django-crontab,配置很简单:
bash复制pip install django-crontab
python复制# settings.py
INSTALLED_APPS = [
# ...
"django_crontab",
]
CRONJOBS = [
("*/10 * * * *", "your_app.tasks.clean_temp_files", ">> logs/cron.log 2>&1"),
]
启动后执行:
bash复制python manage.py crontab add
就能把定时任务挂到系统 cron 上。开发环境不需要真让 cron 跑起来,但至少要把任务函数写好、把注册方式验证一遍,防止上线前临时写定时逻辑,一点测试时间都没有。
4.3 按钮重复提交与接口幂等设计
热搜里有个词“前后端对于按钮重复提交校验方法”,这个对后端启动准备来说也很重要。前后端分离的项目里,用户双击提交按钮,或者前端网络慢导致重试,后端可能会收到多条一模一样的请求。如果每个请求都照常执行业务逻辑,就会产生重复订单、重复扣款。
后端能做的,是在接口层做幂等设计。最简单的方式是让前端在请求头带一个 Idempotency-Key,后端用一个 request_id 表存已经处理过的 key,看到重复 key 直接返回上一次的结果。伪代码如下:
python复制import uuid
def create_order(request):
idem_key = request.headers.get("Idempotency-Key")
if not idem_key:
idem_key = str(uuid.uuid4())
existed = IdempotencyRecord.objects.filter(key=idem_key).first()
if existed:
return JsonResponse(existed.response_data, status=200)
# 业务逻辑...
IdempotencyRecord.objects.create(key=idem_key, response_data=result)
return JsonResponse(result, status=201)
别小看这个动作。联调阶段前端不会故意发重复请求,但真正上线后,弱网环境下的重复提交是必然事件。启动准备阶段就把这个机制预留好,后面能省掉一堆客诉和补单操作。
5. 常见问题与排查技巧实录
5.1 迁移报错:依赖缺失和冲突怎么定位
python manage.py migrate 一执行,报错最常见的几类,我列了个速查表:
| 报错现象 | 常见原因 | 排查方向 |
|---|---|---|
No migrations to apply |
表已存在但迁移记录丢失 | 检查 django_migrations 表,比对迁移文件 |
Table 'xxx' already exists |
之前手工建表或迁移了部分流程 | 用 migrate --fake 时务必确认表结构是否真的与模型一致 |
relation does not exist |
数据库连接指向了错误的库 | 确认 DATABASES 配置、当前连接的库名 |
django.db.utils.OperationalError |
数据库服务没启动或权限不足 | 先用数据库客户端连一下,排除连接问题再谈迁移 |
这里我想提醒一点:--fake 这个参数能骗过 Django,让它以为迁移已经执行过,但它不会真正创建或修改表。当你手工改完表结构后想偷懒用 --fake,一定要先确认表结构已经满足模型约束,否则后面查询报错时根本不知道该往哪排查。
5.2 静态文件 404 的坑
开发模式下,后端能启动,但后台 Admin 页面或者自定义页面的 CSS、JS 加载不出来,十有八九是静态文件没配好。
python复制# settings.py
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
然后执行:
bash复制python manage.py collectstatic
DEBUG 模式下 Django 会自动处理静态文件,但如果你用了 django.contrib.staticfiles 之外的自定义路径,就要在 STATICFILES_DIRS 里配好。实际工作中我遇到过一个问题:前端把编译好的资源放在后端的 static 目录下,结果后端一启动发现所有资源 404,最后发现是文件权限不对,Nginx 或运行后端的用户没有读取权限。这种问题不看服务器日志很难定位,启动准备阶段顺手测一下静态文件能不能访问,能省不少事。
5.3 跨域配置明明加了,还是被拦截
有一种很隐蔽的跨域问题:后端加了 django-cors-headers,CORS_ALLOWED_ORIGINS 也写对了,但请求还是报 CORS 错误。这时候你先别怀疑配置,打开浏览器 Network 面板,看那个被拦截的请求是 OPTIONS 还是 GET/POST。
如果是 OPTIONS 被拦截,说明预检请求没通过,检查三点:
- 中间件位置是不是太靠后,被认证或 CSRF 中间件拦截了
- 请求头里有没有自定义的、不在
CORS_ALLOW_HEADERS里的字段 - 后端是否有全局异常处理把所有
OPTIONS请求都包裹成了 500
如果是 POST 被拦截,但 OPTIONS 正常,那就要检查后端有没有正确返回响应头。大概率是你在视图或中间件里覆盖了响应,导致 CORS 头被吞掉了。我一般建议把跨域测试做成一个接口层面的回归用例:
python复制def test_cors_header(client):
response = client.options("/api/test/")
assert response.headers.get("Access-Control-Allow-Origin") == "http://localhost:3000"
启动阶段把这种用例跑一遍,后面前端再说跨域报错时,你可以直接把测试结果拿出来说话,效率特别高。
5.4 一些启动后立刻能做的“小保健”
最后分享几个我每次启动后端后都会顺手做的小操作:
- 用
python manage.py check检查系统配置和依赖问题,这个命令不会连数据库,跑得很快,能提前暴露模型定义里的 warning。 - 用
python manage.py showmigrations查看迁移执行状态,明确当前库走到了哪个版本。 - 如果项目用了 DRF,在根路由里临时加一个
/api/docs/的接口文档入口,开发联调阶段直接给前端甩一个文档链接,比反复截图、传 word 文档高效太多。
python复制# urls.py
from rest_framework.documentation import include_docs_urls
urlpatterns = [
# ...
path("api/docs/", include_docs_urls(title="后端接口文档")),
]
文档是联调阶段的润滑剂,前端能自己查参数定义和返回结构,后端就不用一遍遍答疑了。项目稳定后如果不想对外暴露,再配置掉就行。
说实话,Django 后端启动只是万里长征第一步。我在实际项目里最深的体会是:启动阶段多花一小时做这些“小准备工作”,后面联调和部署阶段能省下好几天。很多人觉得这些活琐碎、不酷,不愿意做,结果就是开发时接口调不通、部署时配置漏项、上线后日志一片空白,到处救火。
我个人还有个习惯:每次启动新项目后,都会把这些准备步骤固化成一个 startup_checklist.md 文档,放在项目仓库的 docs/ 目录下。换机器、换人接手时,照着清单走一遍就行,不用从头摸索。如果你现在项目也刚跑起来,建议先把跨域、日志、数据库迁移、初始数据这几件事做扎实。后面再遇到什么幺蛾子,至少能确定不是你启动阶段埋的雷。
