- CMS
- 后端
【免费下载链接】django-cms
The easy-to-use and developer-friendly enterprise CMS powered by Django
本文以 django-cms 官方升级文档 docs/upgrade/3.4.4.rst 为主体,系统梳理 3.4.4 版本(2017-06-15 发布,见 CHANGELOG.rst)引入的向后不兼容变更:Page模型内部方法reset_to_live被移除并替换为revert_to_live,以及两个内部占位符工具函数cms.utils.placeholder._scan_placeholders与cms.utils.placeholder.get_placeholders的返回值类型发生改变。读完本文,你将明确这些变更的影响范围、旧代码的迁移方法,以及新版返回值在模板扫描链路中的真实用法与底层实现原理。
一、3.4.4 版本背景:一次以性能修复为核心的维护版本
3.4.4 是 django-cms 3.4 系列的一个维护性版本。根据仓库 CHANGELOG.rst 的完整记录,该版本修复了发布对话框取消逻辑、单语言站点登录后重定向、占位符缓存清理、数据库缓存键竞态条件、嵌套页面inherit标志性能等一系列问题,并调整了若干内部 API。其中与升级直接相关、影响第三方代码兼容性的关键变更,集中在以下两类:
Page模型方法的移除与替换:删除内部方法reset_to_live,改用revert_to_live。CHANGELOG 中的对应条目为"Removed the internalreset_to_publicpage method in favour of therevert_to_livemethod."(注意历史文档与 CHANGELOG 对该方法旧名称的记录略有出入,但替换方向一致)。- 占位符工具函数返回值类型变更:这是由修复
{% placeholder %}标签使用inherit标志时嵌套页面性能问题(CHANGELOG 中"Fixed a performance issue with nested pages when using theinheritflag on the{% placeholder %}tag")所引发的连带 API 调整。
升级文档本身将 Bug Fixes、Improvements 与 Deprecations 章节留空,把全部篇幅用于 "Backward incompatible changes"(向后不兼容变更),可见这两处 API 变化是 3.4.4 升级时需要重点处理的兼容性风险点。
二、破坏性变更一:Page.reset_to_live被移除
变更内容
升级文档明确指出,以下方法已从Page模型中移除:
reset_to_live- 这是一个内部方法(internal method),已被
revert_to_live取代。
- 这是一个内部方法(internal method),已被
影响范围与迁移建议
凡是第三方扩展、自定义Page子类、管理命令或测试代码中直接调用了page.reset_to_live()的地方,升级到 3.4.4 后都会触发AttributeError。迁移时只需将调用点替换为:
# 旧代码(3.4.4 起不再可用) page.reset_to_live() # 新代码(3.4.4 起使用) page.revert_to_live()需要说明的是,该方法属于页面发布/回滚流程的内部实现细节,正常情况下不应出现在业务代码中。如果你的项目中搜索到reset_to_live或reset_to_public的调用,说明你在使用未公开的内部 API,应尽快切换到revert_to_live并同步关注后续版本对发布模型的进一步演进(django-cms 后续将页面内容模型重构为PageContent,发布逻辑也随之迁移,参见 cms/models/pagemodel.py 与 cms/models/contentmodels.py)。
三、破坏性变更二:_scan_placeholders返回值从 slot 名变为标签节点
变更内容
由于占位符继承存在性能问题,django-cms 团队调整了内部占位符工具函数cms.utils.placeholder._scan_placeholders的返回值:
- 旧行为:返回占位符 slot 名称的列表(list of placeholder slot names)。
- 新行为:返回
Placeholder标签实例的列表(list ofPlaceholdertag instances)。
要从新的返回值中取出 slot 名称,需要对每个Placeholder标签实例调用get_name()方法。
源码级原理:_scan_placeholders到底在做什么
当前仓库中该函数的实现位于 cms/utils/placeholder.py。它的职责是递归遍历 Django 模板编译后的节点树(nodelist),把其中所有占位符声明收集起来。函数签名如下:
def _scan_placeholders(nodelist, node_class=None, current_block=None, ignore_blocks=None):其遍历逻辑覆盖了模板继承体系中的各类复杂场景,这也是它需要返回"节点实例"而非"字符串"的根本原因——节点实例携带了比 slot 名更丰富的结构信息:
- 直接命中:节点是
Placeholder标签实例时直接收集; {% include %}引入的模板:递归展开被引入模板的节点树(IncludeNode),对node.template无法解析的变量型 include 则跳过,因为扫描阶段无法展开变量;{% extends %}继承的父模板:通过_get_placeholder_nodes_from_extend处理父模板中的占位符(ExtendsNode分支);{{ block.super }}:在 block 内遇到block.super时递归扫描父块节点;- 嵌套 block 与节点属性:对
child_nodelists属性以及节点上任意NodeList类型的属性递归扫描,BlockNode会被记录为current_block以正确处理block.super。
正是因为返回的是完整的标签节点对象,调用方才能在不重新解析模板的情况下,同时拿到 slot 名称、inherit标志等结构化信息。当时文档建议的get_name()方法对应 3.4.4 时代Placeholder标签的取值接口;在演进后的当前代码库中,Placeholder标签类(见 cms/templatetags/cms_tags.py)提供了get_declaration()方法,返回包含slot与inherit字段的结构化声明,语义上延续了这次改造的方向。
迁移示例
如果你在插件、管理命令或测试中直接使用了_scan_placeholders,迁移方式如下:
from django.template.loader import get_template from cms.utils.placeholder import _scan_placeholders from cms.utils.helpers import _get_nodelist # 获取模板根节点列表 # 旧代码(3.4.4 之前):nodes 是 slot 名称字符串列表 # slot_names = _scan_placeholders(_get_nodelist(get_template("my_template.html"))) # 新代码(3.4.4 起):nodes 是 Placeholder 标签实例列表 nodes = _scan_placeholders(_get_nodelist(get_template("my_template.html"))) slot_names = [node.get_name() for node in nodes]四、破坏性变更三:get_placeholders返回值从 slot 名变为DeclaredPlaceholder
变更内容
与_scan_placeholders配套,cms.utils.placeholder.get_placeholders的返回值同样发生了类型变化:
- 旧行为:返回占位符 slot 名称的列表。
- 新行为:返回
DeclaredPlaceholder实例的列表(list ofDeclaredPlaceholderinstances)。
要从新返回值中取出 slot 名称,需要访问DeclaredPlaceholder实例的slot属性。
源码级原理:get_placeholders的完整实现
当前仓库中该函数的实现位于 cms/utils/placeholder.py,其工作流程清晰展示了新旧两版返回值的衔接关系:
def get_placeholders(template: str) -> list["DeclaredPlaceholder"]: compiled_template = get_template(template) placeholders = [] nodes = _scan_placeholders(_get_nodelist(compiled_template)) clean_placeholders = [] for node in nodes: placeholder = node.get_declaration() slot = placeholder.slot if slot in clean_placeholders: warnings.warn( f'Duplicate {{% placeholder "{slot}" %}} ' f"in template {template}.", DuplicatePlaceholderWarning, ) else: validate_placeholder_name(slot) placeholders.append(placeholder) clean_placeholders.append(slot) return placeholders可以看到,get_placeholders是_scan_placeholders的上层封装,它依次完成以下步骤:
- 编译模板:通过
get_template(template)得到编译后的模板对象; - 扫描节点:调用
_scan_placeholders得到Placeholder标签实例列表; - 提取声明:对每个标签节点调用
get_declaration(),转换为DeclaredPlaceholder; - 去重与告警:同一模板中出现重复 slot 时发出
DuplicatePlaceholderWarning,避免重复渲染; - 名称校验:对每个 slot 调用
validate_placeholder_name,非 ASCII 字符等非法命名会在 cms/utils/placeholder.py 抛出ImproperlyConfigured。
DeclaredPlaceholder的定义在 cms/templatetags/cms_tags.py:
DeclaredPlaceholder = namedtuple("DeclaredPlaceholder", ["slot", "inherit"])这是一个两字段的namedtuple:slot是占位符标识名(即模板中{% placeholder "name" %}的name),inherit是布尔值,表示该占位符是否声明了inherit标志。Placeholder标签的get_declaration()(cms/templatetags/cms_tags.py)在解析时从标签参数中提取 slot,并检查extra_bits中是否包含inherit关键字来填充这两个字段。
此外,get_placeholders在生产环境(settings.DEBUG is False)下会被缓存装饰器包裹(见 cms/utils/placeholder.py),以提升模板扫描性能;开发模式下则关闭缓存,保证模板改动即时生效。
迁移示例
from cms.utils.placeholder import get_placeholders # 旧代码(3.4.4 之前):返回 slot 名称字符串列表 # slot_names = get_placeholders("my_template.html") # 新代码(3.4.4 起):返回 DeclaredPlaceholder 实例列表 declared = get_placeholders("my_template.html") slot_names = [p.slot for p in declared] inherited = [p.slot for p in declared if p.inherit] # 顺便可以读取 inherit 标志五、升级自查清单
在将项目升级到 3.4.4 及以上版本时,建议按以下清单逐项排查:
- 全局搜索
reset_to_live/reset_to_public:在项目代码、自定义Page模型、数据迁移与测试用例中搜索这两个标识符,凡是命中处均需改为revert_to_live。 - 全局搜索
_scan_placeholders:确认调用方对返回值的处理逻辑。若此前假设返回值是字符串列表并直接拼接/比较,需改为遍历节点并调用get_name()(或当前版本的get_declaration())取值。 - 全局搜索
get_placeholders:确认调用方通过slot属性取值,而非直接使用字符串返回值。 - 关注
inherit占位符的性能修复:本次变更的诱因是嵌套页面inherit性能问题,升级后应回归验证使用了{% placeholder "x" inherit %}语法的页面渲染与内容继承行为正常。
结语
django-cms 3.4.4 的向后不兼容变更集中在两个内部 API 上:Page模型的reset_to_live移除与占位符工具函数返回值结构化。前者是简单的重命名替换,后者则是将"占位符 slot 字符串列表"升级为"携带 slot 与 inherit 信息的结构化对象",为后续占位符继承逻辑的性能优化与功能演进奠定了基础。理解_scan_placeholders→get_declaration()→DeclaredPlaceholder这条模板扫描链路(对应源码 cms/utils/placeholder.py 与 cms/templatetags/cms_tags.py),不仅能顺利完成升级,也有助于在自定义模板工具或插件开发中正确使用占位符解析能力。若你正在维护更早版本的第三方插件,强烈建议在升级文档中补充这两条变更的迁移说明,避免下游用户升级后遭遇隐性故障。
- CMS
- 后端
【免费下载链接】django-cms
The easy-to-use and developer-friendly enterprise CMS powered by Django
相关推荐
wezterm.time 模块完全指南:在 Lua 配置中编程处理时间与动态主题
wezterm.time 模块完全指南:在 Lua 配置中编程处理时间与动态主题 wezterm.time 是 wezterm 提供的 Lua 时间模块,用于在
CMS后端django CMS 4.1.0 升级指南:新特性、破坏性变更与迁移实战
django CMS 4.1.0 升级指南:新特性、破坏性变更与迁移实战 导读 本文以官方 4.1.0 release notes https://link.g
CMS后端django CMS 5.0.5 升级指南:从 4.1 迁移、破坏性变更与关键 Bug 修复全解析
django CMS 5.0.5 升级指南:从 4.1 迁移、破坏性变更与关键 Bug 修复全解析 django CMS 5.0.5 是 django CMS
CMS后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考