2

Odoo Tips or Tricks

 2 years ago
source link: https://imnisen.github.io/odoo-notes.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Odoo Tips or Tricks

记录一些odoo相关的知识或技巧,供查阅借鉴。

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'将用户移动到哪个组')


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 根据一个(或者一些)字段值的改变,改变另外一些值

很简单,用[email protected]~

# 如果选择的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的读取和存储

可以参考这篇文章,讲述的很全: 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)]")

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK