创建向导 - (The Process)

简介

向导是描述客户端和服务器之间的相互动作的序列.

以下是一个典型的向导进程:

  1. 向客户端发送一个窗口(待完成的表格)
  2. 客户端发送在此表格中所填的数据
  3. 服务器收到结果,执行一个函数并发送一个新的窗口到客户端
../../_images/Wizard.png

以下是一个向导的截图,该向导是用于处理业务的 (when you click on the gear icon in an account chart):

../../_images/Wizard_screenshot.png

向导 - 原则

向导具有一连串的步骤,每一个步骤由多个动作组成;

  1. 发送表单给客户端或按钮
  2. 客户端的按钮按下后,服务端获取表单的数据
  3. 执行动作
  4. 发送一个新动作给客户端(form,print, ...)

为了定义一个向导,需要创建一个继承于wizard.interface的类,并将其实例化。每个向导都有一个唯一的名字,该名字可以随意取,但它必须以组件名开头(如:account.move.line.reconcile).向导必须定义一个名为states的字典,该字典定义了它所有的步骤。一个简单的向导例子在 http://www.openobject.com/forum/post43900.html#43900

以下为一个向导类的例子:

class wiz_reconcile(wizard.interface):
      states = {
           'init': {
                'actions': [_trans_rec_get],
                'result': {'type': 'form',
                           'arch': _transaction_form,
                           'fields': _transaction_fields,
                           'state':[('reconcile','Reconcile'),('end','Cancel')]}
          },
           'reconcile': {
                'actions': [_trans_rec_reconcile],
                'result': {'type': 'state', 'state':'end'}
          }
     }
wiz_reconcile('account.move.line.reconcile');

‘states’ 字典定义了向导的所有状态。在这个例子里; initreconcile. 还有一个隐藏的状态叫 end .

向导一般从从 init 状态开始,到 end 状态结束.

状态定义了以下两个东西:

  1. 动作列表
  2. 结果

动作列表(The list of actions)

向导的每一步骤/状态都定义了动作列表,到向导进入该状态后便执行这些动作。动作列表可以是空的.

函数(actions)的语法规范如下 :

def _trans_rec_get(self, uid, data, res_get=False):

其中:

  • self 是指向向导当前对象的指针
  • uid 是执行此向导的用户ID
  • data 是包含以下数据的字典:
    • ids: 用户执行向导时,所关联的资源的id列表
    • id: 当用户执行向导时高亮的id
    • form: 一个字典,该字典包含了之前发送的表单里用户填写的数据,若你改变了字典里的值,则表单里的数据就会被提前填写.

每个动作函数必须返回一个字典。字典中的每一项将会与发送来的表单中所填写的数据合并.

结果(The result)

以下为一些result的例子:

Result: 下一步

'result': {'type': 'state',
           'state':'end'}

表示向导得继续下一步的状态: ‘end’. 如果这一步是 ‘end’ 状态,则向导停止.

Result: 发给客户端的新对话

'result': {'type': 'form',
           'arch': _form,
           'fields': _fields,
           'state':[('reconcile','Reconcile'),('end','Cancel')]}

type=form 表示这步骤是发送对话给客户端,对话由以下部分组成:

  1. a form : 带有字段的描述和表单的描述
  2. some buttons : 用户填写完数据后,按此按钮提交

表单的描述(arch)和视图一样,如下:

_form = """<?xml version="1.0"?>
        <form title="Reconciliation">
          <separator string="Reconciliation transactions" colspan="4"/>
          <field name="trans_nbr"/>
          <newline/>
          <field name="credit"/>
          <field name="debit"/>
          <field name="state"/>
          <separator string="Write-Off" colspan="4"/>
          <field name="writeoff"/>
          <newline/>
          <field name="writeoff_acc_id" colspan="3"/>
        </form>
        """

字段的描述和python对象里字段的描述类似。如:

_transaction_fields = {
      'trans_nbr': {'string':'# of Transaction', 'type':'integer', 'readonly':True},
      'credit': {'string':'Credit amount', 'type':'float', 'readonly':True},
      'debit': {'string':'Debit amount', 'type':'float', 'readonly':True},
      'state': {
                'string':"Date/Period Filter",
                'type':'selection',
                'selection':[('bydate','By Date'),
                             ('byperiod','By Period'),
                             ('all','By Date and Period'),
                             ('none','No Filter')],
                'default': lambda *a:'none'
                },
      'writeoff': {'string':'Write-Off amount', 'type':'float', 'readonly':True},
      'writeoff_acc_id': {'string':'Write-Off account',
                           'type':'many2one',
                           'relation':'account.account'
                         },
}

