FPS --
1773 字
9 分钟
XML 基础与 Odoo XML 写法

XML 基础与 Odoo XML 写法#

XML(可扩展标记语言)用标签描述有层次的数据。它规定文档如何组织;<book>、<field> 等标签的具体含义由使用 XML 的程序决定。Odoo 就用 XML 声明数据、视图、菜单和模板。

基础语法#

一个 XML 文档必须有且只有一个根元素。标签区分大小写,开始和结束标签要同名、正确嵌套;没有内容的元素可以写成自闭合形式。

<?xml version="1.0" encoding="UTF-8"?>
<library>
<book id="b1">
<title>XML 入门</title>
<author>小明</author>
</book>
<book id="b2" />
</library>

XML 声明通常放在文件开头,常见版本是 1.0,声明本身可以省略。<library> 是根元素。<book id="b2" /> 等价于 <book id="b2"></book>。<a><b></a></b> 则不合法,因为标签交叉了。

属性与注释#

属性写在开始标签中;属性值必须用成对的单引号或双引号包裹,同一元素不能重复使用同一个属性名。名称不能以数字开头。实际编写时可选用字母开头,再使用字母、数字、下划线或连字符,避免与工具的命名限制冲突。

<book id="b1" language="zh-CN" available="true" />
<!-- 这是注释,不能包含连续的两个连字符 -->

XML 没有内置的布尔属性类型:这里的 "true" 首先是文本,是否当作布尔值由读取它的程序决定。

特殊字符与 CDATA#

文本中的 < 和 & 必须转义;属性值中还要转义与外层引号相同的引号。五个预定义实体如下:

写法表示的字符
&lt;<
&gt;>
&amp;&
&apos;'
&quot;"

例如,Tom & Jerry 应写成 Tom &amp; Jerry;<rule test="a &lt; b"> 中的比较符也需要转义。CDATA 可以在元素文本中保留大段未经转义的字符,但不能用于属性值,也不能在内部直接出现 ]]>:

<example><![CDATA[if a < b and c > d: print("ok")]]></example>

CDATA 不会让其中的内容变成 XML 子元素;解析器读到的是一段文本。

解析、遍历与添加节点#

Python 标准库的 xml.etree.ElementTree 足够演示常见操作。假设 books.xml 保存了前面的 <library> 示例:

import xml.etree.ElementTree as ET
tree = ET.parse("books.xml")
root = tree.getroot()
for book in root.findall("book"):
print(book.get("id"), book.findtext("title"))
new_book = ET.SubElement(root, "book", {"id": "b3"})
ET.SubElement(new_book, "title").text = "新书"
ET.SubElement(new_book, "author").text = "小李"
tree.write("books-updated.xml", encoding="utf-8", xml_declaration=True)

parse() 读取文件,getroot() 取得根元素,findall() 查找子节点,SubElement() 添加节点。示例中的第二本书没有 <title>,因此它的 findtext("title") 会返回 None,获取元素的属性值用get(),findtext()查找元素并返回其文本

XPath 定位节点#

XPath 用路径和条件定位 XML 元素:

表达式含义
/library/book根元素下的直接 book 子元素
//book任意层级的 book 元素
//book[@id='b1']id 为 b1 的 book 元素
//book/title所有 book 下的 title 元素
"book"当前节点的直接子元素 book
"book/title"当前节点的直接子元素 book 的直接子元素 title
".//book"当前节点下任意深度的 book
".//book/title"当前节点下任意深度的 book 的直接子元素 title
"."当前节点本身

Python 的 ElementTree 只支持 XPath 的一个子集。从当前元素查询时通常写相对路径,例如 root.findall("./book[@id='b1']")。Odoo 的视图继承也使用 XPath 表达式定位已有视图中的节点。

Odoo 中的 XML#

以下示例以 Odoo 19 的模块写法为准。Odoo XML 文件通常以 <odoo> 为根元素,按文件中的顺序执行声明;引用的记录要先定义,或者来自已在模块依赖中声明的模块。XML 文件还必须列入模块的 __manifest__.py,安装或升级模块时才会加载。

__manifest__.py
{
"name": "Library Demo",
"depends": ["base"],
"data": [
"views/library_book_views.xml",
"data/library_book_data.xml",
],
}

