Odoo 开发文档

[TOC]

参考资料

Structural 构造属性

名称 说明
_name 业务对象名称
_rec_name 显示名称(default: 'name')
_inherit 继承
_order 排序 (default: 'id')
_auto 是否自动创建数据库表(default: True),如果设置成false,需要重写 init() 来创建数据库表
_table 数据库表名称
_inherits
_constraints
_sql_constraints
_parent_store

Fields 字段

Basic fields 基础字段

名称 参数
odoo.fields.Char size (int) 、 translate
odoo.fields.Boolean
odoo.fields.Integer
odoo.fields.Float digits -- a pair (total, decimal), or a function taking a database cursor and returning a pair (total, decimal)
odoo.fields.Text translate
odoo.fields.Selection
odoo.fields.Html
odoo.fields.Date
odoo.fields.Datetime

字段属性

Name 描述
string 在用户界面显示的label,如果没有设置,则用首字母大写的 field name
help 在用户界面,当鼠标移动在这个field上时,弹出一个提示框,内容为help中填写的内容
readonly(bool) 这个字段是不是只读的,默认为 False
required(bool) 这个字段是不是必填的,默认为 False
index(bool) 这个字段在数据表中是否设置索引,默认为 False
default 要么设置为静态的值,要么设置为一个函数,这个函数接受一个recordset作为参数,并返回一个值。 如: default=lambda self: self.env.user.name
states 一个字典,key为state字段中的各项值,value中可用的为”readonly”, “required”, “invisible”。注意: 使用这个字段,必需是这个model中设置了state 字段,这样才能被客户的正确的处理。这个的设置,通常是为了在某个阶段,才能让用户看到相应的信息states={‘draft’:[(‘readonly’, False)], ‘sent’:[(‘readonly’: False)]}
groups 以英文逗号分割的字符串,使这个字段只属于对应的groups。 如: groups=”crm.group_sale_1,crm.group_sale_2”
copy(bool) 当在客户端复制某个record时,是否将这个字段的值复制过去。一般的field都是True, One2many 以及 computed field 和 related field 和 property field 都是 False
oldname(string) 这个field之前的名字,用以ORM能找到对应的字段进行更新数据
size Char类型字段属性,设置字段存储的最大值(整数、可选)
digits Float类型字段属性, 定义整数部分和小数部分的位数 digits=(12,6)

简单例子

from odoo import models, fields, api, _

class MyModel(models.Model):
    _name = 'mymodule.mymodel'
    # Fields are declared as class attributes:
    char = fields.Char('Char', 64)        # name, size
    text = fields.Text('Text')
    intg = fields.Integer('Integer')
    flot = fields.Float('Float', (16,4))  # name, digits
    flag = fields.Boolean('Boolean')
    date = fields.Date('Date')
    time = fields.Datetime('Date and Time')
    html = fields.HTML('Hypertext')
    biny = fields.Binary('Binary')
    year = fields.Selection(string=u'年度', selection=[(num, str(num)) for num in range((datetime.datetime.now().year - 9), (datetime.datetime.now().year + 2))])
    # 如果这个字段是extended的话,可以通过设置这个属性,给源字段添加一些选项。必需是list of pairs(values, string)
    year = fields.selection_add(...)

Relational fields 关联字段

related field的值就是一个由关系型字段的field names 用 dot 链接的string,当然最后一个field name可能不是关心型的。

如果 cp_name = fields.Char(related='parent_id.company_id.name')。这个related field的一些属性将会自动的从源field 中直接复制: string, help, readonly, required(这个必须是所有的field name都是required,这个related field才会被设置成required=True), groups, digits, size, translate, sanitize, selection, comodel_name, domain, context。

默认的,这个字段也不会存在数据库中。但是可以通过添加store=True,使其存在数据库中。就像computed field 一样, related field 在他依赖的字段值发生变化时,他也会自动的重新计算。

