Django 5.2 LTS 与 6.x 的区别对比
这篇讲什么
- 这篇记录本套笔记的版本基线为什么从 6.1.x 改回 5.2 LTS,以及 5.2.19 与 6.1.3 之间到底差在哪。
- 结论都有实测支撑:56 条 API 探针跑出的差异清单、
pip装django-celery-beat时静默降级 Django 的日志、以及本笔记已经改过的现代写法在 5.2.19 上重新跑通的结果。真要升 6.x(或从 6.x 回退),文末的检查清单可以照着过一遍。
先说结论
- 这一轮把本套笔记的版本基线从 6.1.x 改回 5.2 LTS(实测环境是 5.2.19),等 2027 年 4 月 6.2 LTS 正式发布后整体切到 6.2 LTS。
- 回退不是「6.x 不好」,而是两条实测结果叠加之后的选择:
- 生态包的上限还没放开。
django-celery-beat 2.9.0的元数据写着Django<6.1,>=2.2,在 6.1.3 环境里pip install django-celery-beat会静默把 Django 降到 6.0.10(日志见下文),等于你以为在用 6.1,其实用的是 6.0。 - 6.x 的破坏性变更集中且没必要现在吃。56 条探针跑下来,6.x 相对 5.2 移除了 11 项、改了 20 项行为、新增了 14 项;而这套笔记需要的新特性(CSP 中间件、
django.tasks、AsyncPaginator)一个都用不上。
- 生态包的上限还没放开。
- 反向验证也做了:本套笔记第一批改的现代写法(
re_path、app_name命名空间、外键on_delete、Signal()、请求头判 Ajax)在 5.2.19 上真跑通了 ——makemigrations、migrate、check全部 rc=0,四个页面 HTTP 200。所以换基线不影响已经改完的内容。
支持时间表:LTS 到底意味着什么
-
下面是 2026-11-04 从官方下载页抓的支持表(列名照抄:Release Series / Latest Release / End of mainstream support / End of extended support):
发行系列 最新补丁版 主流支持结束 延长支持结束 今天(2026-11-04)的状态 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 还在主流支持期内 -
「主流 / 延长」两级支持的具体含义(官方发布流程文档的措辞):
- 主流支持期:会往这条发布分支上回合的补丁包括 —— 安全问题、数据丢失 bug、崩溃 bug、新特性里的重大功能 bug,以及本系列引入的回归。
- 延长支持期:只收安全修复和数据丢失修复,其它 bug 不再动这条分支。
- 所以 5.2 现在的状态是「安全无忧、bug 自理」,到 2028-04 才失去支持;对一个学习笔记加个人项目来说,这个窗口足够长。
-
Python 侧的支持矩阵(官方 FAQ 表 + 本机
Requires-Python元数据实测):Django 官方支持的 Python 元数据 Requires-Python(本机实测)5.2 3.10、3.11、3.12、3.13、3.14(3.14 从 5.2.8 起) >=3.106.0 3.12、3.13、3.14 — 6.1 3.12、3.13、3.14、3.15(3.15 从 6.1.3 起) >=3.12- 本机
django环境和 base 都是 Python 3.14.10,5.2.19 与 6.1.3 都在官方支持范围内,所以「选 5.2 还是选 6.1」不能用 Python 版本当理由。 - 真正的差别在 3.10、3.11:只有 5.2 还认它们。公司里那台不能升级 Python 的老机器要装 Django,5.2 是唯一选项。
- 本机
6.2 之后的游戏规则变了
-
官方 Future Roadmap(同一页面,2026-11-04 抓的):
发行系列 发布 主流支持结束 延长支持结束 6.2 LTS 2027-04 2027-12 2030-04 2028 2028-01 2029-01 2031-01 2029 2029-01 2030-01 2032-01 -
官方原话里的两个要点,直接决定了「以后要不要再找 LTS」:
- 6.2 是最后一版按
A.B命名、也是最后一次「指定 LTS」:原文是 「Django 6.2 is the final release under the previous versioning and support policy」。 - 从 Django 2028 起每一版都有三年支持,LTS 这个词不再有特殊含义:原文是 「Starting with Django 2028, every feature release receives the same three-year support period」,节奏也从「大约每 8 个月一版」改成「每年一版,1 月发」。
- 6.2 是最后一版按
-
顺手抓到的一个佐证:6.1.3 的
django/conf/__init__.py里已经在用RemovedInDjango2028Warning这个新的弃用级别,弃用提示直接写着「Django 2028 会移除」。 -
对本套笔记的意义:2027-04 切到 6.2 LTS 之后,下一次迁移是 2028-01 的 Django 2028,而且从此以后每年一版、每版支持三年,选版本的逻辑会比现在简单得多。
56 条探针逐项对比
- 做法是把同一份代码分别在两个解释器上执行再逐项 diff:56 项里 11 项是 6.x 移除、14 项是 6.x 新增、20 项行为不同、11 项相同。
- 写探针时踩到的三个坑,值得单独记一下,否则会得到「两版相同」的假结论:
-
必须先
settings.configure()。不配 settings 时forms.URLField()、serializers.serialize()全是ImproperlyConfigured,两版一样,就被判成「相同」了。 -
getattr要拿类而不是模块。getattr(django.db.backends.base.operations, "field_cast_sql")一定AttributeError,跟版本无关;上一版探针有 10 条栽在这里。 -
django.contrib.postgres的聚合函数会连带import psycopg,本机没装驱动。想看它的签名就用ast解析源码文件,不要执行导入:1
2
3
4
5# 不导入模块,直接读源码里类定义的基类
tree = ast.parse(open(path, encoding="utf-8").read())
for node in tree.body:
if isinstance(node, ast.ClassDef) and node.name == "ArrayAgg":
print([ast.unparse(b) for b in node.bases])
-
6.x 移除的(5.2 上还在)
| 探针 | 5.2.19 实测 | 6.1.3 实测 | 说明 |
|---|---|---|---|
django.utils.itercompat.is_iterable |
<function is_iterable> |
ModuleNotFoundError: No module named 'django.utils.itercompat' |
只会撞在老三方库里 |
django.db.models.enums.ChoicesMeta |
<class '...ChoicesType'>(是别名) |
AttributeError: module 'django.db.models.enums' has no attribute 'ChoicesMeta' |
同上 |
BaseDatabaseOperations.field_cast_sql |
存在于 operations.py:217 |
AttributeError: type object 'BaseDatabaseOperations' has no attribute 'field_cast_sql' |
自定义 DB backend 才会碰 |
Prefetch.get_current_queryset |
<function Prefetch.get_current_queryset>(5.2 已弃用) |
AttributeError |
改名 get_current_querysets(level) |
ForwardManyToOneDescriptor.get_prefetch_queryset |
<function ...> |
AttributeError |
改名 get_prefetch_querysets(instances, querysets=None) |
ModelAdmin.log_deletion |
<function ModelAdmin.log_deletion> |
AttributeError |
只剩 log_deletions(request, queryset),批量语义 |
FileSystemStorage.OS_OPEN_FLAGS |
34049 |
AttributeError |
类属性删了,自定义存储别再去读它 |
ForeignObject.get_joining_columns |
<function ForeignObject.get_joining_columns> |
AttributeError |
用 get_joining_fields() 一族 |
CheckConstraint(check=...) |
能构造,但报 RemovedInDjango60Warning: CheckConstraint.check is deprecated in favor of .condition. |
TypeError: CheckConstraint.__init__() got an unexpected keyword argument 'check' |
一律写 condition= |
format_html("x")(零参数) |
'x' |
TypeError: args or kwargs must be provided. |
只拼静态字符串时改用普通字符串常量 |
默认值与签名变了
| 探针 | 5.2.19 | 6.1.3 | 影响 |
|---|---|---|---|
global_settings.DEFAULT_AUTO_FIELD |
django.db.models.AutoField |
django.db.models.BigAutoField |
新项目的自增主键位数不同,会产生一次真实迁移 |
PBKDF2PasswordHasher.iterations |
1000000 | 1500000 | 6.x 上登录时自动升级哈希参数,用户无感 |
URLField().assume_scheme |
http |
https |
表单把 example.com 补成哪个协议 |
URLField().to_python("example.com") |
http://example.com |
https://example.com |
存库结果直接不同 |
global FORMS_URLFIELD_ASSUME_HTTPS |
有这个设置 | 已删 | 5.2 上它控制上面那个警告 |
Model.save 签名 |
(self, *args, force_insert=False, ...) |
(self, *, force_insert=False, ...) |
6.x 不允许位置参数,obj.save(True) 会炸 |
serializers.serialize("json", []) |
'[]' |
'[]\n' |
6.x 结尾多一个换行,做摘要/字节比对要留意 |
staticfiles.finders.find 签名 |
(path, find_all=False, **kwargs) |
(path, find_all=False) |
自定义 finder 的转发写法要跟着改 |
ArrayAgg 的基类 |
['OrderableAggMixin', 'Aggregate'] |
['Aggregate'] |
ordering= 这个关键字没人接了(见「静默变化」一节) |
Aggregate.__init__ 参数 |
无 order_by |
多了 order_by |
6.x 用 order_by=,且不是所有聚合都允许 |
global USE_BLANK_CHOICE_DASH |
无此设置 | False(且已被标 RemovedInDjango2028Warning) |
以后改 django.db.models.fields.BLANK_CHOICE_LABEL |
global SIGNED_COOKIE_LEGACY_SALT_FALLBACK |
True(core/signing.py 真的在读) |
False,设置本身已弃用 |
6.x 不再兜底旧 cookie 盐 |
包元数据 Requires-Python |
>=3.10 |
>=3.12 |
老 Python 只能选 5.2 |
6.x 新增的(5.2 上确实没有)
| 探针 | 6.1.3 实测 | 5.2.19 | 要不要为了它升 6.x |
|---|---|---|---|
django.utils.csp.CSP |
<enum 'CSP'> |
ModuleNotFoundError |
想要 CSP 也可以先用 django-csp 三方包 |
django.middleware.csp.ContentSecurityPolicyMiddleware |
存在 | ModuleNotFoundError: No module named 'django.middleware.csp' |
同上,注意模块路径不是 django.middleware.security |
django.tasks.task + global TASKS |
TASKS = {'default': {'BACKEND': 'django.tasks.backends.immediate.ImmediateBackend'}} |
无 django.tasks |
官方版的「延迟任务」,本套笔记用 Celery,不需要 |
AsyncPaginator |
存在 | AttributeError |
ASGI 场景才有意义 |
models.StringAgg / AnyValue / BitAnd |
进核心 django.db.models |
只能从 django.contrib.postgres 拿 |
PostgreSQL 专属聚合进了核心,非 PG 库也能 import |
models.JSONNull |
django.db.models.expressions.JSONNull |
AttributeError |
JSON 字段比较用 |
exceptions.ObjectNotUpdated + 模型子类 NotUpdated |
<class 'probe.ProbeNotUpdated.NotUpdated'>,基类 ObjectNotUpdated |
模型类上「无 NotUpdated」 | save(update_fields=...) 没影响行时能显式报错 |
models.FETCH_ONE / FETCH_PEERS / FETCH_RAISE、QuerySet.fetch_mode() |
三个都是 fetch_modes 里的单例,QuerySet.fetch_mode(self, fetch_mode) 文档串是 「Set the fetch mode for the QuerySet.」 |
全部 AttributeError |
6.1 的预取内存控制 |
models.DB_CASCADE |
<DatabaseOnDelete>,可当 on_delete 用 |
AttributeError |
把级联交给数据库外键,需要 DB 支持 |
core.mail.mailers(MailersHandler) |
<django.core.mail.handler.MailersHandler object>;django.core.mail.handler 模块本身 5.2 没有 |
无 | MAILERS 是新的邮件配置入口,get_connection() 已标弃用但仍可用 |
forms.Stylesheet |
django.forms.widgets.Stylesheet |
AttributeError |
表单媒体 CSS 声明 |
模板标签 partialdef |
已注册 | 未注册 | {% partialdef %} / {% partial %} 模板片段复用 |
模板标签 querystring |
已注册 | 已注册 | 这条是「相同」,5.2 就有 |
包兼容性矩阵:回退的直接原因
-
在
django环境(Python 3.14.10)把Django==5.2.19和 21 个生态包全装一遍,逐个import复测,pip check输出No broken requirements found.:包 版本 5.2.19 环境实测 Django 5.2.19 import OK djangorestframework 3.18.3 import OK django-filter 26.2 import OK drf-spectacular 0.30.0 import OK celery 5.6.3 import OK django-celery-results 2.6.0 import OK django-celery-beat 2.9.0 import OK(6.1 上就是它把 Django 拖下去的) redis 8.1.0 import OK django-redis 7.0.0 import OK PyMySQL 1.2.3 import OK cryptography 50.0.2 import OK Pillow 13.0.0 import OK gunicorn 26.2.0 import OK uvicorn 0.54.0 import OK whitenoise 6.12.0 import OK django-debug-toolbar 8.0.0 import OK django-tinymce 5.0.0 import OK django-simple-captcha 0.7.0 import OK django-ratelimit 4.1.0 import OK,模块名是 django_ratelimitdjango-cors-headers 4.9.0 import OK django-unfold 0.110.1 import OK,它自己要求 django>=5.2sentry-sdk 2.73.0 import OK -
同批包在 6.1.3 上唯一的硬伤就是
django-celery-beat。它的元数据实测内容:1
2python -m pip install --no-deps --target /tmp/x django-celery-beat
python -c "from importlib.metadata import distribution as d; print(d('django-celery-beat').metadata['Requires-Dist'])" 2>&11
2
3celery<6.0,>=5.2.3 ; importlib-metadata<5.0; python_version < "3.8" ;
django-timezone-field>=5.0 ; backports.zoneinfo; python_version < "3.9" ;
tzdata ; python-crontab>=2.3.4 ; cron-descriptor<2.0.0,>=1.2.32 ; Django<6.1,>=2.2-
这条
Django<6.1,>=2.2就是全部故事:pip 为了满足它,宁愿把 Django 换小。 -
当时 6.1.3 环境里的真实安装日志(
switch_to_django52.py之前那次):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-6.0.10,这就是「静默降级」:命令 rc=0,没有任何 warning 行提示你 Django 被换了。
-
-
另外三个装不上的包,换成 5.2 也照样装不上(本次在 5.2.19 环境里用
--no-deps --target复测,报错逐字一致):包 实测错误 根因跟 Django 版本有关吗 uwsgi AttributeError: module 'os' has no attribute 'uname'然后Failed to build 'uwsgi' ...无关,Windows 上没有 os.uname,跟 Python 3.14 也无关xadmin UnicodeDecodeError: 'gbk' codec can't decode byte 0xa4 in position 3444然后Failed to build 'xadmin' when getting requirements to build wheel装这一步无关(是本机默认 GBK 读它自己的 setup.py);但用PYTHONUTF8=1绕过后import xadmin会炸NameError: name 'reload' is not defined,那是 Python 2 代码,跟 Django 版本也没关系djcelery ERROR: Could not find a version that satisfies the requirement djcelery (from versions: none)有关,PyPI 上已经没有这个包了,Celery 4+ 用 django-celery-beat/django-celery-results -
原来这张表里还有第四个
django-simpleui(当时报FileNotFoundError: ... 'README.md'),2026-11-05 复测已经不成立了:上游改用日历版本号,2026.1.13能装能用,连旧的4.0.3现在也能重新构建成功,所以它挪到了《Django-常用插件》的「可用」清单里。这条也算给「装不上」这类结论提个醒:它会随上游重新发包而失效,要定期复测。
老写法在 5.2 和 6.1 上死得一模一样
-
这批 API 早在 2.0 到 4.0 就被删光了,所以「换回 5.2」不会让第一批的改写作废。
django_api_probe.py现在会把 18 条老写法在两个解释器上各跑一次并逐字比对:18 条全部一致(比对前把报错里各自的安装路径归一化成(site-packages),否则「不一致」全是路径差异造成的假阳性)。旧写法 5.2.19 与 6.1.3 共同的实测结果 现代写法 from django.conf.urls import urlImportError: cannot import name 'url' from 'django.conf.urls' (site-packages). 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' (site-packages)gettext/gettext_lazyfrom django.utils.encoding import force_textImportError: cannot import name 'force_text' from 'django.utils.encoding' (site-packages)force_str/smart_strfrom django.utils import sixImportError: cannot import name 'six' from 'django.utils' (site-packages)删掉,直接 Py3 写法 Signal(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)request.is_ajax()有 is_ajax 吗: Falserequest.headers.get("x-requested-with") == "XMLHttpRequest"AUTH_PROFILE_MODULE设了之后 get_user_model()仍是<class 'django.contrib.auth.models.User'>删掉,用 OneToOneField render_to_string签名(template_name, context=None, request=None, using=None)显式写 request=request -
现代最小工程在 5.2.19 上的实跑输出(2026-11-04 抓的):
1
2
3
4
5
6
7
8
9
10
114) makemigrations rc=0 Migrations for 'blog':
blog\migrations\0001_initial.py
+ Create model Grade
+ Create model Student
5) migrate rc=0 Applying sessions.0001_initial... OK
6) check rc=0 System check identified no issues (0 silenced).
7) 命名空间反向解析 rc=0 reverse: /students/5/ 模板里 url: /fired/ static 标签: /static/x.css
8) GET / rc=200 body={"ajax": true, "students": 0}
9) GET /students/7/ rc=200 body=pk=7
10) GET /fired/ rc=200 body=signal sent
11) GET /admin/login/ rc=200 字节 4160
最需要提防的是「静默变化」
-
上面这些
ImportError都算友好 —— 立刻炸、信息明确。真正危险的是不报错但行为变了这一类,实测到三个: -
1)聚合函数的
ordering=掉了。5.2 里ArrayAgg、JSONBAgg、StringAgg都继承OrderableAggMixin,这个 mixin 的__init__参数是['order_by', 'ordering', 'self'];6.1 里三个类都不再继承它(who_uses探针返回「无人继承」),OrderableAggMixin.__init__也「方法未找到」。于是:1
2
3
4
5
6from django.db.models import StringAgg
a = StringAgg("name", ",", ordering=["-id"]) # 5.2 靠 mixin 接住;6.1 没人接
b = StringAgg("name", ",", order_by=["-id"]) # 6.x 的正确写法
print(a.extra)
print(b)1
2
3# 6.1.3 实测输出(5.2.19 上 core 里还没有 StringAgg,用 Count 演示同一机制)
{'ordering': ['-id']}
StringAgg(F(name), StringAggDelimiter(F(,)), order_by=OrderBy(F(id), descending=True))- 关键点:未知关键字会被
Aggregate.__init__(**extra)咽进.extra里,不报错、check也不管,SQL 里的排序就这么没了。5.2 与 6.1 上Count("id", ordering=["-pk"])都是.extra == {'ordering': ['-pk']},两版一样沉默。 - 6.1 里
Aggregate.__init__的参数多了order_by,但不是所有聚合都允许:Count("id", order_by=["-pk"])直接TypeError: Count does not allow order_by.。
- 关键点:未知关键字会被
-
2)JSON 序列化多了个结尾换行:5.2 是
'[]',6.1 是'[]\n'。拿序列化结果算哈希、比对 fixture、或者做接口幂等校验的代码会莫名其妙对不上。 -
3)cookie 签名盐的兜底默认值反了:
SIGNED_COOKIE_LEGACY_SALT_FALLBACK在 5.2.19 是True且core/signing.py:_unsign_cookie真的在读它(旧盐验签失败时兜一次),6.1 默认False。6.1 里显式设置它还会吃两条RemovedInDjango2028Warning,实测警告文案:1
2
3The SIGNED_COOKIE_LEGACY_SALT_FALLBACK transitional setting is deprecated. Remove it
from your settings once legacy signed cookies have expired. They will not be accepted
in Django 2028. -
顺手纠正一条我自己之前写错的结论。我原先说「
startproject生成的settings.py里 6.x 用双引号、5.x 用单引号」,这是错的:1
2# 同一个 Django 5.2.19,只改 PATH 里有没有 black
python -m django startproject nf E:\Desktop\Test\nf1
2PATH 含 D:\ProgramData\anaconda3\Scripts(black.EXE 在这)时:INSTALLED_APPS 里是 "django.contrib.admin"
PATH 屏蔽掉 black 之后:INSTALLED_APPS 里是 'django.contrib.admin'- 真实机制是
startproject渲染完模板后调了一次run_formatters(...),它会shutil.which("black"),找到就跑black --fast。5.2 和 6.1 这段代码都在django/core/management/utils.py:166,一模一样。 - 而
settings.py-tpl模板本身两版都是单引号。所以引号风格取决于本机有没有装 black,跟版本无关;我用单引号去str.replace生成出来的 settings.py 才会静默失配(症状是INSTALLED_APPS没加进去,makemigrations报Model class blog.models.Grade doesn't declare an explicit app_label)。写脚本改生成文件时,正确做法是改完再assert期望内容确实出现在文件里。
- 真实机制是
真要升 6.x(或从 6.x 回退到 5.2)时的检查清单
- 升级方向(5.2 → 6.x)按这个顺序走:
- 先钉住依赖:
python -m pip freeze > base.txt,再看每个包的 Django 上限:把输出贴到笔记里,升级前先确认没有一个包写着1
2
3
4
5
6
7python - <<'PY'
from importlib.metadata import distributions
for d in sorted(distributions(), key=lambda x: x.metadata["Name"].lower()):
for r in d.requires or []:
if r.lower().startswith("django"):
print(f"{d.metadata['Name']:28} {r}")
PYDjango<6。 - 只在 5.2 上把弃用警告清零:
python -W error::DeprecationWarning manage.py check,再跑一遍测试。5.2 上还有RemovedInDjango60Warning,它清干净了才谈升级。 - 逐项替换(就是本笔记实测到的移除清单):
CheckConstraint(check=)改condition=;Prefetch.get_current_queryset改get_current_querysets;get_prefetch_queryset改get_prefetch_querysets;ModelAdmin.log_deletion改log_deletions;自定义存储别再读FileSystemStorage.OS_OPEN_FLAGS;format_html("纯文本")改成普通字符串。 - 签名类:全仓搜
\.save(后面跟位置参数的调用(obj.save(True)这种),6.x 只接受关键字参数。 - 数据类:新项目在 6.x 下
DEFAULT_AUTO_FIELD默认变成BigAutoField;已有项目显式写着AutoField就别动,改了要生成一次真实迁移并评估回滚。 - 表单类:
URLField默认协议从http变https,同时FORMS_URLFIELD_ASSUME_HTTPS这个设置消失。用户表里存的是「用户填的原样」还是「补全后的 URL」,要先确认。 - 聚合类:把
ordering=全改成order_by=,并且确认该聚合允许排序(Count就不允许)。 - 最后跑真实请求,不是只跑
check。
- 先钉住依赖:
- 回退方向(6.x → 5.2)要动的是另一批:
- 用掉过
django.tasks/TASKS的地方改回 Celery; ContentSecurityPolicyMiddleware(django.middleware.csp)改回django-csp或自己往HttpResponse塞头;{% partialdef %}/{% partial %}改回{% include %}加with;MAILERS改回EMAIL_BACKEND+get_connection();- 依赖
Model.NotUpdated/ObjectNotUpdated/DB_CASCADE/FETCH_ONE/forms.Stylesheet/AsyncPaginator的代码全部撤掉,5.2 上它们不存在。
- 用掉过
本套笔记现在的版本口径
- 基线:Django 5.2.19 LTS,Python 3.14.10,conda 环境
django,安装走中科大镜像。 - 全部 38 篇 Django 笔记里的写法统一按「5.2 与 6.x 都能跑」的标准来写,也就是:
- 只用两版都存在且行为一致的现代 API(
path/re_path/app_name/on_delete/Signal()/request.headers); - 6.x 才有的东西一律先讲 5.2 的做法,再用一行注明「6.0 起有
django.tasks这类官方替代」,不拿它当默认写法; - 5.2 才有的(比如
SIGNED_COOKIE_LEGACY_SALT_FALLBACK = True)不写进示例。
- 只用两版都存在且行为一致的现代 API(
- 切换计划:2027-04 6.2 LTS 发布后整体切到 6.2 LTS,切之前先确认
django-celery-beat放开上限、并且上面「升级方向」那 8 步在本仓跑通。
参考
- Django download:支持时间表与 Future Roadmap,本篇两张表的数据来源。
- Django release process:主流支持 / 延长支持的定义,以及 2028 年起每年一版、每版三年的新政策。
- Django FAQ:What Python version can I use with Django:各 Django 版本对应的 Python 支持范围。
- Django 6.0 release notes 与 6.1 release notes:被移除的 API 清单。
- django-celery-beat on PyPI:
Django<6.1这条约束的出处。 - black:
startproject末尾会调用它,所以生成文件的引号风格由本机是否装了 black 决定。