文件顺序有意义:后一个文件可以引用前一个文件建立的 XML ID。下面的 library.book 是示例模型,需先在模块的 Python 代码中定义 name、author 和 note 字段;仅写 XML 不会自动创建这个业务模型。

创建数据记录#

record 的 model 指向要写入的 Odoo 模型,id 是该记录的 XML ID。field 用于设置字段;纯文本、ref 与 eval 分别适合普通值、已有 XML ID 和需要求值的表达式。

<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="book_xml_intro" model="library.book">
<field name="name">XML 入门</field>
<field name="author">小明</field>
</record>
<record id="book_odoo_intro" model="library.book">
<field name="name">Odoo 入门</field>
<field name="author">小李</field>
</record>
</odoo>

同一模块里可直接用 book_xml_intro 引用它;跨模块引用时写完整的 模块名.book_xml_intro。例如 <field name="company_id" ref="base.main_company"/> 会把一个 Many2one 字段指向已有公司。布尔值等类型可以用 eval,例如 <field name="active" eval="True"/>。不要把 ref 或 eval 当成普通文本写进标签体内。

定义列表、表单、动作和菜单#

视图本身也是 ir.ui.view 记录;界面结构写在 arch 字段内。动作把模型与视图类型关联起来,菜单再指向动作。以下内容可放在 views/library_book_views.xml:

<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_library_book_list" model="ir.ui.view">
<field name="name">library.book.list</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<list string="图书">
<field name="name" />
<field name="author" />
</list>
</field>
</record>
<record id="view_library_book_form" model="ir.ui.view">
<field name="name">library.book.form</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<form string="图书">
<sheet>
<group>
<field name="name" />
<field name="author" />
</group>
</sheet>
</form>
</field>
</record>
<record id="action_library_books" model="ir.actions.act_window">
<field name="name">图书</field>
<field name="res_model">library.book</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_library_root" name="图书馆" sequence="10" />
<menuitem id="menu_library_books" name="图书" parent="menu_library_root"
action="action_library_books" sequence="10" />
</odoo>

view_mode 里的类型用逗号连接,不加空格。list 是此处 Odoo 19 的列表视图名称;旧版本资料可能写 tree。菜单能否实际显示和访问还受模型访问权限影响,通常还需在 security/ir.model.access.csv 中给相应用户组配置权限。

用 XPath 继承视图#

修改已有视图时,创建一条带 inherit_id 的新 ir.ui.view 记录,再在 arch 中定位目标。下面的示例继承上面定义的表单视图,在 author 字段之后加入预先定义好的 note 字段:

<record id="view_library_book_form_note" model="ir.ui.view">
<field name="name">library.book.form.note</field>
<field name="model">library.book</field>
<field name="inherit_id" ref="view_library_book_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='author']" position="after">
<field name="note" />
</xpath>
</field>
</record>

这段记录应放在原视图记录之后、</odoo> 之前。跨模块继承时,inherit_id 一般写成 原模块名.视图 XML ID,并在 manifest 的 depends 中声明该模块。常见 position 有 before、after、inside、replace 和 attributes;定位表达式必须能匹配已有视图节点。

只在安装时加载的数据#

不希望模块升级时反复覆盖的初始数据,可以放在 <data noupdate="1"> 中;普通视图配置一般不应随意加这个标记,否则升级后修改可能无法按预期应用。

<odoo>
<data noupdate="1">
<record id="book_sample" model="library.book">
<field name="name">初始样书</field>
</record>
</data>
</odoo>

QWeb 模板也是 XML#

Odoo 的 QWeb 模板在 XML 元素上使用 t-* 指令控制输出。下面只演示模板语法;实际放入报告或网页时,还要按相应场景声明模板并传入 books 数据。

<t t-name="library_demo.book_list">
<ul>
<li t-foreach="books" t-as="book">
<t t-out="book.name" />
</li>
</ul>
</t>

t-foreach 遍历集合,t-as 命名当前项,t-out 输出内容并进行 HTML 转义。QWeb 指令的具体表达式运行环境取决于模板运行在服务端还是浏览器端,不要把两种环境的对象直接混用。

参考资料#

XML 基础与 Odoo XML 写法
https://www.pardofelis.top/posts/261f7a2c/
作者
所愿Syuan
发布于
2026-10-12
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

评论

正在加载评论...

最后更新于 ,距今已过 2 天

部分内容可能已过时