Many2one

class odoo.fields.Many2one(comodel_name=None, string=None, **kwargs)

Name Description
Parameters: comodel_name (string) 目标model的name
domain(domain or string) 可选,用以在客户端供用户选择时,先进行一定的筛选
context(dict) 可选,用以在客户端处理这个字段时,设置他的context
ondelete 设置当引用的record被删除是,如果对本record进行的行为,可填:set null, restrict, cascade
auto_join whether JOINs are generated upon search through that field (boolean, by default False)
delegate set it to True to make fields of the target model accessible from the current model (corresponds to _inherits)

One2many

class odoo.fields.One2many(comodel_name=None, inverse_name=None, string=None, **kwargs)

comodel_name 和 inverse_name 是必需设置的,除了这个field 是 related field or field extendsions

Name Description
Parameters: comodel_name (string) 目标model的name
inverse_name 对应target model(B model)的 Many2one的field的名字,这个field的 comodel_name必需是A model
domain 同Many2one
context 同上
auto_join 同上
limit optional limit to use upon read (integer)

Many2many

class odoo.fields.Many2many(comodel_name=None, relation=None, column1=None, column2=None, string=None, **kwargs)

relations column1 column2 是可选的,如果没提供,那么Odoo将会自动的根据对应的两个model自动生成。

Name Description
Parameters: comodel_name (string) 目标model的name
relation(string) 可选,储存两者关系的表的名字
column1(string) optional name of the column referring to “these” records in the table relation
column2(string) optional name of the column referring to “those” records in the table relation
domain(domain or string) 和其他两种relation field一样
context(dict) 同上
limit(int) 同上
selc = fields.Selection([('code','Desc'),...], 'Label')
refr = fields.Reference([('a.model', 'A Model'),...], 'Label')
m2o  = fields.Many2one('amodule.amodel', 'Related Value')  # comodel, string
o2m  = fields.One2many('amodule.amodel', 'inverse_id', 'Related List')

Computed fields 计算字段

用以设置某个字段的值是直接根据计算而来,而不是仅仅的从数据库中读取。下面给出了这类字段所需要设置的属性,如果要定义一个computed field,只需要设置 compute 属性即可。

Name 描述
Parameters: compute 一个方法的名字,这个方法是用来计算这个field的值的
inverse 一个方法的名字,用以在设置这个字段后,针对对应的字段设置他们的值。可选的
search 一个方法的名字,定义针对这个字段的search行为。 可选的
store(bool) 这个字段是否存在数据库中。设置compute之后,默认为False
compute_sudo(bool) 这个字段是否需要以admin的权限的来计算,用以越过access rights, 默认False
upper = fields.Char(compute='_compute_upper', inverse='_inverse_upper', search='_search_upper')

@api.depends('name')
def _compute_upper(self):
    for rec in self:
        self.upper = self.name.upper() if self.name else False

def _inverse_upper(self):
    for rec in self:
        self.name = self.upper.lower() if self.upper else False

def _search_upper(self, operator, value):
    if operator == 'like':
         operator = 'ilike'
    return [('name', operator, value)]

这个 compute method 将被作用到这个model中的每一个record中。@api.depends('field_name') 这个装饰器必需作用与compute method上,用以指明这些依赖,以明确当那些依赖字段的值发生变化时,好重新计算这个field的值。重计算是自动的,并且确保了cache和database的一致性。

注意:一个method可以作用于多个字段。只需要对这些计算字段的compute属性设置相同的compute method name 即可,这样这些方法只会针对这些字段被调用一次。

默认情况下,computed field不会存在数据库中,他们计算是 on-the-fly。可以通过添加一个属性 store=True 使得这个字段存在数据库中。带来的优势就是可以针对这个字段进行 search,并且是在数据库层就被search 完毕。劣势是当这个字段必需重新计算是,将会update database。