向导中每一步/状态都有多个按钮,这些按钮都分布在对话框的右下方。向导的每一步所涉及的按钮列表在结果字典的状态键中声明.

例如:

'state':[('end', 'Cancel', 'gtk-cancel'), ('reconcile', 'Reconcile', '', True)]
  1. 下一步的名称(决定下一个状态)
  2. 按钮的名字 (用于在客户端上的展示)
  3. the gtk stock item without the stock prefix (自 4.2)
  4. a boolean, 如果为true,按钮被设置为默认的动作 (自 4.2)

以下为这种表单的截图:

../../_images/Wizard_screenshot1.png

Result: 调用方法决定下一个状态

def _check_refund(self, cr, uid, data, context):
    ...
    return datas['form']['refund_id'] and 'wait_invoice' or 'end'

    ...

    'result': {'type':'choice', 'next_state':_check_refund}

Result: 打印一个报表

def _get_invoice_id(self, uid, datas):
      ...
      return {'ids': [...]}

      ...

      'actions': [_get_invoice_id],
      'result': {'type':'print',
                 'report':'account.invoice',
                 'get_id_from_action': True,
                 'state':'check_refund'}

Result: 客户端执行一个动作

def _makeInvoices(self, cr, uid, data, context):
    ...
    return {
                'domain': "[('id','in', ["+','.join(map(str,newinv))+"])]",
                'name': 'Invoices',
                'view_type': 'form',
                'view_mode': 'tree,form',
                'res_model': 'account.invoice',
                'view_id': False,
                'context': "{'type':'out_refund'}",
                'type': 'ir.actions.act_window'
        }

        ...

        'result': {'type': 'action',
        'action': _makeInvoices,
        'state': 'end'}

函数的返回的结果必须为 ir.actions.* 的所有字段. 这里为ir.action.act_window,所以客户端会打开一个新的标签页,新的标签页包含了account.invoicd的信息.

建议用一下方式读取 ir.actions 对象:

def _account_chart_open_window(self, cr, uid, data, context):
        mod_obj = pooler.get_pool(cr.dbname).get('ir.model.data')
        act_obj = pooler.get_pool(cr.dbname).get('ir.actions.act_window')

        result = mod_obj._get_id(cr, uid, 'account', 'action_account_tree')
        id = mod_obj.read(cr, uid, [result], ['res_id'])[0]['res_id']
        result = act_obj.read(cr, uid, [id])[0]
        result['context'] = str({'fiscalyear': data['form']['fiscalyear']})
        return result

        ...

        'result': {'type': 'action',
                   'action': _account_chart_open_window,
                   'state':'end'}

规范

表单(Form)

_form = '''<?xml version="1.0"?>
<form string="Your String">
    <field name="Field 1"/>
    <newline/>
    <field name="Field 2"/>
</form>'''

字段(Fields)

标准(Standard)

Field type: char, integer, boolean, float, date, datetime

_fields = {
      'str_field': {'string':'product name', 'type':'char', 'readonly':True},
}
  • string: 字段的标签(必填)
  • type: (必填)
  • readonly: (可选)

关系(Relational)

Field type: one2one,many2one,one2many,many2many

_fields = {
    'field_id': {'string':'Write-Off account', 'type':'many2one', 'relation':'account.account'}
}
  • string: 字段标签 (必填)
  • type: (必填)
  • relation: 所关系的对象名称

选择(Selection)

Field type: selection

