Odoo10 开发者文档(3)--建立一个模块·

警告
该教程需要 已安装odoo

开启/停止Odoo服务
odoo使用客户端/服务器架构,客户端是通过RPC(远程过程调用协议)访问Odoo服务的web浏览器。
业务逻辑和扩展通常在服务器端执行,尽管支持客户端功能(如,想交互式地图的新数据表示)可以添加到客户端。
为了启动服务器,在shell内简单的调用命令Odoo,如果必要添加文件的完整路径

odoo-bin

服务器按Ctrl-C两次从终端停止,或杀死相应的OS进程。


建立一个Odoo模块
服务器和客户端扩展都打包为可选择的加载在数据库中的模块。
Odoo模块既可以添加新的业务逻辑到Odoo系统,也可以修改和扩展现有的业务逻辑:一个模块可以被创建为Odoo通用会计支持添加你的国家的会计准则,而下一个模块,增加了对公车的实时可视化支持。
因此,odoo的一切都随着模块的开始结束而开始结束。

1.模块的组成
一个Odoo模块可以包含多个元素:
业务对象:
声明为Python的类,这些资源是自动持续的,通过基于配置的Odoo。
数据文件
XML或CSV文件声明元数据(视图或工作流),配置数据(模块参数化),演示数据和更多
Web controllers
处理Web浏览器的请求
静态web数据
Web界面或网站使用的图片,CSS或JS文件

2.模块结构
每个模块是模块目录内的目录。模块目录通过–addons-path 选项被指定。

提示
大多数命令行选项也可以使用配置文件来设置

odoo模块是通过它的manifest声明的。一个模块也是一个带__init__.py的Python包,包含模块中各种Python文件的导入说明。
例如,如果模块有一个mymodule.py__init__.py应该包含文件:

from . import mymodule

Odoo提供了一种机制来帮助建立一个新的模块,odoo-bin有一子命令scaffold 创建一个空的模块:

 odoo-bin scaffold <module name> <where to put it>

该命令为模块创建一个子目录,并自动为模块创建一堆标准文件。其中大部分只包含注释代码或xml。这些文件的使用将在本教程中解释。

练习
模块创建
使用命令行上创建一个空的模块Open Academy,并安装在Odoo上。
1. 调用命令odoo-bin scaffold openacademy odoo/addons
2. 将manifest文件更新到模块。
3. 不要改变其他文件。

openacademy/__manifest__.py::

# -*- coding: utf-8 -*-
{
    'name': "Open Academy",

    'summary': """Manage trainings""",

    'description': """
        Open Academy module for managing trainings:
            - training courses
            - training sessions
            - attendees registration
    """,

    'author': "My Company",
    'website': "http://www.yourcompany.com",

    # Categories can be used to filter modules in modules listing
    # Check https://github.com/odoo/odoo/blob/master/odoo/addons/base/module/module_data.xml
    # for the full list
    'category': 'Test',
    'version': '0.1',

    # any module necessary for this one to work correctly
    'depends': ['base'],

    # always loaded
    'data': [
        # 'security/ir.model.access.csv',
        'templates.xml',
    ],
    # only loaded in demonstration mode
    'demo': [
        'demo.xml',
    ],
}
openacademy/__init__.py:

# -*- coding: utf-8 -*-
from . import controllers
from . import models
openacademy/controllers.py:

# -*- coding: utf-8 -*-
from odoo import http

# class Openacademy(http.Controller):
#     @http.route('/openacademy/openacademy/', auth='public')
#     def index(self, **kw):
#         return "Hello, world"

#     @http.route('/openacademy/openacademy/objects/', auth='public')
#     def list(self, **kw):
#         return http.request.render('openacademy.listing', {
#             'root': '/openacademy/openacademy',
#             'objects': http.request.env['openacademy.openacademy'].search([]),
#         })

#     @http.route('/openacademy/openacademy/objects/<model("openacademy.openacademy"):obj>/', auth='public')
#     def object(self, obj, **kw):
#         return http.request.render('openacademy.object', {
#             'object': obj
#         })

openacademy/demo.xml:

<odoo>
    <data>
        <!--  -->
        <!--   <record id="object0" model="openacademy.openacademy"> -->
        <!--     <field name="name">Object 0</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="object1" model="openacademy.openacademy"> -->
        <!--     <field name="name">Object 1</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="object2" model="openacademy.openacademy"> -->
        <!--     <field name="name">Object 2</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="object3" model="openacademy.openacademy"> -->
        <!--     <field name="name">Object 3</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="object4" model="openacademy.openacademy"> -->
        <!--     <field name="name">Object 4</field> -->
        <!--   </record> -->
        <!--  -->
    </data>
</odoo>

openacademy/models.py:

# -*- coding: utf-8 -*-

from odoo import models, fields, api

# class openacademy(models.Model):
#     _name = 'openacademy.openacademy'

#     name = fields.Char()

openacademy/security/ir.model.access.csv:

id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0

openacademy/templates.xml:

<odoo>
    <data>
        <!-- <template id="listing"> -->
        <!--   <ul> -->
        <!--     <li t-foreach="objects" t-as="object"> -->
        <!--       <a t-attf-href="{{ root }}/objects/{{ object.id }}"> -->
        <!--         <t t-esc="object.display_name"/> -->
        <!--       </a> -->
        <!--     </li> -->
        <!--   </ul> -->
        <!-- </template> -->
        <!-- <template id="object"> -->
        <!--   <h1><t t-esc="object.display_name"/></h1> -->
        <!--   <dl> -->
        <!--     <t t-foreach="object._fields" t-as="field"> -->
        <!--       <dt><t t-esc="field"/></dt> -->
        <!--       <dd><t t-esc="object[field]"/></dd> -->
        <!--     </t> -->
        <!--   </dl> -->
        <!-- </template> -->
    </data>
</odoo>

3.对象关系映射

Odoo的一个关键组成部分是ORM层。这一层可以避免手工编写大多数SQL并提供了可扩展性和安全性的服务。
业务对象被声明为Python类扩展模型,该模型将它们集成到自动持久化系统中。
模型可以通过在它们的定义中设置一些属性进行配置。最重要的属性是_name,这是必要的,在Odoo中定义系统模型的名字。这里是一个模型的最小完整定义:

from odoo import models
class MinimalModel(models.Model):
    _name = 'test.model'

4.模型文件
字段用于定义模型可以存储和在哪里。字段被定义为模型类的属性:

from odoo import models, fields

class LessMinimalModel(models.Model):
    _name = 'test.model2'

    name = fields.Char()

(1) 共同属性
就像模型本身一样,它的字段可以通过将配置属性作为参数来配置:

name = field.Char(required=True)