inverse method,如他的名字一样,进行 compute method的逆运算。当你对 computed field设置某个值后,必需对这个computed field的 依赖字段进行某些改变,使得 compute method 对 依赖字段 计算之后,得到的值与你填入computed field的值相同。

注意:如果没有设置inverse method,那么这个computed field是 readonly = True 的 search method 就是当有对这个field search时,hack掉基础的search 行为,并且返回一个新的domain,再进行search。必须是 field operator value

from openerp import models, fields, api, _

class MyModel(models.Model):
    _name = 'module.mymodel'

    name = fields.Char(required=True)
    parent = fields.Many2one('test_new_api.category')
    display_name = fields.Char(compute='_compute_display_name', inverse='_inverse_display_name')

    @api.one
    @api.depends('name', 'parent.display_name') # this definition is recursive
    def _compute_display_name(self):
        if self.parent:
            self.display_name = self.parent.display_name + ' / ' + self.name
        else:
            self.display_name = self.name

    @api.one
    def _inverse_display_name(self):
        names = self.display_name.split('/')
        # determine sequence of categories
        categories = []
        for name in names[:-1]:
            category = self.search([('name', 'ilike', name.strip())])
            categories.append(category[0])
        categories.append(self)
        # assign parents following sequence
        for parent, child in zip(categories, categories[1:]):
            if parent and child:
                child.parent = parent
        # assign name of last category, and reassign display_name (to normalize it)
        self.name = names[-1].strip()

View 视图

隐藏

<field name="currency_id" invisible="True"/>
<field name="currency_id" invisible="1"/>

在某种条件下隐藏

<field name="expense_description" attrs="{'invisible':[('expense_audit','!=','1')]}" laceholder="请填写错误信息..."/>

隐藏label

<field name="description" widget="html" nolabel="True"/>
<field name="description" widget="html" nolabel="1"/>

只读 readonly

<field name="budget_id" readonly="True"/>

条件 domain

<field name="product_id" domain="[('pro_type','=','rests')]"/>

设定值 eval

<field name="fill_date" eval="datetime.now()" readonly="True"/>

禁用下拉框选择更多页面创建按钮

<field name="field" options="{'no_create': True,'no_open': True}" />

显示在一行

<label for="odometer"/>
<div class="o_row">
  <field name="odometer"/>
  <field name="odometer_unit"/>
</div>

显示单位

<label for="power"/>
<div class="o_row">
    <field name="power"/><span>kW</span>
</div> string="General Properties">

显示短标签

<label for="cost_generated"/>
<div>
  <field name="cost_generated" class="oe_inline" attrs="{'invisible': [('cost_frequency','=','no')]}"/>
  <field name="cost_frequency" class="oe_inline"/>
</div>
<label for="number_of_days_temp" string="Duration"/>
<div>
  <div attrs="{'invisible': [('type', '=', 'add')]}">
    <field name="date_from" attrs="{'required':[('type', '=', 'remove')]}" class="oe_inline"/>
    <label string="-" class="oe_inline"/>
    <field name="date_to" attrs="{'required':[('type', '=', 'remove')]}" class="oe_inline"/>
  </div>
  <div>
    <field name="number_of_days_temp" class="oe_inline"/> days
  </div>
</div>

Button 按钮

type

  • object - 对象
  • action - 动作
  • workflow - 工作流

name

对应python方法

groups

对应权限组

states

对应状态field

context 上下文

default_ 开始代表直接赋值过去

<button class="oe_stat_button" name="%(budget_review_action)d" type="action" icon="fa-calendar-check-o" attrs="{'invisible':[('state','!=','check')]}" context="{'default_budget_id': id, 'default_contract_area': square, 'default_contract_price': total_price, 'default_start_date': start_date, 'default_end_date': end_date}" string="创建审核单"/>

Widget 挂件

禁用下拉框的创建编辑等选项