_fields = {
    'field_id':  {
                 'string':"Date/Period Filter",
                 'type':'selection',
                 'selection':[('bydate','By Date'),
                              ('byperiod','By Period'),
                              ('all','By Date and Period'),
                              ('none','No Filter')],
                 'default': lambda *a:'none'
                 },
  • string: 字段标签 (必填)
  • type: (必填)
  • selection: 选择字段中的键和值

添加一个新向导

创建一个新向导,你应该:

  • 在一个 .py 文件中创建向导定义
    • 向导一般都是定义在组件中的向导子文件夹中 server/bin/addons/module_name/wizard/your_wizard_name.py
  • 把向导添加到导入的声明列表,该列表位于组件向导子目录的 __init__.py 文件.
  • 在数据库中声明向导

声明需要映射向导和客户端键之间的关系,从而才能启动相应的客户端。声明一个新向导,需要把它加到 module_name_wizard.xml 文件里,该文件包含了此组件所有的向导声明。若该文件不存在,则需先创建.

这里以 account_wizard.xml 文件做一个例子;

<?xml version="1.0"?>
<openerp>
    <data>
        <delete model="ir.actions.wizard" search="[('wiz_name','like','account.')]" />
        <wizard string="Reconcile Transactions" model="account.move.line"
                name="account.move.line.reconcile" />
        <wizard string="Verify Transac steptions" model="account.move.line"
                name="account.move.line.check" keyword="tree_but_action" />
        <wizard string="Verify Transactions" model="account.move.line"
                name="account.move.line.check" />
        <wizard string="Print Journal" model="account.account"
                name="account.journal" />
        <wizard string="Split Invoice" model="account.invoice"
                name="account.invoice.split" />
        <wizard string="Refund Invoice" model="account.invoice"
                name="account.invoice.refund" />
    </data>
</openerp>

向导的标签属性:

  • id: 此向导的唯一标识.
  • string: 如果一个资源关联多个向导,此字符串会显示).
  • model: 对象从该模型中获取所需数据.
  • name: 向导的名称,只可内部使用并且唯一.
  • replace (可选): 此向导是否要重写 all 所有已经存在的向导。缺省值: False.
  • menu (可选): 是否 (True|False) 把向导和 ‘gears’ 按钮 (i.e. show the button or not) 关联到一起。缺省值: True.
  • keyword (可选): 向导绑定另一动作 (print icon, gear icon, ...). 关键字属性的可能值为:
    • client_print_multi: 表单中的打印图标
    • client_action_multi: 表单中的 ‘gears’ 图标
    • tree_but_action: 列表中的 ‘gears’ 图标 (在左侧的快捷方式)
    • tree_but_open: 在树的一个分支,双击 (在左侧的快捷方式). 例如,有这样的应用,在菜单中来绑定向导.

__openerp__.py

若创建的向导是模块中的第一个,还需要创建 modulename_wizard.xml 文件. 在这样的情况下,需要在 __openerp__.py 模块文件中增加 update_xml 文件.

例如 ,下面的 account 模块 account_wizard.xml 需要添加到 __openerp__.py 文件.

{
    "name": OpenERP Accounting",
    "version": "0.1",
    "depends": ["base"],
    "init_xml": ["account_workflow.xml", "account_data.xml"],
    "update_xml": ["account_view.xml","account_report.xml", "account_wizard.xml"],
}

osv_memory 向导系统

开发一个 osv_memory 向导, 只需创建一普通的对象,不是继承至 osv.osv, 而是继承至 osv.osv_memory. 向导 “wizard” 的方法是在对象中的,如果向导很复杂,可以在对象中定义工作流. osv_memory 对象是存储在内存中的,而不是存储在 postgresql.