一些属性在所有领域都可用,这里是最常见的:
string (unicode, default: field's name) 用户界面中字段的标签(用户可见)。
required (bool, default: False) 如果为TRUE,字段不能为空,则必须具有默认值或在创建记录时始终给予值。
help (unicode, default: '') Long-form, 为用户提供了一个帮助提示。
index (bool, default: False) 要求 Odoo创建列上的数据库索引。

(2) 简单字段
有两种类型的字段:“simple”字段,它们直接存储在模型表中的原子值,以及连接记录(同一模型或不同模型)的“relational”字段。
简单字段的例子:Boolean, Date, Char.

(3) 保留字段
Odoo在所有模型创造了一些字段。这些字段由系统管理,不应写入。如果有用或必要,它们可以被读取:
id (Id) 模型中记录的唯一标识符。
create_date (Datetime) 记录的创建日期.
create_uid (Many2one) 创建记录的用户。
write_date (Datetime) 记录的最后修改日期。
write_uid (Many2one) 最后修改记录的用户。

(4) 特殊字段
默认情况下,Odoo所有的模型也需要一个name字段,以便各种显示和搜索行为。被用于这些目的的字段能通过设置_rec_name重写。

练习
定义一个模型
在openacademy 模块里 定义一个新的数据模型Course ,course 有标题(title)和描述(description) ,必须有标题
编辑openacademy/models/models.py文件,包含到Course 类中。

openacademy/models.py:

from odoo import models, fields, api

class Course(models.Model):
    _name = 'openacademy.course'

    name = fields.Char(string="Title", required=True)
    description = fields.Text()

5.数据字段
虽然使用Python代码定制行为,但模块的值的一部分在加载时设置的数据中。

提示
一些模块的存在只为数据添加到Odoo。

模块数据通过数据文件,带有<record>元素的xml文件声明。每个元素创建或更新数据库记录。

<odoo>
    <data>
        <record model="{model name}" id="{record identifier}">
            <field name="{a field name}">{a value}</field>
        </record>
    </data>
</odoo>

· model是用于记录Odoo模型名称。
· id是一个外部标识符,它允许引用到记录(而不必知道它的内部数据库标识符)。
· <field> 元素的name是模型中字段的name(例如描述)。他们的body是字段的值。

必须在清单文件中声明要加载的数据文件。它们可以在“data”列表中(总是加载)或在“demo”列表中声明(仅在演示模式下加载)。

练习
定义演示数据 ,用演示课程创建演示数据,完善课程模式。
编辑openacademy/demo/demo.xml文件来包含一些数据

openacademy/demo.xml:

<odoo>
    <data>
        <record model="openacademy.course" id="course0">
            <field name="name">Course 0</field>
            <field name="description">Course 0's description

Can have multiple lines
            </field>
        </record>
        <record model="openacademy.course" id="course1">
            <field name="name">Course 1</field>
            <!-- no description for this one -->
        </record>
        <record model="openacademy.course" id="course2">
            <field name="name">Course 2</field>
            <field name="description">Course 2's description</field>
        </record>
    </data>
</odoo>
  1. Actions和Menus
    动作和菜单是数据库中的常规记录,通常通过数据文件声明。动作可以用三种方式触发:
    (1) 通过点击菜单项(链接到特定的动作)
    (2) 通过点击视图的按钮 (如果被连接到动作)
    (3) 作为对象的上下文操作
    因为菜单的声明有点复杂,有一个 <menuitem>快捷方式来更容易地声明 ir.ui.menu 并链接它到相关的action。
<record model="ir.actions.act_window" id="action_list_ideas">
    <field name="name">Ideas</field>
    <field name="res_model">idea.idea</field>
    <field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10"
          action="action_list_ideas"/>

危险
必须在xml文件的相应菜单前声明该操作。
数据文件是按顺序执行的,在菜单能创建前,动作的id必须存在于数据库中

-

练习
定义新的菜单入口
在OpenAcademy 菜单入口定义新的菜单入口来访问课程,用户应该能够:
· 显示所有课程列表
· 创建/修改课程
(1) 创建openacademy/views/openacademy.xml,附带一个action和触发action的菜单。
(2) 把它添加到openacademy/__manifest__.py的data列表。

openacademy/__manifest__.py

    'data': [
        # 'security/ir.model.access.csv',
        'templates.xml',
        'views/openacademy.xml',
    ],
    # only loaded in demonstration mode
    'demo': [



----------


openacademy/views/openacademy.xml:

<?xml version="1.0" encoding="UTF-8"?>
<odoo>
    <data>
        <!-- window action -->
        <!--
            The following tag is an action definition for a "window action",
            that is an action opening a view or a set of views
        -->
        <record model="ir.actions.act_window" id="course_list_action">
            <field name="name">Courses</field>
            <field name="res_model">openacademy.course</field>
            <field name="view_type">form</field>
            <field name="view_mode">tree,form</field>
            <field name="help" type="html">
                <p class="oe_view_nocontent_create">Create the first course
                </p>
            </field>
        </record>

        <!-- top level menu: no parent -->
        <menuitem id="main_openacademy_menu" name="Open Academy"/>
        <!-- A first level in the left side menu is needed
             before using action= attribute -->
        <menuitem id="openacademy_menu" name="Open Academy"
                  parent="main_openacademy_menu"/>
        <!-- the following menuitem should appear *after*
             its parent openacademy_menu and *after* its
             action course_list_action -->
        <menuitem id="courses_menu" name="Courses" parent="openacademy_menu"
                  action="course_list_action"/>
        <!-- Full id location:
             action="openacademy.course_list_action"
             It is not required when it is the same module -->
    </data>
</odoo>

基本视图
视图定义显示模型记录的方式。每种类型的视图表示一个可视化模式(一个记录列表,一个聚合的图形,…)。视图可以被要求通过他们的类型(例如类合作伙伴名单)或专门通过它们的ID的通用要求,具有正确的类型和优先级最低的视图将被使用(所以各类型优先级最低的视图是默认视图为型)。
视图继承允许改变在别处声明的视图(添加或删除内容)。

  1. 通用视图声明
    视图被声明为ir.ui.view模型的记录,视图类型由arch字段的根元素说明:
<record model="ir.ui.view" id="view_id">
    <field name="name">view.name</field>
    <field name="model">object_name</field>
    <field name="priority" eval="16"/>
    <field name="arch" type="xml">
        <!-- view content: <form>, <tree>, <graph>, ... -->
    </field>
</record>

危险
视图的内容是XML.因此,arch字段必须声明为type="xml",以便正确解析。

2.树视图
树视图,也被称为列表视图。以表单形式显示记录。
根元素是<tree>,树视图的最简单形式只列出了表中显示的所有字段(每个字段列):

<tree string="Idea list">
    <field name="name"/>
    <field name="inventor_id"/>
</tree>

3.表单视图
表单被用于创建和编辑单个记录。
根元素是<form>,它们由高层次结构元素(groups、notebooks)和交互元素(按钮和字段)组成:

<form string="Idea form">
    <group colspan="4">
        <group colspan="2" col="2">
            <separator string="General stuff" colspan="2"/>
            <field name="name"/>
            <field name="inventor_id"/>
        </group>

        <group colspan="2" col="2">
            <separator string="Dates" colspan="2"/>
            <field name="active"/>
            <field name="invent_date" readonly="1"/>
        </group>

        <notebook colspan="4">
            <page string="Description">
                <field name="description" nolabel="1"/>
            </page>
        </notebook>

        <field name="state"/>
    </group>
</form>

练习
使用XML制作form视图
给Course 对象创建form视图,数据应该显示:Course 的name和description。

openacademy/views/openacademy.xml:

<?xml version="1.0" encoding="UTF-8"?>
<odoo>
    <data>
        <record model="ir.ui.view" id="course_form_view">
            <field name="name">course.form</field>
            <field name="model">openacademy.course</field>
            <field name="arch" type="xml">
                <form string="Course Form">
                    <sheet>
                        <group>
                            <field name="name"/>
                            <field name="description"/>
                        </group>
                    </sheet>
                </form>
            </field>
        </record>

        <!-- window action -->
        <!--
            The following tag is an action definition for a "window action",

练习
Notebooks
在Course 表单视图里,将“description”字段置于选项卡之下,以便稍后添加其他选项卡,包含附加信息。
如下修改Course 表单视图:

openacademy/views/openacademy.xml

                    <sheet>
                        <group>
                            <field name="name"/>
                        </group>
                        <notebook>
                            <page string="Description">
                                <field name="description"/>
                            </page>
                            <page string="About">
                                This is an example of notebooks
                            </page>
                        </notebook>
                    </sheet>
                </form>
            </field>

表单视图也可以使用纯HTML来进行更灵活的布局:

<form string="Idea Form">
    <header>
        <button string="Confirm" type="object" name="action_confirm"
                states="draft" class="oe_highlight" />
        <button string="Mark as done" type="object" name="action_done"
                states="confirmed" class="oe_highlight"/>
        <button string="Reset to draft" type="object" name="action_draft"
                states="confirmed,done" />
        <field name="state" widget="statusbar"/>
    </header>
    <sheet>
        <div class="oe_title">
            <label for="name" class="oe_edit_only" string="Idea Name" />
            <h1><field name="name" /></h1>
        </div>
        <separator string="General" colspan="2" />
        <group colspan="2" col="2">
            <field name="description" placeholder="Idea description..." />
        </group>
    </sheet>
</form>

4.搜索视图
搜索视图自定义与列表视图关联的搜索字段(以及其他聚合视图)。它们的根元素是<search>,它们由定义哪些字段可以被搜索的字段组成:

<search>
    <field name="name"/>
    <field name="inventor_id"/>
</search>

如果模型没有搜索视图,Odoo产生一个只允许用名称字段检索的搜索视图。

练习
搜索视图
允许根据他们的标题或描述来搜索课程。

openacademy/views/openacademy.xml

            </field>
        </record>

        <record model="ir.ui.view" id="course_search_view">
            <field name="name">course.search</field>
            <field name="model">openacademy.course</field>
            <field name="arch" type="xml">
                <search>
                    <field name="name"/>
                    <field name="description"/>
                </search>
            </field>
        </record>

        <!-- window action -->
        <!--
            The following tag is an action definition for a "window action",

模型之间的关系
例如,销售订单记录与包含客户数据的客户机记录有关,它还与销售订单线记录有关。

练习
创建一个session 模型
对于Open Academy 模块,我们考虑一个session 模型:一个session 是一个给定的时间给定的观众在一个给定的时间发生的课程。
创建session 模型。session 具有名称、开始日期、持续时间和若干个座位。添加一个动作和菜单项来显示它们。通过菜单项使新模型可见。
1、在openacademy/models/models.py中创建session 类
2、在openacademy/view/openacademy.xml中给session 对象添加入口

openacademy/models.py


    name = fields.Char(string="Title", required=True)
    description = fields.Text()


class Session(models.Model):
    _name = 'openacademy.session'

    name = fields.Char(required=True)
    start_date = fields.Date()
    duration = fields.Float(digits=(6, 2), help="Duration in days")
    seats = fields.Integer(string="Number of seats")
openacademy/views/openacademy.xml

        <!-- Full id location:
             action="openacademy.course_list_action"
             It is not required when it is the same module -->

        <!-- session form view -->
        <record model="ir.ui.view" id="session_form_view">
            <field name="name">session.form</field>
            <field name="model">openacademy.session</field>
            <field name="arch" type="xml">
                <form string="Session Form">
                    <sheet>
                        <group>
                            <field name="name"/>
                            <field name="start_date"/>
                            <field name="duration"/>
                            <field name="seats"/>
                        </group>
                    </sheet>
                </form>
            </field>
        </record>

        <record model="ir.actions.act_window" id="session_list_action">
            <field name="name">Sessions</field>
            <field name="res_model">openacademy.session</field>
            <field name="view_type">form</field>
            <field name="view_mode">tree,form</field>
        </record>

        <menuitem id="session_menu" name="Sessions"
                  parent="openacademy_menu"
                  action="session_list_action"/>
    </data>
</odoo>

注意
digits=(6, 2)指定浮点数的精度:6是数字的总数,而2是小数点后的位数。请注意,它的结果在小数点前是最大值4位数字。

1.关系字段
关系字段链接记录,包括相同的模型(层次结构)或不同模型之间的链接记录。
关系字段类型:
(1) Many2one(other_model, ondelete='set null')
到另一个对象的简单链接:

print foo.other_id.name

(2) One2many(other_model, related_field)
一个虚拟的关系,一个many2one的逆。一个one2many作为容器的记录,访问的结果是一个(可能为空)的记录集:

for other in foo.other_ids:
    print other.name

危险
因为One2many 是一个虚拟的关系,在other_model里必须有一个Many2one字段,name必须是related_field 。

(3)Many2many(other_model)
双向多重关系,一方的任何记录都可以与另一方的任何记录有关。作为记录的容器,访问它也会导致一组可能空的记录:

for other in foo.other_ids:
    print other.name

练习
Many2one 关系
使用Many2one ,修改Course 和Session 模型,反映他们与其他模型的关系:
· course有一个responsible 用户,该字段的值是内建模型res.users的一个记录。
· Session 有一个instructor,该字段的值是内建模型res.partner的一个记录。
· 一个session被连接到一个course,该字段的值是模型openacademy.course的一个记录,并且是必须的。
· 更新视图

添加Many2one字段到模型里, 并将它们添加到视图中。

openacademy/models.py

    name = fields.Char(string="Title", required=True)
    description = fields.Text()

    responsible_id = fields.Many2one('res.users',
        ondelete='set null', string="Responsible", index=True)


class Session(models.Model):
    _name = 'openacademy.session'

----------
    start_date = fields.Date()
    duration = fields.Float(digits=(6, 2), help="Duration in days")
    seats = fields.Integer(string="Number of seats")

    instructor_id = fields.Many2one('res.partner', string="Instructor")
    course_id = fields.Many2one('openacademy.course',
        ondelete='cascade', string="Course", required=True)
openacademy/views/openacademy.xml

                    <sheet>
                        <group>
                            <field name="name"/>
                            <field name="responsible_id"/>
                        </group>
                        <notebook>
                            <page string="Description">


----------
           </field>
        </record>

        <!-- override the automatically generated list view for courses -->
        <record model="ir.ui.view" id="course_tree_view">
            <field name="name">course.tree</field>
            <field name="model">openacademy.course</field>
            <field name="arch" type="xml">
                <tree string="Course Tree">
                    <field name="name"/>
                    <field name="responsible_id"/>
                </tree>
            </field>
        </record>

        <!-- window action -->
        <!--
            The following tag is an action definition for a "window action",


----------
                <form string="Session Form">
                    <sheet>
                        <group>
                            <group string="General">
                                <field name="course_id"/>
                                <field name="name"/>
                                <field name="instructor_id"/>
                            </group>
                            <group string="Schedule">
                                <field name="start_date"/>
                                <field name="duration"/>
                                <field name="seats"/>
                            </group>
                        </group>
                    </sheet>
                </form>
            </field>
        </record>

        <!-- session tree/list view -->
        <record model="ir.ui.view" id="session_tree_view">
            <field name="name">session.tree</field>
            <field name="model">openacademy.session</field>
            <field name="arch" type="xml">
                <tree string="Session Tree">
                    <field name="name"/>
                    <field name="course_id"/>
                </tree>
            </field>
        </record>

        <record model="ir.actions.act_window" id="session_list_action">
            <field name="name">Sessions</field>
            <field name="res_model">openacademy.session</field>

练习
one2many 逆关系
使用one2many逆关系字段,修改模型,以反映course和session之间的关系
修改course类 ,在course表单视图添加该字段。

openacademy/models.py


    responsible_id = fields.Many2one('res.users',
        ondelete='set null', string="Responsible", index=True)
    session_ids = fields.One2many(
        'openacademy.session', 'course_id', string="Sessions")


class Session(models.Model):
openacademy/views/openacademy.xml

                            <page string="Description">
                                <field name="description"/>
                            </page>
                            <page string="Sessions">
                                <field name="session_ids">
                                    <tree string="Registered sessions">
                                        <field name="name"/>
                                        <field name="instructor_id"/>
                                    </tree>
                                </field>
                            </page>
                        </notebook>
                    </sheet>

练习
多重many2many 关系
利用关系字段many2many,修改session模型 ,将一组人与每一个课程关联。出席者将通过伙伴的记录作为代表,所以我们将与内置的模型res.partner关联。相应地调整视图。
修改session类,并添加到表单视图

openacademy/models.py

    instructor_id = fields.Many2one('res.partner', string="Instructor")
    course_id = fields.Many2one('openacademy.course',
        ondelete='cascade', string="Course", required=True)
    attendee_ids = fields.Many2many('res.partner', string="Attendees")
openacademy/views/openacademy.xml

                                <field name="seats"/>
                            </group>
                        </group>
                        <label for="attendee_ids"/>
                        <field name="attendee_ids"/>
                    </sheet>
                </form>
            </field>

继承
1.模型继承
Odoo提供两继承机制,以模块化的方式扩展现有的模型。
第一继承机制允许模块修改在其他模块中定义的模型的行为:
· 添加字段到模块
· 重写模型上字段的定义
· 向模型添加约束,
· 向模型添加方法,
· 在模型上重写现有方法。
第二继承机制(委托)允许将模型的每个记录与父模型中的记录连接起来,并提供对父记录字段的透明访问。
这里写图片描述

2.视图继承
与其修改现有的视图(通过重写他们),Odoo提供的视图继承让子“拓展”视图应用在顶部的根视图,并可以从父内容添加或删除。
拓展视图使用inherit_id字段参考它的父视图。而不是单一的,arch字段是由任意数量的xpath元素选择组成,并改变它们父视图的内容的视图:

<!-- improved idea categories list -->
<record id="idea_category_list2" model="ir.ui.view">
    <field name="name">id.category.list2</field>
    <field name="model">idea.category</field>
    <field name="inherit_id" ref="id_category_list"/>
    <field name="arch" type="xml">
        <!-- find field description and add the field
             idea_ids after it -->
        <xpath expr="//field[@name='description']" position="after">
          <field name="idea_ids" string="Number of ideas"/>
        </xpath>
    </field>
</record>

· expr 在父视图中选择单个元素的XPath表达式。如果不匹配任何元素或多于一个元素,则引发错误
· position 用于匹配元素的操作:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值