Django 环境搭建与版本迁移
这篇讲什么
- 这是这套 Django 笔记的入口:38 篇文章按目录怎么读、当前基线是怎么定下来的、以及 2019 年那批 1.x 写法在今天哪些还能跑、哪些已经报错。
- 文末给了一个能整段复制跑起来的最小工程,以及迁移自己项目时的检查顺序。
这套 Django 笔记怎么读
-
地图如下。篇数是实测统计(
ls数出来的),「本批做了什么」指的是 2026-11-04 这一批只做了结构合并与版本口径统一,逐篇跑实测重写是后面几批的事。子目录 篇数 装什么 本批做了什么 入门 14 基本入门、settings 详解、模型、视图、模板、验证码、站点管理、中间件、上传文件、分页、Ajax、富文本、发送邮件、缓存 url()→re_path()、外键补on_delete、is_ajax()换成请求头判断、删updated:、补 tags;settings 详解REST框架 8 理解 RESTful、设计风格、序列化、请求与响应、基于类的视图、认证和权限、ViewSets 与 Routers、DRF 架构总结 同上(这批 url()最密集)异步任务与信号 2 Celery(架构与用法,原来两篇已合并)、Signals 同上,另挂 djcelery停更提示常用插件 1 常用插件清单(Xadmin 使用教程已并入《Django-站点管理》) 挂「已停更」提示,现代替代方案见第三批重写;目录 11-05 由「后台与常用插件」改名 开发方法论 2 快速迭代开发过程、通往 Hacker 之路 纯方法论,无代码,只补 tags 部署与运维 9 Sentry 架构总结、生产注意事项、水平扩展、让网站访问更快、监控告警、生产安全、云环境部署、持续集成、宝塔 + uWSGI 删 updated:、补 tags、url()口径统一 -
想快速上手的话,顺序建议是:本篇(环境和口径)→《Django 5.2 LTS 与 6.x 的区别对比》→
入门/Django-基本入门.md→入门/Django-模型.md→入门/Django-视图.md→入门/Django-模板.md→入门/Django-站点管理.md,再按需要进 REST框架 或 部署与运维。
环境与版本口径
-
版本基线:Django 5.2 LTS,本机实测用的是
5.2.19。选它而不是 6.1 的理由和逐条实测差异都在《Django 5.2 LTS 与 6.x 的区别对比》里,这里只留结论:6.2 LTS(2027-04)发布前不追 6.x,因为django-celery-beat这类常用包的上限还卡在Django<6.1,装了它 pip 会静默把 Django 降级。 -
实验环境是 conda 里一个独立环境
django,Python 用的是 base 同款的 3.14.10:1
2
3conda create -n django python=3.14 -y
conda activate django
python -V # Python 3.14.10 -
安装走中科大镜像(本机 pip 默认源慢):
1
pip install -i https://mirrors.ustc.edu.cn/pypi/web/simple Django==5.2.19
-
这一批在 5.2.19 环境里实测装上的包与确切版本(
pip list+importlib.metadata逐个数出来的,24 个全部 import 成功,pip check输出No broken requirements found.):包 版本 装成功 备注 Django 5.2.19 是 requires_python >= 3.10,LTS 延长支持到 2028-04djangorestframework 3.18.3 是 要求 django>=5.2django-filter 26.2 是 drf-spectacular 0.30.0 是 OpenAPI 文档,替代已停更的 drf-yasg celery 5.6.3 是 django-celery-results 2.6.0 是 要求 Django>=3.2.25,无上限django-celery-beat 2.9.0 是 元数据写着 Django<6.1,>=2.2,5.2 上正常,6.1 上会拖着你降级redis 8.1.0 是 django-redis 7.0.0 是 要求 Django<7.0,>=5.2PyMySQL 1.2.3 是 纯 Python 兼容路线,只有 install_as_MySQLdb()那种写法才需要它mysqlclient 2.3.0 是 官方推荐的 MySQL 驱动,导入名是 MySQLdb;Windows 有 cp314 wheel,wheel 自带的客户端库是 MariaDB Connector/C 3.4.9psycopg / psycopg-binary 3.3.8 是 官方推荐的 PostgreSQL 驱动(psycopg 3, psycopg2已不受支持),连 WSL 里的 PostgreSQL 18.6 实测通过cryptography 50.0.2 是 Pillow 13.0.0 是 ImageField 需要 gunicorn 26.2.0 是 uvicorn 0.54.0 是 ASGI whitenoise 6.12.0 是 静态资源 django-debug-toolbar 8.0.0 是 要求 django>=5.2django-tinymce 5.0.0 是 富文本 django-simple-captcha 0.7.0 是 验证码 django-ratelimit 4.1.0 是 模块名是 django_ratelimit,老笔记里写的ratelimit是另一个已死的包django-cors-headers 4.9.0 是 django-unfold 0.110.1 是 现代后台皮肤,本仓默认推荐(Xadmin 已停更,Simple UI 是备选) sentry-sdk 2.73.0 是 simpleui( django-simpleui)2026.1.13 是 后台皮肤;老笔记记的「源码包缺 README.md装不上」在 2026-11-05 复测已不成立,详见《Django-常用插件》xadmin 0.6.1 否 包还在 PyPI 上,但本机构建时 UnicodeDecodeError: 'gbk' codec can't decode byte 0xa4;用 UTF-8 模式装上后import xadmin又炸NameError: name 'reload' is not defined(Python 2 写法),停在 2018 年djcelery — 否 from versions: none,包已经不在 PyPI 上了,改用django-celery-beat+django-celery-resultsuwsgi 2.0.9 否 Windows 上 setup.py里用os.uname,直接AttributeError: module 'os' has no attribute 'uname';Linux 上线再谈 -
「静默降级」的日志长这样(这是在 6.1.3 环境里试装
django-celery-beat时抓的,一行 warning 都没有):1
2Successfully installed Django-6.0.10 cron-descriptor-1.4.5
django-celery-beat-2.9.0 django-timezone-field-7.2.2 python-crontab-3.4.0所以装任何 Django 生态包之前,先看它的
Requires-Dist里 Django 的上限,别等 pip 悄悄换掉你的版本。
Django 的版本线,以及我们为什么停在 5.2 LTS
-
官方支持表(2026-11-04 从 djangoproject.com/download/ 抓的):
发行系列 最新发布 主流支持终结 延长支持结束 5.2 LTS 5.2.19 2025-12-03 2028-04 6.0 6.0.10 2026-08-04 2027-04 6.1 6.1.3 2027-04 2027-12 6.2 LTS 计划 2027-04 发布 2027-12 2030-04 -
本套笔记的口径:现在按 5.2 LTS 写,2027 年 4 月 6.2 LTS 发布后整体切到 6.2 LTS。理由有三条:
- 5.2 的延长支持到 2028-04,6.2 LTS 到 2030-04,两条线接得上,不会出现「正在用的版本刚失去支持」;
- 6.0/6.1 不是 LTS,主流支持只有 8 个月左右,笔记里的写法跟着它们改一遍不划算;
- 生态兼容性现实:
django-celery-beat 2.9.0的元数据上限是Django<6.1。
-
一个必须知道的规则变化:6.2 是最后一版
A.B命名、也是最后一次「指定 LTS」。从 Django 2028 起每年 1 月发一版,每版都是三年支持(一年主流 + 两年延长),到时候不用再专门找 LTS 了。 -
Python 侧的约束:5.2 的
requires_python是>=3.10,6.0/6.1 是>=3.12。本机django环境是 3.14.10,实测 5.2.19 在 3.14 上startproject/migrate/check/runserver全跑通(System check identified no issues (0 silenced).、首页 HTTP 200),官方 FAQ 也把 3.14 列进了 5.2 的支持范围(从 5.2.8 起)。 -
换版本线时实测到的两个细节:
python -m django startproject生成的settings.py用单引号还是双引号,跟 Django 版本无关。真实机制是渲染完模板后startproject会调run_formatters(...),里面shutil.which("black")找到了就black --fast跑一遍。5.2.19 上亲测:PATH 里有 base 环境那个black.EXE时输出"django.contrib.admin",把 PATH 屏蔽掉就输出'django.contrib.admin'(模板settings.py-tpl本身两版都是单引号)。- 所以写脚本批量改 settings 时,替换完必须 assert 期望内容真的进了文件。这个坑我这轮踩过:
str.replace静默不生效,makemigrations直接报Model class blog.models.Grade doesn't declare an explicit app_label。
1.x 写法在 5.2.19 上的死活清单
-
下面每一行都是在
django环境里 import 一遍得到的真实结果,不是查文档抄的。同一套检查也跑过 6.1.3,18 条结果逐字一致(比对前把报错里各自的安装路径归一化),所以这批替换不随版本基线变化。旧写法 在 Django 5.2.19 上的实测结果 现代写法 from django.conf.urls import urlImportError: cannot import name 'url' from 'django.conf.urls',后面还带一句Did you mean: 'urls'?from django.urls import path, re_pathfrom django.core.urlresolvers import reverseModuleNotFoundError: No module named 'django.core.urlresolvers'from django.urls import reversefrom django.utils.translation import ugettextImportError: cannot import name 'ugettext' from 'django.utils.translation'gettext/gettext_lazyfrom django.utils.encoding import force_textImportError: cannot import name 'force_text' from 'django.utils.encoding'force_str/smart_strfrom django.utils import sixImportError: cannot import name 'six' from 'django.utils'删掉,直接 Py3 写法 @python_2_unicode_compatibleImportError: cannot import name 'python_2_unicode_compatible'删装饰器,只写 __str__from django.shortcuts import render_to_responseImportError: cannot import name 'render_to_response' from 'django.shortcuts'render(request, ...)from django.utils.http import urlquoteImportError: cannot import name 'urlquote' from 'django.utils.http'urllib.parse.quotefrom django.contrib.staticfiles.templatetags.staticfiles import staticModuleNotFoundError: No module named 'django.contrib.staticfiles.templatetags'模板里 {% static %},代码里django.templatetags.static.staticSignal(providing_args=[...])TypeError: Signal.__init__() got an unexpected keyword argument 'providing_args'Signal()ForeignKey("Grade")TypeError: ForeignKey.__init__() missing 1 required positional argument: 'on_delete'ForeignKey("Grade", on_delete=models.CASCADE)include('myApp.urls', namespace="x")能 import,但要求被包含的 urls.py 里有 app_name,否则reverse找不到app_name写在 app 的 urls.py,include()里不再写 namespacerequest.is_ajax()hasattr(request, 'is_ajax')为 Falserequest.headers.get("x-requested-with") == "XMLHttpRequest"render_to_string('a.html', context, request)签名是 (template_name, context=None, request=None, using=None),第三个位置参数就是 request显式写 render_to_string(..., request=request) -
最危险的是三类「静默失效」:不报错、
check也全过,但配置根本没生效。5.2.19 实测:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
django.setup()
from django.conf import settings
from django.core.management import call_command
# 1) 老中间件设置名
settings.MIDDLEWARE_CLASSES = ["django.middleware.common.CommonMiddleware"]
call_command("check")
print("check 没报错;MIDDLEWARE 仍是", len(settings.MIDDLEWARE), "条")
# 2) 老模板设置名
settings.TEMPLATE_CONTEXT_PROCESSORS = ("nonexistent.processor",)
from django.template import engines
print(engines["django"].engine.context_processors)
# 3) 老的用户扩展设置名
settings.AUTH_PROFILE_MODULE = "myapp.Profile"
from django.contrib.auth import get_user_model
print(get_user_model())1
2
3
4System check identified no issues (0 silenced).
check 没报错;MIDDLEWARE 仍是 7 条
['django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages']
<class 'django.contrib.auth.models.User'>MIDDLEWARE_CLASSES写了等于没写,check一声不吭,真正生效的还是MIDDLEWARE里那 7 条。TEMPLATE_CONTEXT_PROCESSORS同理:设一个根本不存在的处理器都不报错,引擎用的还是TEMPLATES里那三个;配置要写进TEMPLATES[...]["OPTIONS"]["context_processors"]。AUTH_PROFILE_MODULE也一样,设上之后get_user_model()照常返回django.contrib.auth.models.User,这个键只是躺在 settings 上没人读。
一个能跑的现代最小工程
-
下面这套是用 5.2.19 真跑过的(2026-11-04 抓的输出),逐条对应上面的现代写法。
1
2
3django-admin startproject mysite
cd mysite
python manage.py startapp blog -
mysite/settings.py里加应用:settings.py 1
2
3
4
5
6
7
8
9
10
11
12ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"blog",
] -
blog/models.py:外键必须给on_delete,信号不再写providing_args。models.py 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from django.conf import settings
from django.db import models
from django.dispatch import Signal
article_published = Signal() # 5.2 已经没有 providing_args 这个参数
class Grade(models.Model):
name = models.CharField(max_length=32)
def __str__(self): # 不再需要 @python_2_unicode_compatible
return self.name
class Student(models.Model):
name = models.CharField(max_length=32)
grade = models.ForeignKey(Grade, on_delete=models.CASCADE)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="students",
on_delete=models.CASCADE) -
blog/urls.py:命名空间写在app_name,不再写在include()里。urls.py 1
2
3
4
5
6
7
8
9
10from django.urls import re_path
from . import views
app_name = "sunck" # 命名空间来自这里
urlpatterns = [
re_path(r"^students/(?P<pk>\d+)/$", views.grade_students, name="t3test"),
re_path(r"^fired/$", views.fired, name="fired"),
] -
mysite/urls.py:path()管简单路由,re_path()管正则。urls.py 1
2
3
4
5
6
7
8
9
10
11from django.contrib import admin
from django.urls import include, path, re_path
from blog import views
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.index, name="index"),
re_path(r"^", include("blog.urls")), # 命名空间走 blog/urls.py 的 app_name
path("api-auth/", include("rest_framework.urls")), # DRF 自己已带 app_name
] -
blog/views.py:is_ajax()没了,自己看请求头。views.py 1
2
3
4
5
6
7
8from django.http import HttpResponse, JsonResponse
from .models import Student, article_published
def index(request):
ajax = request.headers.get("x-requested-with") == "XMLHttpRequest"
return JsonResponse({"ajax": ajax, "students": Student.objects.count()}) -
建库、检查、跑起来,实测输出:
1
2
3
4python manage.py makemigrations blog
python manage.py migrate
python manage.py check
python manage.py runserver 127.0.0.1:8431 --noreload1
2
3
4
5
6Migrations for 'blog':
blog\migrations\0001_initial.py
+ Create model Grade
+ Create model Student
Applying sessions.0001_initial... OK
System check identified no issues (0 silenced).1
2
3
4[04/Nov/2026 20:12:25] "GET / HTTP/1.1" 200 29
[04/Nov/2026 20:12:25] "GET /students/7/ HTTP/1.1" 200 4
[04/Nov/2026 20:12:25] "GET /fired/ HTTP/1.1" 200 11
[04/Nov/2026 20:12:25] "GET /admin/login/ HTTP/1.1" 200 4160- 带
X-Requested-With: XMLHttpRequest请求/回来的是{"ajax": true, "students": 0},说明请求头判断这条路是对的。 - 反向解析实测:
reverse("sunck:t3test", kwargs={"pk": 5})得到/students/5/;模板里{% url 'sunck:fired' %}得到/fired/;{% static 'x.css' %}得到/static/x.css。
- 带
迁移自己项目时的检查顺序
- 按这个顺序走,能把「升级后莫名不生效」的坑一次排掉:
- 先只升级 Django 本身,第三方包全部不动,跑
python manage.py check; - 打开
-W error::DeprecationWarning或把LOGGING里py.warnings打出来,看有多少弃用警告; - 装任何会连带解析依赖的包之前(
pip install django-celery-beat这种),先看它的Requires-Dist里 Django 的上限,别让 pip 静默降级; - 把 settings 里那些「老名字」逐个搜一遍:
MIDDLEWARE_CLASSES、TEMPLATE_DIRS、TEMPLATE_CONTEXT_PROCESSORS、TEMPLATE_LOADERS、AUTH_PROFILE_MODULE—— 它们不会报错,只会静默失效; - 路由层全量搜
url(、django.conf.urls、include(..., namespace=;模型层搜不带on_delete的ForeignKey(/OneToOneField(/ManyToManyField((多对多不强制,但外键必须); - 用脚本改 settings 的话,改完
assert期望内容确实在文件里(引号风格会被本机的 black 改变,见上一节); - 最后跑一遍真实请求(
runserver+ 首页 + 一个带命名空间的反查),确认不是「能 import 但跑不起来」。
- 先只升级 Django 本身,第三方包全部不动,跑
参考
- Django download:当前支持的版本线、LTS 标注与 Future Roadmap。
- Django release process:主流支持 / 延长支持的定义,以及 2028 年起每年一版的新政策。
- Django FAQ:What Python version can I use with Django:各 Django 版本对应的 Python 支持范围。
- Django 5.2 release notes:本基线的新特性清单(
CompositePrimaryKey就是 5.2 加的)。 - django-celery-beat on PyPI:
Django<6.1这条约束的出处。 - django-unfold:本仓默认推荐的现代后台皮肤(Xadmin 已停更,Simple UI 是可用的备选)。