向导
向导以动态形式描述与用户(或对话框)的交互式会话。向导与其他的模型不同,其基类是TransientModel
而不是常见的Model
。TransientModel
类扩展自Model
,并使用了其所有机制,具有以下特殊性:
- 向导记录不是永久性的,会在一段时间后自动从数据库中删除。这就是为什么他们被称为瞬态。
- 向导模型不需要访问权限:用户拥有向导记录的所有权限
- 向导记录可以通过many2one字段引用普通记录或向导记录,但普通记录无法通过many2one引用向导记录。
我们要创建一个向导,用来让用户可以生成授课的参与者,或者一次创建一个授课列表。
练习定义向导
创建一个向导模型,这个向导模型通过many2one关联授课模型,并通过many2many关联合作伙伴模型。添加新文件openacademy/wizard.py
openacademy/__init__.py
from . import controllers
from . import models
from . import partner
from . import wizard
openacademy/wizard.py
# -*- 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")
启动向导
向导通过ir.actions.act_window
记录来启动,target
字段设置值为new
。后者将在一个弹出窗口中打开向导。操作可以有菜单项触发。还有另外一种方式来启动向导:使用类似于上面的ir.actions.act_window
记录,但有一个额外字段src_model
,指定那个模型的操作可用。该向导将出现在模型主视图的上下文操作中。因为这是在ORM中的内部钩子,所以这个操作通过在XML文件的act_window
标签中进行定义。
向导使用常规视图并且它的按钮可以使用special="cancel"
来关闭向导窗口而不需要保持。
练习启动向导
- 为向导定义一个form视图
- 在授课模型的上下文中添加action用于启动向导
- 给向导的session字段定义默认值;使用上下文参数
self._context
来获取当前授课
openacademy/wizard.py
class Wizard(models.TransientModel):
_name = 'openacademy.wizard'
def _default_session(self):
return self.env['openacademy.session'].browse(self._context.get('active_id'))
session_id = fields.Many2one('openacademy.session',
string="Session", required=True, default=_default_session)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
openacademy/views/openacademy.xml
parent="openacademy_menu"
action="session_list_action"/>
wizard.form
openacademy.wizard
练习注册与会者
给向导添加按钮,并且实现相应的方法,将与会者添加到给定的授课。
openacademy / views / openacademy.xml
openacademy/wizard.py
session_id = fields.Many2one('openacademy.session',
string="Session", required=True, default=_default_session)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
@api.multi
def subscribe(self):
self.session_id.attendee_ids |= self.attendee_ids
return {}
练习与会者注册多个授课
修改向导模型,以便与会者可以注册到多个授课
openacademy/views/openacademy.xml
openacademy/wizard.py
class Wizard(models.TransientModel):
_name = 'openacademy.wizard'
def _default_sessions(self):
return self.env['openacademy.session'].browse(self._context.get('active_ids'))
session_ids = fields.Many2many('openacademy.session',
string="Sessions", required=True, default=_default_sessions)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
@api.multi
def subscribe(self):
for session in self.session_ids:
session.attendee_ids |= self.attendee_ids
return {}
国际化
每个模块都可以在i18n目录提供自己的翻译,文件名的形式为LANG.po,其中LANG是语言的代码,或者是语言和国家的组合,例如:pt.po(葡萄牙语)和pt_BR.po(巴西葡萄牙语)。对于所有开启的语言,Odoo都会自动载入翻译。开发者总是使用英语建立模块,然后使用Odoo的文本POT导出功能导出模块术语(设置->翻译->导入/导出->导出翻译),生成模块的POT模板文件,然后到处PO翻译文件。许多IDE具有用于编辑和合并PO/POT文件的插件或功能。
提示
把Odoo生成的导出文件公布在Transifex,可以轻松的使用软件进行翻译。
|- idea/ # The module directory
|- i18n/ # Translation files
| - idea.pot # Translation Template (exported from Odoo)
| - fr.po # French translation
| - pt_BR.po # Brazilian Portuguese translation
| (...)
提示
默认情况下Odoo的POT导出仅提取XML文件的标签和Python代码中的字段定义,但是任何Python字符串都可以翻译,通过使用odoo._()
方法,例如_("Label")
。
练习
翻译一个模块
为已经安装的Odoo模块选择第二语言。使用Odoo提供的功能对模块进行翻译。
- 创建目录
openacademy/i18n/
- 安装任意一种你希望的语言 (设置->翻译->加载翻译 )
- 同步翻译术语(设置->翻译->应用程序术语->同步术语)
- 导出不指定语言的翻译模板文件(设置->翻译->导入/导出->导出翻译),保存在
openacademy/i18n/
- 导出指定语言的翻译文件(设置->翻译->导入/导出->导出翻译),保存在
openacademy/i18n/
- 打开导出的翻译文件(使用任意一款文本编辑软件或者专用的PO文件编辑器,例如POEdit然后翻译其中的术语)
- 在
models.py
文件中,为odoo._
方法添加一个导入声明,并且标记需要翻译的字符串 - 重复3-6的步骤
openacademy/models.py
# -*- coding: utf-8 -*-
from datetime import timedelta
from odoo import models, fields, api, exceptions, _
class Course(models.Model):
_name = 'openacademy.course'
default = dict(default or {})
copied_count = self.search_count(
[('name', '=like', _(u"Copy of {}%").format(self.name))])
if not copied_count:
new_name = _(u"Copy of {}").format(self.name)
else:
new_name = _(u"Copy of {} ({})").format(self.name, copied_count)
default['name'] = new_name
return super(Course, self).copy(default)
if self.seats < 0:
return {
'warning': {
'title': _("Incorrect 'seats' value"),
'message': _("The number of available seats may not be negative"),
},
}
if self.seats < len(self.attendee_ids):
return {
'warning': {
'title': _("Too many attendees"),
'message': _("Increase seats or remove excess attendees"),
},
}
def _check_instructor_not_in_attendees(self):
for r in self:
if r.instructor_id and r.instructor_id in r.attendee_ids:
raise exceptions.ValidationError(_("A session's instructor can't be an attendee"))