
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
文本中的 < 和 & 必须转义;属性值中还要转义与外层引号相同的引号。五个预定义实体如下:
| 写法 | 表示的字符 |
|---|---|
< | < |
> | > |
& | & |
' | ' |
" | " |
例如,Tom & Jerry 应写成 Tom & Jerry;<rule test="a < 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,安装或升级模块时才会加载。
{ "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 指令的具体表达式运行环境取决于模板运行在服务端还是浏览器端,不要把两种环境的对象直接混用。
参考资料
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
最后更新于 ,距今已过 2 天
部分内容可能已过时
评论