就这些,仅仅是改变了继承。这些向导可以被定义在任意位置,而不仅仅是 addons/modulename/modulename_wizard.py. 从历史上看,_wizard前缀的是(旧式)向导,所以有可能是一个osv_memory为基础的“新风格”“向导”是完全正常的对象(只是用来模拟旧的向导,并没有完全脱离旧式的向导. 此外,osv_memory的“向导”,往往需要多个对象 (例如. 一个osv_memory对象为每个状态的原始向导) 所以对应的是不完全的 1:1.

所以,为何他们看着想旧式的向导呢?

  • 在打开对象的动作中,你可以写入以下
<field name="target">new</field>

这表示对象会在一个新的窗口中打开,而非当前这个.

  • 可以使用 <button special=”cancel” .../> 来关闭窗口.

例如 : 在 project.py 文件中.

class config_compute_remaining(osv.osv_memory):
    _name='config.compute.remaining'
    def _get_remaining(self,cr, uid, ctx):
        if 'active_id' in ctx:
            return self.pool.get('project.task').browse(cr,uid,ctx['active_id']).remaining_hours
        return False
    _columns = {
        'remaining_hours' : fields.float('Remaining Hours', digits=(16,2),),
            }
    _defaults = {
        'remaining_hours': _get_remaining
        }
    def compute_hours(self, cr, uid, ids, context=None):
        if 'active_id' in context:
            remaining_hrs=self.browse(cr,uid,ids)[0].remaining_hours
            self.pool.get('project.task').write(cr,uid,context['active_id'],
                                                 {'remaining_hours' : remaining_hrs})
        return {
                'type': 'ir.actions.act_window_close',
         }
config_compute_remaining()
  • 视图也和普通的视图一样 (注意按钮).

例如 :

<record id="view_config_compute_remaining" model="ir.ui.view">
            <field name="name">Compute Remaining Hours </field>
            <field name="model">config.compute.remaining</field>
            <field name="type">form</field>
            <field name="arch" type="xml">
                <form string="Remaining Hours">
                    <separator colspan="4" string="Change Remaining Hours"/>
                    <newline/>
                    <field name="remaining_hours" widget="float_time"/>
                    <group col="4" colspan="4">
                        <button icon="gtk-cancel" special="cancel" string="Cancel"/>
                        <button icon="gtk-ok" name="compute_hours" string="Update" type="object"/>
                    </group>
                </form>
            </field>
        </record>
  • 动作也和普通的动作一样 (不要忘了添加一个target 属性)

例如 :

<record id="action_config_compute_remaining" model="ir.actions.act_window">
    <field name="name">Compute Remaining Hours</field>
    <field name="type">ir.actions.act_window</field>
    <field name="res_model">config.compute.remaining</field>
    <field name="view_type">form</field>
    <field name="view_mode">form</field>
    <field name="target">new</field>
</record>

osv_memory 配置项

有时,你的插件不希望用默认的配置,而需要进一步的配置从而工作的更好。在这种情况下, 你希望在安装之后能有一个配置向导,并在今后需要重新配置时能再次调用该向导.

5.0以上的openerp有这样的功能,但却无相应的文档,而且需要手工操作。为了这样的需求, 一个简单明了的新的解决方案已经出现.

基础概念

新的解决方案提供一个具有基本的行为 osv_memory 对象,你必须继承该对象。这行为 是用来控制配置项和扩展之间的流的,而且必须继承自此对象.

同时,还有一个可继承的视图,该视图提供一个基本的框架,通过这种机制从而达到很强的可定制性。所以强烈建议你继承该视图.

创建基本的配置项

你的配置模型

首先是创建配置项本身,这是以个普通的 osv_memory 对象,该对象有一些限制:

  • 必须继承至 res.config, 提供一个基本的配置行为和基本的事件控制器和扩展点
  • 必须提供一个 execute 方法.[#]_ 当验证配置表单和包含验证逻辑时,就会调用这个方法。方法没有返回值.
class my_item_config(osv.osv_memory):
    _name = 'my.model.config'
    _inherit = 'res.config' # mandatory

    _columns = {
        'my_field': fields.char('Field', size=64, required=True),
    }

    def execute(self, cr, uid, ids, context=None):
        'do whatever configuration work you need here'
my_item_config()

配置视图

接下来是配置表单。Openerp提供一个基础视图,你可以继承这个基础视图,所以你 不需要自己创建按钮和控制进度条。强烈推荐使用这个基本视图.

ir.ui.view 中加入一个 inherit_id 字段,把它的值设为 res_config_view_base:

<record id="my_config_view_form" model="ir.ui.view">
    <field name="name">my.item.config.view</field>
    <!-- this is the model defined above -->
    <field name="model">my.model.config</field>
    <field name="type">form</field>
    <field name="inherit_id" ref="base.res_config_view_base"/>
    ...
</record>

当不做任何改变时,会展示出一个对话框,该对话框中包含一个进度条和两个按钮, 毫无生趣. res_config_view_base 有一个特别的group hook,你可以用你自己 的代码代替它,如下:

<field name="arch" type="xml">
    <group string="res_config_contents" position="replace">
        <!-- your content should be inserted within this, the string
             attribute of the previous group is used to easily find
             it for replacement -->
        <label colspan="4" align="0.0" string="
            Configure this item by defining its field"/>
        <field colspan="2" name="my_field"/>
    </group>
</field>

打开你的窗口

下一步是创建 act_window ,用于连接模型和视图的配置:

<record id="my_config_window" model="ir.actions.act_window">
    <field name="name">My config window</field>
    <field name="type">ir.actions.act_window</field>
    <field name="res_model">my.model.config</field>
    <field name="view_type">form</field>
    <field name="view_id" ref="my_config_view_form"/>
    <field name="view_mode">form</field>
    <field name="target">new</field>
</record>
当在配置向导步骤的子菜单中列出多种配置选项时,注意到 act_windowname 字段会被显示出来
(在 Administration > Configuration > Configuration > Wizards).

注册你的动作

最后是在openerp中注册配置项。这是在 ir.actions.todo 对象中完成的, 需要一个 action_id 字段关联到之前创建的 act_window:

<record id="my_config_step" model="ir.actions.todo">
    <field name="action_id" ref="my_config_window"/>
</record>

ir.actions.todo 有3个可选字段:

sequence (default: 10)
执行次序,数值小的先执行.
active (default: True)
不活跃的步骤在下一轮配置时将不会被执行.
state (default: 'open')
配置步骤的当前状态,用于记录执行过程中所发生的时间,值包含有 'open', 'done', 'skip' and 'cancel'.

结果如下图:

../../_images/config_wizard_base.png

定制你的配置项

目前所具备的知识已经足够配置你的插件了,但做一些好的定制能得到更好的用户体验.

更进一步的视图的定制

也许你已经注意到之前的截图,在默认的情况下,窗口是没有标题的,虽然并无大碍但却影响美观.

在设置一个标题之前,需要在视图里做一些微小的改动: group 标签中需要填入 data ,这样就能修改父窗口中的多项配置:

<record id="my_config_view_form" model="ir.ui.view">
    <field name="name">my.item.config.view</field>
    <!-- this is the model defined above -->
    <field name="model">my.model.config</field>
    <field name="type">form</field>
    <field name="inherit_id">res_config_view_base</field>
    <field name="arch" type="xml">
        <data>
            <group string="res_config_contents" position="replace">
                <!-- your content should be inserted within this, the
                     string attribute of the previous group is used to
                     easily find it for replacement
                 -->
                 <label colspan="4" align="0.0" string="
                        Configure this item by defining its field
                 ">
                 <field colspan="2" name="my_field"/>
             </group>
         </data>
     </field>
</record>

然后,就能通过增加以下代码 data 元件,从而转换原始 formstring 属性 (这个例子,或许在 group 前):

<!-- position=attributes is new and is used to alter the
     element's attributes, instead of its content -->
<form position="attributes">
    <!-- set the value of the 'string' attribute -->
    <attribute name="string">Set item field</attribute>
</form>

Warning

Comments in view overload

At this point (December 2009) OpenERP cannot handle comments at the toplevel of the view element overload. When testing or reusing these examples, remember to strip out the comments or you will get runtime errors when testing the addon.

完成这步后,配置的表单有用了一个标题:

../../_images/config_wizard_title.png

更有趣的配置比如改变按钮,按钮是由对话框底部的 res_config_view_base 提供的: 删除一个按钮(若配置无法被跳过),改变按钮标签, ...

由于这些改变无具体的与之关联的属性,需要使用xpath选择器(使用 xpath 元素).

删除Skip按钮,把Recond按钮的标签改成Set。例如,可以在 group 元素后加入以下代码,如下:

<!-- select the button 'action_skip' of the original template
     and replace it by nothing, removing it -->
<xpath expr="//button[@name='action_skip']"
    position="replace"/>
<!-- select the button 'action_next' -->
<xpath expr="//button[@name='action_next']"
       position="attributes">
    <!-- and change the attribute 'string' to 'Set' -->
    <attribute name="string">Set</attribute>
</xpath>

and yield:

../../_images/config_wizard_buttons.png

还可以用这种方法改变按钮的名称, 这样方法在对象中被唤醒 (不推荐).

定制模型

使用 execute method hook, 可以很轻易的实现许多要求,但addon-specific要求会 更复杂点. res.config 必须提供所有的hooks用于复杂的行为.

忽略下一步

最后,通过调用 res.config 中的 self.next 方法,进入下一步的配置项。 action_nextaction_skip 最后 做的事。但是在某些情况下,循环当前的视图或实现一个和工作流一样的行为是必要的。在这样的情况下, 你可以通过 execute 返回一个字典, res.config 会跳转到那个视图,而不是 self.next 返回的那个 .

这是创建项目所必须做的,例如,让用户在一行中创建多个新用户.

在 skipping 执行一个动作

action_next 对比( action_next 要求 execute 被子类实现), action_skip 是实现 res.config 的。 但是在子模型需要完成sipping discovery的动作的情况下,它需要提供一个方法,该方法名为 cancel , 你可以用和 execute 一样的方法重载此函数。这是和 execute 一致的:不仅在 cancel 结束时可以自动调用 next 方法, 而且能 忽略下一步

选取动作

既可以选择action重载 action_nextaction_skip 方法,也可以实现更多的函数,如果此实例中的按钮多余两.

在这样的情况下,请记住,你需要提供一个方法,让用户能调用self.next函数,这样他才能配置他剩下的插件.

res.config 的公开接口

接口和标准的Openerp的变量一致: self, cr, uid, ids and context.

execute

Hook 方法会被 action_next 按钮(默认标签:Record)唤醒. 除非你想展示一个 新的视图而非下一步的配置项,否则不要返回任何内容 anything 。返回字典以为的东西将会导致未定义的行为.

重写它是必须的。如果不这么做,将会导致 NotImplementedError 错误.

默认的 res.config 在重载时不能被调用 (don’t use super).

cancel

action_skip 按钮 (default label: Skip) 会调用相关的方法,他和 execute‘ 是一样的,除了它不是必须被重载.

action_next and action_skip

基础视图中的时间控制按钮,重载他们是不需要的,但是在默认的 res.config 实现被调用 (via super) 且有返回值的情况下是必须的.

[1]This isn’t completely true, as you will see when 定制你的配置项
[2]this method is part of the official API and you’re free to overload it if needed, but you should always call res.config‘s through super when your work is done. Overloading next is also probably overkill in most situations.

Guidelines on how to convert old-style wizard to new osv_memory style

OSV Memory Wizard

provide important advantages over the pre-5.0 wizard system, with support features that were difficult to implement in wizards previously, such as:

  1. inheritance
  2. workflows
  3. complex relation fields
  4. computed fields
  5. all kind of views (lists, graphs, ...)

The new wizards are also easier and more intuitive to write as they make use of the same syntax as other osv objects and views.

This section will highlight the main steps usually required when porting a classical wizard to the new osv_memory wizard system. For more details about the osv_memory wizard see also section XXX.

Basically the idea is to create a regular osv object to hold the data structures and the logic of the wizard, but instead of inheriting from osv.osv, you inherit from osv.osv_memory. The methods of the old-style wizard will be moved as methods of the osv_memory object, and the various views changed into real views defined on the model of the wizard.

If the wizard is complex, you could even define a workflow on the wizard object (see section XXX for details about workflows)

Using a very simple wizard as an example, here is a step-by-step conversion to the new osv_memory system:

Steps

  1. Create a new object that extends osv_memory, including the required fields and methods:
../../_images/wizard_window.png
def _action_open_window(self, cr, uid, data, context):
.
.

class product_margins(wizard.interface):
    form1 = '''<?xml version="1.0"?>
    <form string="View Stock of Products">
        <separator string="Select " colspan="4"/>
        <field name="from_date"/>
        <field name="to_date"/>
        <field name="invoice_state"/>
    </form>'''

    form1_fields = {
        'from_date': {
                'string': 'From',
                'type': 'date',
                'default': lambda *a:time.strftime('%Y-01-01'),

        },
        'to_date': {
                'string': 'To',
                'type': 'date',
                'default': lambda *a:time.strftime('%Y-12-31'),

        },
        'invoice_state': {
                'string': 'Invoice State',
                'type': 'selection',
                'selection': [('paid','Paid'),('open_paid','Open and Paid'),('draft_open_paid','Draft, Open and Paid'),],
                'required': True,
                'default': lambda *a:"open_paid",
        },
    }

    states = {
      'init': {
            'actions': [],
            'result': {'type': 'form', 'arch':form1, 'fields':form1_fields, 'state': [('end', 'Cancel','gtk-cancel'),('open', 'Open Margins','gtk-ok')]}
        },
    'open': {
            'actions': [],
            'result': {'type': 'action', 'action': _action_open_window, 'state':'end'}
        }
    }
product_margins('product.margins')

New Wizard File : <<module_name>>_<<filename>>.py

class product_margin(osv.osv_memory):
    '''
    Product Margin
    '''
    _name = 'product.margin'
    _description = 'Product Margin'

    def _action_open_window(self, cr, uid, ids, context):
        .
        .
        .

    _columns = {
        #TODO : import time required to get correct date
        'from_date': fields.date('From'),
        #TODO : import time required to get correct date
        'to_date': fields.date('To'),
        'invoice_state':fields.selection([
           ('paid','Paid'),
           ('open_paid','Open and Paid'),
           ('draft_open_paid','Draft, Open and Paid'),
        ],'Invoice State', select=True, required=True),
    }
    _defaults = {
        'from_date':  lambda *a:time.strftime('%Y-01-01'),
        'to_date': lambda *a:time.strftime('%Y-01-01'),
        'invoice_state': lambda *a:"open_paid",
    }
product_margin()

转换视图到实际视图(real view)显示你的向导模块定义:

旧的向导文件 : wizard_product_margin.py

form1 = '''<?xml version="1.0"?>
<form string="View Stock of Products">
    <separator string="Select " colspan="4"/>
    <field name="date"/>
    <field name="invoice_state"/>
</form>'''

新的向导文件 : wizard/<<module_name>>_<<filename>>_view.xml

<record id="product_margin_form_view" model="ir.ui.view">
    <field name="name">product.margin.form</field>
    <field name="model">product.margin</field>
    <field name="type">form</field>
    <field name="arch" type="xml">
        <form string="Properties categories">
                <separator colspan="4" string="General Information"/>
                <field name="from_date" />
                <field name="to_date" />
                <field name="invoice_state" />
                <group col="4" colspan="2">
                            <button special="cancel" string="Cancel" type="object"/>
                            <button name="_action_open_window" string="Open Margins" type="object" default_focus=”1”/>
                </group>
        </form>
    </field>
</record>

Default_focus attribute

<button name="_action_open_window" string="Open Margins" type="object" default_focus=”1”/>

default_focus=”1” is a new attribute added in 5.2. While opening wizard default control will be on the widget having this attribute. There must be only one widget on a view having this attribute = 1 otherwise it will raise exception.

Note: 在旧向导全部状态下,我们应该用新途径建立按钮。

  1. To open the new wizard, you need to register an action that opens the first view on your wizard object. You will need to do the same for each view if your wizard contains several views. To make the view open in a pop-up window you can add a special target=’new’ field in the action:
<act_window name="Open Margin"
        res_model="product.margin"
        src_model="product.product"
        view_mode="form"
        target="new"
        key2="client_action_multi"
        id="product_margin_act_window"/>

key2=”client_action_multi” : While using it in the act_window, wizard will be added in the

  1. Action
../../_images/wizard_button.png
  1. Sidebar
../../_images/wizard_panel.png

If key2 is omitted then it will be displayed only in sidebar.

Note: The “src_model” attribute is only required if you want to put the wizard in the side bar of an object, you can leave it out, for example if you define an action to open the second view of a wizard.

  1. You can register this new action as a menuitem or in the context bar of any object by using a <menuitem> or <act_window> record instead of the old <wizard> tag that can be removed:

菜单选项

你可以使用下面的菜单语法打开一个向导视图(wizard view), 使用相应的act_window的XML id.

<menuitem id="main" name="OSV Memory Wizard Test"/>
<menuitem
    action="product_margin_act_window"
    id="menu_product_act"
    parent="main" />

4.打开向导视图(wizard view)经由“其他”按钮你可以使用下面的语法,应用相应的act_window里的XML id,构建按钮。这个常被应用到制作多步骤向导中。

<button name="%(product_margin.product_margin_act_window)d"
        string="Test Wizard" type="action" states="draft"/>

5.最后,你必须清理模块,如果你修改该了向导python文件名,那么必须更新__init__.py的python文件,同时对你新的XML文件加入到__openerp__.py文件的update_xml字段中。