Django1.8升级到Django4.2升级方案(包含python2.8升级到python3.11)
Django升级方案
Python2.x到SaaS可以先做一次python版本升级。
- 安装2to3代码升级工具python3 -m pip install 2to3
- 使用2to3进行代码升级python3 -m 2to3 -w {项目目录}
python2和python3差异
| 方面 | Python 2 | Python 3 | 迁移建议 |
|---|---|---|---|
print 语句 | print "hello" | print("hello") | 使用函数形式 print() |
| 整数除法 | 5 / 2 == 2 | 5 / 2 == 2.5 | 使用 // 取整,/ 取浮点 |
| 字符串类型 | ASCII 默认,str 是字节串 | Unicode 默认,str 是文本串 | 注意编码处理,使用 bytes 与 str 区分 |
xrange 和 range | xrange 返回迭代器,range 返回列表 | range 返回迭代器 | 用 range() 替代 xrange() |
dict.iteritems() | 返回迭代器 | 直接用 dict.items() | 代码替换 |
| 异常语法 | except Exception, e: | except Exception as e: | 修改异常捕获语法 |
raw_input() | 输入字符串函数 | 改为 input() | 修改用户输入处理 |
一、Django1.8升级到Django1.11.12
1.1 settings设置变动
变动:
| 变更前 | 变更后 |
|---|---|
| LANGUAGE_CODE = 'zh-CH' | LANGUAGE_CODE = 'zh-hans' |
1.2 路由定义变动
| 变动前 | 变动后 |
|---|---|
| from django.conf.urls import patterns patterns() | urlpatterns = [] |
| (r'^router/', 'moudle.views.viewfunc') | url(r'^router/', moudle.views.viewfunc) |
二、Django1.11升级到Django2.2.12
2.1 模型关系字段必须显式写 on_delete
在 Django 1.11 你可以写:
user = models.ForeignKey(User)在 2.2 这是不合法的,必须指定:
user = models.ForeignKey(User, on_delete=models.CASCADE)检查所有 ForeignKey、OneToOneField、ManyToManyField(虽然 ManyToMany 不需要 on_delete)并补齐。
升级注意事项
- 如果你原来在 1.11 中某些外键 没有 写 on_delete(默认是 CASCADE),在升级时必须显式指定一个行为(包括 models.PROTECT);
- 已经写成 on_delete=models.PROTECT 的不需要因为版本换做别的处理。
2.2 MIDDELWARE_CLASSES 变更为MIDDELWARE
2.3 Django TEMPLATES配置变化
2.3.1 前因
- 在 Django 1.11 中,django.contrib.messages.context_processors.messages 和相关的 MessageMiddleware 虽然是推荐配置,但某些情况下,即使没有显式启用,后台也可能勉强运行(依赖旧版本的一些容错机制)。
- 很多老项目在 settings.py 里精简了 TEMPLATES 配置,只保留了自己需要的 context processor,没有把 messages 处理器写进去。
- 因为 Django 1.11 的 admin 对 messages 的强依赖校验不严格,所以这类问题没被暴露出来。
2.3.2 变化点(Django 2.0 ~ 2.2)
- 从 Django 2.0 开始,Django 官方在 admin 应用的系统检查 中加入了严格校验:如果你启用了 django.contrib.admin 但是 在 TEMPLATES 的 context_processors 里没有 django.contrib.messages.context_processors.messages 就会在 runserver 或 check 阶段直接报错 (admin.E404)。
- 这样做是为了保证 admin 的消息提示(保存成功、删除成功等)不会因为配置缺失而失效。
2.3.3 后果
- 升级到 Django 2.2 后,项目启动时就会抛出这个错误:(admin.E404) 'django.contrib.messages.context_processors.messages' must be enabled...
- 解决方法是:在 INSTALLED_APPS 里加入 django.contrib.messages在 MIDDLEWARE 里加入 django.contrib.messages.middleware.MessageMiddleware在 TEMPLATES[0]['OPTIONS']['context_processors'] 中加入 "django.contrib.messages.context_processors.messages"
2.4 自定义错误页面的函数签名变动
django1.11中,这些函数的签名都是一个参数:request, django2.x中“部分”函数签名改为 func(request, exception)。
| 处理器 | 正确签名 | 说明 |
|---|---|---|
handler400 | def error_400(request, exception): | 400、403、404 都必须接受两个参数request, exception。 |
handler403 | def error_403(request, exception): | 用于权限拒绝(Forbidden)。 |
handler404 | def error_404(request, exception): | 页面找不到。 |
handler500 | def error_500(request): | 只有 request,不能加 exception(Django 不能传它)。 |
2.5 模型与 app 相关
app 注册 / app_label
- 确保每个模型都属于一个已经在 INSTALLED_APPS 中注册的 app。
- 避免像你之前遇到的 RuntimeError: model ... doesn't declare an explicit app_label and isn't an application in INSTALLED_APPS:推荐做法:模型定义在 app 包(如 cmdb_collector/models.py)且 app 被加在 INSTALLED_APPS 里(最好用 AppConfig)。备选/边缘:若模型放在非标准位置,显式在 Meta 里写:class Meta: app_label = "cmdb_collector"
2.6 国际化函数变更
国际化函数
- 把 django.utils.translation.ugettext_lazy 替换为 gettext_lazy(ugettext_* 系列在未来已弃用):from django.utils.translation import ugettext_lazy # 变更为:from django.utils.translation import gettext_lazy as _
2.7 mysqlclient/PyMySQL升级
推荐使用mysqlclient==1.4.6。去掉PyMySQL
2.8 middleware的定义方式发生改变
Django 1.11 到 2.2 Middleware 主要变化
| 方面 | Django 1.11 | Django 2.2 | 说明/原因 |
|---|---|---|---|
| 中间件配置项 | MIDDLEWARE_CLASSES | MIDDLEWARE | Django 2.0 开始弃用 MIDDLEWARE_CLASSES,统一用 MIDDLEWARE |
| 中间件写法 | 支持“旧式中间件”,定义钩子方法如 process_request、process_view、process_response 等 | 只支持“新式中间件”,必须实现 __init__(get_response) 和 __call__(request) | 旧式钩子接口被废弃,鼓励用更简洁的新式中间件写法 |
| 旧式中间件兼容层 | 支持旧式写法 | 通过 MiddlewareMixin 保持兼容 | 使用 MiddlewareMixin 让旧式钩子在新系统下依然能用 |
| 中间件实例化方式 | 无需传入参数,Django直接调用钩子方法 | 中间件实例化时会传入 get_response,作为调用下一个中间件的入口 | 新机制明确链式调用,更灵活高效 |
| 中间件的调用流程 | Django 调用对应钩子方法(如 process_request) | Django 调用 __call__ 方法,通过 get_response 调用链式中间件 | 统一调用接口,更容易组合和调试 |
2.8.1 Django 1.11 旧式中间件写法
# settings.pyMIDDLEWARE_CLASSES = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'myapp.middleware.LoginMiddleware',]# myapp/middleware.pyclass LoginMiddleware(object): def process_request(self, request): print("请求开始") def process_view(self, request, view_func, view_args, view_kwargs): print("请求调用视图之前") def process_response(self, request, response): print("响应返回之前") return response- Django 会自动调用这些钩子方法。
- 中间件是老式,继承自 object,没有 __init__ 和 __call__。
2.8.2 Django 2.2 新式中间件写法
# settings.pyMIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'myapp.middleware.LoginMiddleware',]# myapp/middleware.pyclass LoginMiddleware: def __init__(self, get_response): self.get_response = get_response # 获取调用链的下一个中间件或视图 def __call__(self, request): # 请求处理代码(类似 process_request) print("请求开始") response = self.get_response(request) # 调用下一个中间件或视图 # 响应处理代码(类似 process_response) print("响应返回之前") return response- __init__ 接收 get_response,保存调用链下一环。
- __call__ 中处理请求和响应逻辑。
- 旧的钩子 process_request、process_view、process_response 都没有了。
2.8.3 兼容旧式中间件写法:使用 MiddlewareMixin
如果你想用旧式钩子在 Django 2.2:
from django.utils.deprecation import MiddlewareMixinclass LoginMiddleware(MiddlewareMixin): def process_request(self, request): print("请求开始") def process_view(self, request, view_func, view_args, view_kwargs): print("请求调用视图之前") def process_response(self, request, response): print("响应返回之前") return response- MiddlewareMixin 帮你实现了 __init__ 和 __call__,让旧钩子在新中间件机制里生效。
2.8.4 适配总结:
- 把 MIDDLEWARE_CLASSES 改成 MIDDLEWARE。
- 自定义中间件改写为新式写法(推荐)。
- 如果暂时不能改写,继承 MiddlewareMixin 也能兼容旧式写法。
三、Django2.2升级到Django3.2
🚫 移除的功能(常见)
| 项目 | Django 2.x 行为 | Django 3.x 变化 |
|---|---|---|
url() | 使用 from django.conf.urls import url | 移除,改为 path()/re_path() |
django.utils.six | 存在 | 移除,使用原生 Python 替代 |
ugettext, ugettext_lazy | 仍可用 | 改为 gettext, gettext_lazy |
on_delete=None | 默认可省略 | 必须显式指定 on_delete |
| 中间件顺序错误 | 有时警告 | 报错更严格 |
MIDDLEWARE_CLASSES | 仍被允许 | 已移除,统一使用 MIDDLEWARE |
3.1 完全移除了django.utils.six
from django.utils.six.moves.urllib.parse import urlparse 需要修改为:from urllib.parse import urlparse
3.2 available_attrs 从 Django 3.1 开始被移除了
不要再使用 available_attrs,改用标准库 functools.wraps
如果你的旧代码是这样的:
from django.utils.decorators import available_attrsfrom functools import wrapsdef my_decorator(view_func): @wraps(view_func, assigned=available_attrs(view_func)) def _wrapped_view(request, *args, **kwargs): # do something return view_func(request, *args, **kwargs) return _wrapped_view请修改为:
from functools import wrapsdef my_decorator(view_func): @wraps(view_func) def _wrapped_view(request, *args, **kwargs): # do something return view_func(request, *args, **kwargs) return _wrapped_view3.3 从 Django 3.2 开始,如果你定义的模型中没有显式指定主键字段(id = models.AutoField(...)),django默认会使用BigAutoField (64-bit 自增主键)
Django 会发出如下提示:
You didn't explicitly set a default `auto_field`, so Django is assuming `BigAutoField`.3.3.1 解决方法(推荐方式)
方法 1:在 settings.py 中设置全局默认主键类型(推荐)
在你的项目的 settings.py 中添加:
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'这样,所有未指定主键字段的模型都将默认使用 BigAutoField,并且不会再显示提示信息。
方法 2:在模型中手动指定主键类型(可选)
from django.db import modelsclass MyModel(models.Model): id = models.AutoField(primary_key=True) # 或 BigAutoField name = models.CharField(max_length=100)如果你只想对某些模型使用 AutoField 或 BigAutoField,可以在模型中手动指定。
3.4 django-cors-headers问题
3.4.1 版本问题
推荐:使用3.10.x - 4.2.x
3.4.2 CorsMiddleware中间件问题
CorsMiddleware 必须放在中间件最前面或靠前的位置,否则不生效。
3.4.3 cors配置
CORS_ORIGIN_WHITELIST 需要变更为 CORS_ALLOW_ALL_ORIGINS。
3.5 whitenoise中间变更
版本要使用:5.2 ~ 5.3.0。
四、python3.6升级到python3.11.10
4.1 代码操作指引
4.1.1. 获取推荐版本
我们将所有依赖库主要分为两大类:“语言/框架”和“常规库”。其中,“语言/框架”指 Python、Django 这类组件,它们处于项目的核心位置,对项目功能影响显著,实施升级所需要的工作量也通常比较大。“常规库”指其他的依赖库,如 requests 等,升级所需的工作量较小。
- 语言和框架(2024.11) Python 语言:
| 名称 | 推荐版本 | 说明 |
|---|---|---|
| python | 3.11.10 | |
| django | 4.2.16 | |
| celery | 5.4.0 | |
| djangorestframework | 3.15.2 |
- 常规库 :
| 名称 | 推荐版本 | 说明 |
|---|---|---|
| arrow | 1.3.0 | |
| asyncssh | 2.14.2 | |
| azure-storage-blob | 12.9.0 | |
| babel | 2.14.0 | |
| bleach | 6.1.0 | |
| certifi | 2024.2.2 | |
| cryptography | 42.0.5 | |
| django-celery-results | 2.5.1 | |
| elasticsearch | 8.12.1 | |
| eventlet | 0.35.2 | |
| future | 1.0.0 | |
| gevent | 24.2.1 | |
| gitpython | 3.1.42 | |
| grpcio | 1.62.1 | |
| httplib2 | 0.22.0 | |
| ipaddress | 1.0.23 | |
| ipython | 8.22.2 | |
| jinja2 | 3.1.3 | |
| mako | 1.3.2 | |
| mistune | 3.0.2 | |
| mysqlclient | 2.2.7 | |
| Bumpy | 2.3.2 | |
| opentelemetry-instrumentation | 0.44b0 | |
| pandas | 2.3.1 | |
| paramiko | 3.4.0 | |
| pillow | 10.2.0 | |
| protobuf | 5.26.0 | |
| psutil | 5.9.8 | |
| pycryptodome | 3.20.0 | |
| pydantic | 2.6.4 | |
| pygments | 2.17.2 | |
| pyjwt | 2.8.0 | |
| pyodbc | 4.0.39 | |
| pyopenssl | 24.1.0 | |
| python-dateutil | 2.8.2 | |
| PyYAML | 6.0.1 | |
| redis | 5.0.3 | |
| requests | 2.31.0 | |
| rsa | 4.9 | |
| selenium | 4.18.1 | |
| sentry-sdk | 1.43.0 | |
| sqlalchemy | 2.0.28 | |
| sqlparse | 0.4.4 | |
| suds-jurko | 0.6 | |
| ujson | 5.9.0 | |
| urllib3 | 1.26.20 / 2.2.1 | |
| uwsgi | 2.0.24 | |
| websockets | 12.0 | |
| werkzeug | 3.0.1 | |
| wheel | 0.43.0 | |
- 禁用库 Python 语言
| 名称 | 禁用版本 | 说明 | 修复方案 |
|---|---|---|---|
| impacket | 所有版本 | 1) CVE-2021-31800 2)近期恶意程序监测发现统一运维平台(蓝鲸生产环境)引入了Impacket网络协议工具包,用于windows agent的远程安装,由于Impacket包中含有黑客工具secretsdump(可导出本机登录用户的密码或密码HASH)而被防病毒设备检测并告警,系统运维中心在收到我处安全事件处置单时,已第一时间删除secretsdump工具。 | 去掉,或者直接考虑需要用到的函数到项目代码里 |
| pycrypto | 所有版本 | 1) 客户进行开源软件扫描,扫描出来pycrypto ,pycrypto模块是黑名单模块,需要进行整改。 2) PyCrypto is unmaintained, obsolete, and contains security vulnerabilities. | 修复方式:替换 pycrypto 依赖 为 pyCryptodome(建议最新的3.20.0版本) |
注:当前,表格中各依赖库的推荐版本来自文档苏研院平台漏洞扫描组件升级分析。由于该分析报告生成于 2024 年上半年,因此,其中部分版本号可能已过时。针对已过时的条目,等待具体实施升级时,再做针对性调整。
版本更新策略:
- 如果 patch 版本号有更新,总是优先使用新版本,比如总是用 1.2.4 替代 1.2.3。
- 12.2 updated:依赖包如果不在表格里,先确认包是否在项目中是冗余的,如果冗余直接去掉,如果用到了默认先尝试升到最新。
4.2 celery必须升级到celery5.x版本。
- 在 Celery 3.x 和 4.x 版本中,@periodic_task 是常用的周期任务装饰器,用于直接在任务函数上声明执行频率。
- 从 Celery 5.x 开始,官方移除了 @periodic_task 装饰器,不再支持这种写法。
- 现在推荐通过配置 beat_schedule 来管理周期任务。
Celery 5.x 定义周期任务的推荐方式:
- 定义普通任务from celery import Celeryapp = Celery('proj')@app.taskdef my_periodic_task(): print("This runs periodically")
- 通过 beat_schedule 配置周期任务from celery.schedules import crontabapp.conf.beat_schedule = { 'run-every-5-minutes': { 'task': 'proj.my_periodic_task', # 任务路径 'schedule': crontab(minute='*/5'), # 相当于3.x中的periodic_task的runevery参数(如:contab示例) # 'args': (), # 如果需要传参数 },}
五、Django3.2升级到Django4.2
5.1 数据库改动
如何兼容 MySQL 5.7
最重要的改动是 4.2 版本不再支持 MySQL 5.7。
如果仍旧存在项目无法升级到 MySQL 8,那么可以考虑引入一个 Patch 来临时解决问题:
from django.db.backends.mysql.features import DatabaseFeaturesfrom django.utils.functional import cached_propertyclass PatchFeatures: @cached_property def minimum_database_version(self): if self.connection.mysql_is_mariadb: return (10, 4) else: return (5, 7)目前 Django 仅是对 5.7 做了软性的不兼容改动,在没有使用 8.0 特异的功能时,对 5.7 版本的使用无影响
DatabaseFeatures.minimum_database_version = PatchFeatures.minimum_database_version5.2 安全相关配置改动
- CSRF_TRUSTED_ORIGINS
CSRF 现在会参考 Origin Header,如果使用了 CSRF_COOKIE_NAME,就需要在 settings 中额外配置 CSRF_TRUSTED_ORIGINS。例子:
CSRF_TRUSTED_ORIGINS = ["https://*.example.com", "http://*.example.com"]- SECURE_PROXY_SSL_HEADER
用于控制 CSRF 模块如何判断请求是否安全,如果在内网调用可以考虑配置修改这个配置,具体的情况可以参考 官方文档。
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")- SECURE_CROSS_ORIGIN_OPENER_POLICY
SecurityMiddleware 现在添加了值为“same-origin”的 Cross-Origin Opener Policy 标头,以防止跨源弹出窗口共享相同的浏览上下文。对我们的登出功能有影响,需要手动修改默认值:
SECURE_CROSS_ORIGIN_OPENER_POLICY = "unsafe-none"5.4 request.is_ajax()
Django 3.0:仍保留is_ajax(),但未标记为弃用。 Django 3.1:正式移除该方法,改为推荐手动检查请求头X-Requested-With。 替换方法:
def is_ajax(request): return request.headers.get('X-Requested-With') == 'XMLHttpRequest'# 使用is_ajax(request)5.5 其他函数变动
| 新版本去除内容 | 替换 |
|---|---|
| from django.conf.urls | from django.urls import include, re_path, path |
| django.utils.http.urlquote() | urllib.parse.quote() |
| django.utils.http.urlquote_plus() | urllib.parse.quote_plus() |
| django.utils.http.urlunquote() | urllib.parse.unquote() |
| django.utils.http.urlunquote_plus() | urllib.parse.unquote_plus() |
| django.utils.encoding.force_text() | django.utils.encoding.force_str() |
| django.utils.encoding.smart_text() | django.utils.encoding.smart_str() |
| django.utils.translation.ugettext() | django.utils.translation.gettext() |
| django.utils.translation.ugettext_lazy() | django.utils.translation.gettext_lazy() |
| django.utils.translation.ugettext_noop() | django.utils.translation.gettext_noop() |
| django.utils.translation.ungettext() | django.utils.translation.ngettext() |
| django.utils.translation.ungettext_lazy() | django.utils.translation.ngettext_lazy() |
| django.utils.text.unescape_entities() | html.unescape() |
| django.utils.http.is_safe_url() | django.utils.http.url_has_allowed_host_and_scheme() |
| django.contrib.postgres.forms.JSONField | django.forms.JSONField |
| django.contrib.postgres.fields.jsonb.JSONField | django.db.models.JSONField |
| django.contrib.postgres.fields.jsonb.KeyTransform | django.db.models.fields.json.KeyTransform |
| django.contrib.postgres.fields.jsonb.KeyTextTransform | django.db.models.fields.json.KeyTextTransform |
| django-admin.py | django-admin |
| django.dispatch.Signal(providing_arg=[]) | django.dispatch.Signal() |
| default_app_config not required any more | |
| NullBooleanField | BooleanField(null=True) |
| {% ifequal %} {% ifnotequal %} template tags | {% if %} {% endif %} |
| EmailValidator(whitelist, domain_whitelist) | EmailValidator (allowlist, domain_allowlist) |
| settings.PASSWORD_RESET_TIMEOUT_DAYS | settings.PASSWORD_RESET_TIMEOUT |
| sha1 | sha256 |
如果你的项目里大量使用了需要修改的内容,可以考虑使用 django-upgrade 工具自动进行转换(需要先升级到 Py3.10)。
5.6 新增的内容
5.6.1“更安全”的密码加密算法
用更多的内存防止黑客并行爆破的“更安全”的加密算法 scrypt。(注:用户管理项目可以参考)
5.6.2 官方支持的 RedisCache Backend
相较于社区方案 django-redis,官方提供的功能更为基础,除非是新项目,否则更建议直接升级到原有的 django-redis 库的支持 Django 4 版本。
5.6.3 异步 View 类 和 ORM 接口
算是补全了更多层次的 Django 操作,可以更容易地作异步数据查询:
user = await Creators.object.afirst()考虑到大部份项目并没有使用上 async 特性,仅需要保持关注即可。
评论(0)