many2one widget (default)

  • no_quick_create - remove the Create and edit... option.
  • no_create_edit - remove the Create "search_value" option.
  • no_create - no_quick_create and no_create_edit combined.
  • no_open - in read mode: do not render as a link.
<field name="field_name" options="{'no_quick_create': True, 'no_create_edit' : True}"/>

many2many widget (default)

  • no_create - remove the Create button.
<field name="field_name" options="{'no_create': True}"/>

many2many_tags widget

  • no_quick_create - remove the Create and edit... option.
  • no_create_edit - remove the Create "search_value" option.

  • no_create - no_quick_create and no_create_edit together.

<field name="field_name" widget="many2many_tags" options="{'no_create_edit': True}"/>

Advanced Views 高级视图

控制Relational字段的创建、修改、删除、快速创建、打开

<field name="location" options="{'no_create_edit': True, 'no_quick_create': True, 'no_create': True, 'no_open':True}"/>

Tree views

增加合计行

sum="Total" 字段必须为可以计算的类型

<field name="preferential" mode="tree" options="{'no_create_edit': True, 'no_quick_create': True}" nolabel="1">
    <tree>
        <field name="main_materials_money" widget="monetary" sum="Total"/>
        <field name="other_decoration" widget="monetary" sum="Total other"/>
    </tree>
</field>

控制Tree视图行的增删改,使用在Tree标签上

create="false" edit="false" delete="false"

<field name="preferential" mode="tree" options="{'no_create_edit': True, 'no_quick_create': True}" nolabel="1">
    <tree create="false" edit="false" delete="false">
        <field name="from_number"/>
        <field name="dis_name"/>
        <field name="fill_date"/>
    </tree>
</field>

更改行样式

<tree string="Idea Categories" decoration-info="state=='draft'"
    decoration-danger="state=='trashed'">
    <field name="name"/>
    <field name="state"/>
</tree>

允许行编辑editable="top"editable="bottom"

<field name="arch" type="xml">
    <tree editable="top">
        <field name="name"/>
    </tree>
</field>

Search views

<search string="Ideas">
    <field name="name"/>
    <field name="description" string="Name and description"
           filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
    <field name="inventor_id"/>
    <field name="country_id" widget="selection"/>

    <filter name="my_ideas" string="My Ideas"
            domain="[('inventor_id', '=', uid)]"/>
    <group string="Group By">
        <filter name="group_by_inventor" string="Inventor"
                context="{'group_by': 'inventor_id'}"/>
    </group>
</search>

ORM API

集合操作

记录集是不变的,但是相同模块的集合可以通过使用一系列的操作符来结合,返回新的记录集, 集合操作 保留顺序

  • record in set 是否返回 record (第一项必须是一个元素的记录集) 在 set 中 。 record not in set 是相反的操作
  • set1 <= set2set1 < set2 返回是否 set1set2 的子集
  • set1 >= set2set1 > set2 返回是否 set1set2 的超级
  • set1 | set2 返回两个集合的合集,一个新的记录集包括任一个集合中的所有记录
  • set1 & set2 返回两个集合的并集,一个新的记录集仅包括两个集合中都有的记录
  • set1 - set2 返回一个记录在 set1 而不 set2 中的新的记录集

其他记录集操作方法

记录集是可迭代的因此通常的Python 工具是可用于转化的map(), sorted(),ifilter(), ...) 然而这些返回的要么是 list 要么是 iterator,去除了在结果之上调用的方法的能力,或者去除了使用集合的操作。

记录集因此提供这些操作返回记录集本身:

filtered()

返回一个只包含满足提供判定函数的记录集。判定也可以是由真或假字段筛选的字符串:

# only keep records whose company is the current user's
records.filtered(lambda r: r.company_id == user.company_id)

# only keep records whose partner is a company

sorted()

返回一个通过关键字函数排序的记录集。如果未提供关键字,使用模块默认的排序:

# sort records by name
records.sorted(key=lambda r: r.name)

