Odoo Tips or Tricks
记录一些odoo相关的知识或技巧,供查阅借鉴。
Table of Contents
- 1. Form 表单里的 Button显示与否
- 2. 在列表视图的“动作按钮”添加动作, 可以单个或者批量操作
- 3. 上传图片时遇到报错 IOError: decoder jpeg not available
- 4. 在Form表单上弹出向导
- 5. New API 根据一个(或者一些)字段值的改变,改变另外一些值
- 6. New API 根据一个(或者一些)字段值的改变,筛选另外一些值(给另外一些值加上domain)
- 7. 字段fields.Date和fields.Datetime的读取和存储
- 8. Form表单里的提示性文字,在xml上的写法
- 9. XML 设置字段的一些属性, 以及一些常用视图的写法
- 10. XML one2many many2many类型字段widget格式
- 11. Form视图的Group属性
- 12. Form表单上的不同按钮触发同一个向导,但向导但视图需要根据不同按钮做些调整
- 13. 一个Bug,fields.Selection
- 14. 列表视图(Tree View)根据字段显示不同颜色
- 15. 复杂domain解析求值顺序
- 16. Field 设置domain
1 Form 表单里的 Button显示与否
可以通过 attrs
属性来设置,如下:
<button name="delete_message_button" string="删除群发" type="object" class="oe_inline oe_stat_button" icon="fa-refresh" attrs="{'invisible':['|',('state','not in',('send','finished')),('msgtype','not in',('mpnews', 'mpvideo'))]}"/>
但会跟 根据 state
来显示与否冲突
<button name="delete_message_button" string="删除群发" type="object" class="oe_inline oe_stat_button" states='draft' <-- 差异 icon="fa-refresh" attrs="{'invisible':['|',('state','not in',('send','finished')),('msgtype','not in',('mpnews', 'mpvideo'))]}"/>
上面例子里的 state
为表中的一个字段, 通常的模式是放到顶补做状态栏,然后按钮可以根据状态来是否显示。
当我需要同时根据状态和表中其它一个字段来控制是否显示时,就不能用 states='draft,send'
这种模式,会冲突导致 attrs
设置的属性无效。
Fuck so much patterns!
2 在列表视图的“动作按钮”添加动作, 可以单个或者批量操作
模式如下, 参考res.users 修改密码的模式 通常动作是弹出一个向导,所以需要先建立一个向导model
class change_password_wizard(osv.TransientModel): """ A wizard to manage the change of users' passwords """ _name = "change.password.wizard" _description = "Change Password Wizard" _columns = { 'user_ids': fields.one2many('change.password.user', 'wizard_id', string='Users'), } def _default_user_ids(self, cr, uid, context=None): if context is None: context = {} user_model = self.pool['res.users'] user_ids = context.get('active_model') == 'res.users' and context.get('active_ids') or [] return [ (0, 0, {'user_id': user.id, 'user_login': user.login}) for user in user_model.browse(cr, uid, user_ids, context=context) ] _defaults = { 'user_ids': _default_user_ids, }
根据实际需要建立几个模型(源代码处建立了两个,而我自己用的地方一个就够了)。
上述代码 _default_user_ids
方法得到选择的那些记录
XML 视图如下:
<!-- change password wizard --> <record id="change_password_wizard_view" model="ir.ui.view"> <field name="name">Change Password</field> <field name="model">change.password.wizard</field> <field name="arch" type="xml"> <form string="Change Password"> <field name="user_ids"/> <footer> <button string="Change Password" name="change_password_button" type="object" class="btn-primary"/> <button string="Cancel" class="btn-default" special="cancel" /> </footer> </form> </field> </record> <record id="change_password_wizard_user_tree_view" model="ir.ui.view"> <field name="name">Change Password Users</field> <field name="model">change.password.user</field> <field name="arch" type="xml"> <!-- the user list is editable, but one cannot add or delete rows --> <tree string="Users" editable="bottom" create="false" delete="false"> <field name="user_login"/> <field name="new_passwd" required="True" password="True"/> </tree> </field> </record> <act_window id="change_password_wizard_action" name="Change Password" src_model="res.users" res_model="change.password.wizard" view_type="form" view_mode="form" key2="client_action_multi" target="new" groups="base.group_erp_manager"/>
前两个record根据自己需要创建,主要是第三个, src_mode
指定在哪个mode上见示出来, res_model
指定弹出的wizard是哪个model的
在列表视图上需要钩选某些记录才能显示更多操作的按钮, 这时候往往需要将钩选记录的一些信息传入到弹出到Form中,需么从context中获得,模式如下:
# model file # 移动选定到用户到其它组 class change_users_groups_wizard(osv.TransientModel): _name = "change.users.groups.wizard" _description = u"更改用户组的向导" @api.multi def _get_default_user_ids(self): # 新api获得context的写法 context = dict(self._context or {}) # 从环境中去除当前model和当前钩选的ids user_ids = context.get('active_model') == 'my.users' and context.get('active_ids') or [] # 因为user_ids是many2many格式,所以这样返回, 如果是many2one 返回id return [(6, 0, user_ids)] user_ids = fields.Many2many('my.users', string=u'要移动的用户',default= _get_default_user_ids) groups_id = fields.Many2one('my.users.groups', string=u'移动到组', help=u'将用户移动到哪个组')
3 上传图片时遇到报错 IOError: decoder jpeg not available
4 在Form表单上弹出向导
首先表单视图上添加动作按钮
<button name="get_user_data" string="获取用户数据" type="object" class="oe_inline oe_stat_button" icon="fa-strikethrough"/>
然后对应的model 下写这个按钮触发的方法
@api.multi def get_user_data(self): return { 'name': _(u'获取用户数据'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'get.user.data.wizard', 'view_id': False, 'type': 'ir.actions.act_window', 'target': 'new' }
这样会弹出定义的向导(此处是"get.user.data.wizard")定义好的form视图。
向导xml可以这样写
<record id="view_get_user_data_wizard_form" model="ir.ui.view"> <field name="name">获取用户数据向导</field> <field name="model">get.user.data.wizard</field> <field name="arch" type="xml"> <form string="自定义菜单"> <sheet> <div class="oe_button_box" name="button_box"> </div> <div class="oe_title"> <label for="app_id" class="oe_edit_only"/> <h1> <field name="app_id" options="{'no_create': True,'no_open': True}"/> </h1> </div> <group> <group> <field name="company_id" groups="base.group_multi_company"/> <field name="begin_date" /> <field name="end_date" /> </group> <group> </group> </group> <footer> <button string="确认" name="get_userdata" type="object" class="btn-primary"/> <button string="取消" class="btn-default" special="cancel" /> </footer> </sheet> </form> </field> </record>
然后在该向导model中定义按钮方法写对应的逻辑就可以了
PS: 如果要在向导上获得点击向导前form的model或者id ,可以参照2
5 New API 根据一个(或者一些)字段值的改变,改变另外一些值
很简单,用~@api.onchange~
# 如果选择的type为url, 自动给content属性加上url前缀 @api.onchange('type') def _onchange_app_id(self): if self.type == 'url': self.content = 'http://' return
有些时候,会发现这样自动生成的内容在完成表单后没有保存上, 可以在字段上加上~compute=onchangeappid~来告诉model保存这个值
6 New API 根据一个(或者一些)字段值的改变,筛选另外一些值(给另外一些值加上domain)
# 选择app_id筛选user_id @api.onchange('app_id') def _onchange_app_id(self): if self.app_id: users = self.env['my.users'].search([('app_id', '=', self.app_id.id)]) user_ids = [user.id for user in users] return {'domain':{'user_id':[('id','in',user_ids)]}} # 如果是筛选多个字段,那么domain后多加字段筛选
7 字段fields.Date和fields.Datetime的读取和存储
- 用ORM直接读取出来的格式为字符串格式,fields.Date 默认格式为"%Y-%m-%d",fields.datetime默认格式为"%Y-%m-%d %H:%M:%S"(具体参数表示见https://docs.python.org/2/library/time.html?highlight=time#module-time)
- 使用ORM存的时候可以用datetime.datetime类型存储
- 时间格式间的转换
可以参考这篇文章,讲述的很全: http://blog.sina.com.cn/s/blog_b09d460201018o0v.html
### 假设self.begin_date_time取出的是默认的字符串格式,DEFAULT_DATETIME_FORMAT是"Y-%m-%d %H:%M:%S" # 字符串转化为time.struct_time time.strptime(self.begin_date_time, DEFAULT_DATETIME_FORMAT) struct_time类型可以用[0],[1]这样的index取出对应的year, month等等 # time.struct_time转化为浮点型的时间戳 time.mktime(time.struct_time) # 时间戳转化成dateime.datetime类型 datetime.fromtimestamp(timestamp) # struct_time 到字符串时间 time.strftime(DEFAULT_DATETIME_FORMAT, strcut_time)
8 Form表单里的提示性文字,在xml上的写法
可以用seperator
<separator style="font-size:13px;" colspan="4" string="注意: 如果不选择'发送的用户'将会默认发送给所有用户"/>
9 XML 设置字段的一些属性, 以及一些常用视图的写法
<!-- 设置对象不能创建,不能打开,一般用在many2one字段上 --> <field name="company_id" options="{'no_create': True,'no_open': True}"/> <!-- 设置在一定情况下隐藏或显示 --> <field name="state" /> <field name="result_ids" string="发送的消息返回的结果" attrs="{'invisible':[('state','=','draft')]}" /> <!-- 在一定情况下必填 --> <field name="send_type" /> <field name="group_id" attrs="{'required':[('send_type','=','group')]}" /> <!-- 当用户属于某个组当时候才可见 --> <field name="company_id" groups="base.group_multi_company"/> <!-- 新建记录前, 页面当提示信息, 比如新建要注意当地方 --> <!-- 写在action视图当最后一个字段 --> <field name="help" type="html"> <p>在公众平台网站上,为订阅号提供了每天一条的群发权限,为服务号提供每月(自然月)4条的群发权限。</p> <p>请注意</p> </field> <!-- 密码星号表示 --> <field name="ldap_password" password="True"/> <!-- default_focus 新开窗口光标位置 --> <field name="name" default_focus="1"/> <!-- digits 直接格式化浮点字段 --> <field digits="(14, 3)" name="volume" /> <!-- 新建一个空行 --> <newline/> <!-- 常用的的Form格式 --> <sheet> <!-- 如果页面上部分右边有些动作按钮,则向下面这样写 --> <div class="oe_button_box" name="button_box"> <button name="test_button" string="测试" type="object" class="oe_inline oe_stat_button" icon="fa-refresh"/> <button name="sync_button" string="同步" type="object" class="oe_inline oe_stat_button" icon="fa-strikethrough"/> </div> <!-- 如果有图片 --> <field name="image" widget='image' class="oe_avatar"/> <!-- 为了美观Form视图标题栏,可以将标题放大,可以参考下面格式 --> <div class="oe_title"> <label for="app_id" class="oe_edit_only"/> <h1> <field name="app_id" options="{'no_create': True,'no_open': True}"/> </h1> </div> <!-- other parts below--> </sheet> <!-- 一个常用的表单顶部有状态栏,和按钮改动状态的写法 --> <form string="发送消息表单"> <header> <button name="send_message_button" style="float:left;" string="发送信息" states="draft,failed" type="object" class="oe_highlight" /> <field name="state" widget="statusbar" statusbar_visible="draft,send,failed,cancel" /> </header> <sheet> <!-- other part --> </sheet> </form> <!-- One2Many 在one的一端显示many的记录--> <field name="result_ids" string="发送的消息返回的结果" attrs="{'invisible':[('state','=','draft')]}"> <form> <group> <field name="user_id"/> <field name="msgid"/> <field name="errmsg"/> <field name="errcode"/> </group> </form> <tree> <field name="user_id"/> <field name="msgid"/> <field name="errmsg"/> <field name="errcode"/> </tree> </field>
10 XML one2many many2many类型字段widget格式
可以选用下面这些: one2onelist,one2manylist,many2onelist,many2many,url,email,image,floattime,reference,many2manytags, selection,handle
11 Form视图的Group属性
- colspan 说明该控件占用父容器多少列 openerp form 为顶级容器,约定为 4 列
- rowspan 行数
- col 用于容器控件,如 group,它表示这个容器内部分几列
- string 组的名称
12 Form表单上的不同按钮触发同一个向导,但向导但视图需要根据不同按钮做些调整
在触发向导的按钮上传入环境变量用来区分是哪个按钮。 Button 按钮视图
<button name="get_data1" string="获取数据1" type="object" class="oe_inline oe_stat_button" icon="fa-strikethrough"/> <button name="get_data2" string="获取数据2" type="object" class="oe_inline oe_stat_button" icon="fa-strikethrough"/>
对应model里的按钮方法
@api.multi def get_data1(self): return { 'name': _(u'获取数据1'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'my.data.wizard', #向导的model 'view_id': '', 'type': 'ir.actions.act_window', 'target': 'new', 'context': {'wizard_code': '1'} } # 获取图文群发每日数据 @api.multi def get_data2(self): return { 'name': _(u'获取数据2'), 'view_type': 'form', 'view_mode': 'form', 'res_model': 'my.data.wizard', 'view_id': '', 'type': 'ir.actions.act_window', 'target': 'new', 'context': {'wizard_code': '2'} }
向导的model用一个额外的字段(比如叫'wizardcode')保存从环境变量获得的wizardcode
@api.model def default_get(self, fields_list): res = super(my_data_wizard, self).default_get(fields_list) context = self._context or {} if context.get('wizard_code', False): res.update({"wizard_code": context.get('wizard_code')}) return res
然后在视图将字段"wizardcode"隐藏,其它部分根据这个字段作相应变化
Done!
13 一个Bug,fields.Selection
ODOO的ORM模型有个转换bug,当用fields.Seletion字段是时,要避免元组的第一个不为0. 具体来说, 比如有两个字段
user_source = fields.Selection([('0', u'会话'), ('1', u'好友'), ('2', u'朋友圈'),('3', u'腾讯微博'),('4', u'历史消息'), ('5', u'其它')], string=u'用户的渠道',help=u'代表用户从哪里进入来阅读该图文') user_source1 = fields.Selection([(0, u'会话'), (1, u'好友'), (2, u'朋友圈'),(3, u'腾讯微博'),(4, u'历史消息'), (5, u'其它')], string=u'用户的渠道',help=u'代表用户从哪里进入来阅读该图文')
当我将某条记录的usersource1 设为数字0时, 是无法存上“会话”这个选项的。 当我查看具体执行的sql语句时,发现insert对应的项是"NULL", ORM模型将我给它的值数字0转化成了NULL存储(我在one2many用(0,0,vals)插入时发现的)。 相反的,当我在数据库里,手动将这个记录这个值设为数字0时,在后台界面上也显示不出具体的值。
14 列表视图(Tree View)根据字段显示不同颜色
<record id="acade_activity_tree" model="ir.ui.view"> <field name="name">acade_activity_tree</field> <field name="model">acade.activity</field> <field name="type">tree</field> <field name="arch" type="xml"> <tree string="acade.activity.tree" colors="green:is_over==False;red:is_over==True;"> <field name="title"/> <field name="start_time"/> <field name="end_time"/> <field name="address"/> <field name="view_times"/> <field name="apply_doctors"/> <field name="chat_created"/> <field name="is_over"/> </tree> </field> </record>
重点在"colors"这个属性
15 复杂domain解析求值顺序
从右往左读取,读取到一个三元结构求值,再向左读取
16 Field 设置domain
is_permanent = fields.Boolean(u'是否是永久素材') # domain 里跟常量比较的写法 field_b = fields.Many2one('a.model', domain=[('is_permanent', '=', True)]) # domain 里跟本model的字段比较 field_c = fields.Many2one('a.model', domain="[('is_permanent', '=', is_permanent)]")