mapped()

将提供的函数应用于记录集中的每一条记录,如果结果是记录集将返回一个记录集:

# returns a list of summing two fields for each record in the set
records.mapped(lambda r: r.field1 + r.field2)

提供的函数可以使字符串来获取字段的值:

# returns a list of names
records.mapped('name')

# returns a recordset of partners
record.mapped('partner_id')

# returns the union of all partner banks, with duplicates removed
record.mapped('partner_id.bank_ids')

Environment 环境

通过ORM存储各种环境中的数据:数据库游标(数据库查询) 、当前用户 (用来权限检查)、当前环境(存储任意的元数据)。环境可以被存储在缓存中。

所有的记录集都有一个不可变的环境,它可以通过 env 访问,通过 (user), 游标 (cr) or 上下文 (context)给当前用户访问:

>>> records.env
<Environment object ...>
>>> records.env.user
res.user(3)
>>> records.env.cr
<Cursor object ...)

当从其他记录集创建了一个记录集,这个环境是可以被继承的。环境可以被用于从其他模块获取一个空的记录集, 并且查询这个模块:

>>> self.env['res.partner']
res.partner
>>> self.env['res.partner'].search([['is_company', '=', True], ['customer', '=', True]])
res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74)

转换环境

来自一个记录集的环境可以被定制。使用转换环境返回一个新版本的记录集。

sudo()

根据提供的用户来创建一个新的环境,如果没有提供则使用管理员(绕过权限/规则的安全上下文), 返回一个调用使用新的环境的记录集:

# create partner object as administrator
env['res.partner'].sudo().create({'name': "A Partner"})

# list partners visible by the "public" user
public = env.ref('base.public_user')
env['res.partner'].sudo(public).search([])

with_context()

  1. 可以携带一个位置参数,它将取代目前的环境的上下文
  2. 可以通过关键字携带任意数量的参数,这些参数将被增加到当前环境上下文中或步骤1中的上下文设置中:
# look for partner, or create one with specified timezone if none is
# found
env['res.partner'].with_context(tz=a_tz).find_or_create(email_address)

with_env()

彻底替换现存的环境

ORM Method 共用方法

search() 提供一个 search domain ,返回一个匹配的记录集。 也可以返回匹配的记录集的子集,通过 offsetlimit 参数,同时通过 order 参数排序:

>>> # searches the current model
>>> self.search([('is_company', '=', True), ('customer', '=', True)])
res.partner(7, 18, 12, 14, 17, 19, 8, 31, 26, 16, 13, 20, 30, 22, 29, 15, 23, 28, 74)
>>> self.search([('is_company', '=', True)], limit=1).name
'Agrolait'

.. tip:: 只检查是否有任何匹配的记录,或计数的数目,使用 :meth:`~odoo.models.Model.search_count`

create()

提供一系列数量字段的值,返回包含该记录的记录集:

>>> self.create({'name': "New Name"})
res.partner(78)

write()

提供一系列字段值,将他们写入到记录集中的所有记录中。不返回任何东西:

self.write({'name': "Newer Name"})

browse()

提供数据库id或者ids的集合,返回一个记录集,当记录的id从odoo之外被获取是有用的 (例如 往返通过外部的系统)或:ref:[UNKNOWN NODE title_reference]:

>>> self.browse([7, 18, 12])
res.partner(7, 18, 12)

exists()

返回一个仅存在于数据库中记录的新的记录集。可以用来检查是否该记录依旧存在:

if not record.exists():
    raise Exception("The record has been deleted")

或者调用一个方法后应该移除一些记录:

records.may_remove_some()
# only keep records which were not deleted
records = records.exists()

ref()

环境的方法返回匹配到的 external id 的记录:

>>> env.ref('base.group_public')
res.groups(2)

ensure_one()

检查该记录集是一个signleton(仅包含一个单一记录),否则提示一个错误:

records.ensure_one()
# is equivalent to but clearer than:
assert len(records) == 1, "Expected singleton"

Method decorators 方法修饰符

名称 说明
@api.one one装饰符自动遍历记录集,把self重新定义成当前记录。注意,返回值是一个list. web client有可能不支持该装饰。这时应该用@api.multi修饰函数,函数中可能还需要条用self.ensure_one() 。
@api.multi 该方法携带一系列ids被展现出来,对应旧的API的 cr, uid, ids, *arguments, context:。
@api.model 该方法没有使用ids被展现出来,它的记录集是空的,对应旧的API的 cr, uid, *arguments, context:
@api.constrains 该装饰确保被修饰的函数在create, write, unlink时被调用。当约束条件满足时,函数应该raise 相应的异常警告消息
@api.depends 返回指定“计算”方法字段依赖项的装饰器
@api.onchange 返回一个装饰器装饰的Onchange()方法
@api.returns 返回用于返回模型实例的方法的装饰器

Domain 动态过滤

domain是一个标准列表,每个标准是一个三元组(要么是 list 或是一个 tuple )的 (field_name, operator, value)

field_name (str) 当前模型的字段名,或者通过 Many2one 的关系遍历, 使用点符号。

operator 用于比较 field_namevalue 的操作符。

value 变量类型,必须是与命名字段可比的

操作符

名称 说明
=,!=,>,>=,<,<= 等于,不等于,大于,大于等于,小于,小于等于
=? 未设置或等于(如果 valueNoneFalse ,则返回真,否则表现为 =
like 模糊匹配,可以使用通配符,下划线“_”匹配一个字符,百分号“%”匹配零或者多个字符
ilike 类似like,不区分大小写
not ilike 等同于不区分大小写的 not like
=ilike 等同于不区分大小写的 =like
not like 模糊不匹配的
in 包含,判断值是否在元素的列表里面
not in 不包含,判断值是否不在元素的列表里面
child_of 是一个 value 记录的子节点(子孙), 采用模型的语义(即关系字段命名为 _parent_name )。

条件间的逻辑前缀

名称 说明
& 逻辑运算符 AND ,组合条件的默认操作。
\ 逻辑 OR,2个参数
! 逻辑 NOT,1个参数

注意如果需要在xml使用Domain,大于,小于因为和标签符号一样所以需要转义,大于>,小于<

& :逻辑 AND

['&', ('partner_id.coutnry_id.code', '=', 'CN'), ('partner_id.coutry_id.code', '=', 'US')]

这里的 & 就是把后面的2个条件通过AND组合起来也就是 A AND B,但是注意到这里还有“条件组合”的情况,所以还有可能是

['&', ('partner_id.coutnry_id.code', '=', 'CN'), 
'&',('partner_id.coutry_id.code', '=', 'US'), ('partner_id.coutry_id.code', '=', 'GB')]

转换为一般的表示方法则是 A AND (B AND C),但是因为'&'是默认的逻辑关系,所以我们其实可以不用显式表示

[('partner_id.coutnry_id.code', '=', 'CN'),('partner_id.coutry_id.code', '=', 'US'), 
('partner_id.coutry_id.code', '=', 'GB')]

动态过滤例子**

location = fields.Many2one(comodel_name='material.suit.category', string=u'区域位置', domain=lambda self: self._get_location_domain())
suite_series = fields.Many2one(comodel_name='material.suit', string=u'整装系列', domain=[('name', 'in', [u'心',u'爱'])], required=True)

@api.model
def _get_location_domain(self):
    item = self._context.get('suite_series')
    return "[('identifier', '=', 'mainarea'), ('the_full_name' , 'ilike', '%s')]" % self.env['material.suit'].browse(item).name
<field name="main_materials_list" context="{'suite_series':suite_series}" />
domain="[('website_id','=',context.get('website_id'))]"
context="{'category_id': context.get(''), 'suit_id': context.get('')}"

在这里需要从视图上先从上一个视图页面获取值,可以使用context="{'suite_series':suite_series}"来传递。

Wizards 向导模式

Wizard 可以通过 dynamic form 给 user 提供 交互式的sessions,a warzard 就是一个简单的model,继承自TransientModel ,而不是普通的Model, class TransientModel 也是继承自 Model, 拥有Model的所有特性,但是多出如下几点:

  • Wizard record 不是固定的,他们将会在一定时间后,就被从database中删除掉,所以叫做 Transient
  • Wizard models 是不需要给它设置权限的,因为任意用户都拥有它的所有权限
  • Wizard records 可能会用到 many2one 连接到 普通的 record,但是普通的 record 是不能通过 many2one 连接到 wizard records
# -*- coding: utf-8 -*-

from odoo import models, fields, api

class Wizard(models.TransientModel):
    _name = 'openacademy.wizard'

    session_id = fields.Many2one('openacademy.session', string="Session", required=True)
    attendee_ids = fields.Many2many('res.partner', string="Attendees")

    @api.multi
    def subscribe(self):
        self.session_id.attendee_ids |= self.attendee_ids
        return {}

Launching wizards

Wizards 是通过 ir.actions.act_window 被调用,在这个action中设置 field target 为 new,这样设置之后,通过menu点击触发action,将会打开一个新的窗口。

<!-- 新增 -->
<record model='ir.ui.view' id='wizard_form_view'>
    <field name='name'>wizard.form</field>
    <field name='model'>openacademy.wizard</field>
    <field name='arch' type='xml'>
        <form string='Add Attendees'>
            <group>
                <field name='session_id'/>
                <field name='attendee_ids'/>
            </group>
        </form>
    </field>
</record>

<!-- 新增 footer -->
<footer>
    <button name="subscribe" type="object"
            string="Subscribe" class="oe_highlight"/>
    <button special="cancel" string="Cancel"/>
</footer>

<act_window id="launch_the_wizard"
            name="Launch the Wizard"
            src_model="context.model.name"
            res_model="wizard.model.name"
            view_mode="form"
            target="new"
            key2="client_action_multi"/>

Wizard 也拥有普通 views的特性,但是 它的button可以设置 special="cancel",用以关闭窗口,且wizard中执行的其它操作,将不会生效。

常用方法

odoo-bin 常用命令

# 创建模块
python odoo-bin scaffold <module name> <where to put it>

Many2many 赋值

详细Many2many字段解释,往上翻。

product_ids = fields.Many2many(comodel_name='product.product', relation='material_product_rel', column1='main_id', column2='product_id', string=u'材料')

@api.onchange('name')
def _set_values(self):
    self.product_ids = [(6, False, [pdc.product_id.id for pdc in self.name.product_id])]

@api.model
def create(self, values):
    """重写保存方法"""
    if values['name'] and not 'product_ids' in values:
        material = self.env['project.record'].browse(values['name'])
        values['product_ids'] = [(6, False, [pdc.product_id.id for pdc in material.product_id])]
        return super(BudgetMainMaterial, self).create(values)

根据选择的many2many值来显示相应字段

<group>
    <field name="power_ids" widget="many2many_tags"/>
    <field name="power_char" attrs="{'invisible':[('power_ids','not in', [(6, False, [])])]}"/>
</group>

One2many 选择数据

直接在View里面使用widget即可,widget="many2many_kanban"

<page string="材料">
    <group>
        <field name="product_ids" widget="many2many_kanban" mode="tree" nolabel="1">
            <tree>
                <field name="name"/>
                <field name="product_brand_id"/>
                <field name="unit"/>
                <field name="specification"/>
                <field name="model_type"/>
                <field name="comments"/>
                <field name="attribute_value_ids"/>
            </tree>
        </field>
    </group>
</page>

results matching ""

    No results matching ""