odoo里面怎么用py切换py notebookk下的2个page

拒绝访问 |
| 百度云加速
请打开cookies.
此网站 () 的管理员禁止了您的访问。原因是您的访问包含了非浏览器特征(39a214a607ec43a1-ua98).
重新安装浏览器,或使用别的浏览器Odoo9.0模块开发全流程
时间: 17:51:01
&&&& 阅读:45
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&
业务对象声明为Python类, 由Odoo自己主动加载.
XML或CSV文件格式, 在当中声明了元数据(视图或工作流)、配置数据(模块參数)、演示数据等.
处理Web浏览器发来的requests.
静态web数据
Web用到的图像, CSS或JavaScript文件.
一个Odoo模块也是一个Python模块, 存放在一个文件夹中, 包括一个__init__.py文件, 用于导入其它Python模块.
from . import mymodule
odoo.py提供了一个子命令scaffold能够方便地创建一个空的模块.
$ odoo.py scaffold &module name& &where to put it&
命令运行后, 将会创建一个子文件夹而且当中包含了Odoo模块所需的一些基本文件.
运行 ./odoo.py scaffold openacademy addons, 在addons文件夹下创建一个名为openacademy的模块, 生成的文件夹文件结构例如以下.
openacademy
├── __init__.py
├── __openerp__.py
├── controllers.py
├── demo.xml
├── models.py
├── security
└── ir.model.access.csv
└── templates.xml
各文件内容请查看文件或查看,
然后对__openerp__.py中的几种标识文本进行改动,
至少须要加入‘installable‘:True, ‘application‘:True。
ORM层是Odoo的一个关键组件, 它能够避免大部分的SQL语句编写从而提高扩展性和安全性.
业务对象用派生自Model的Python类(模型)来编写, 该类的_name属性定义了模型在Odoo系统中的名称.
from openerp import models
class MinimalModel(models.Model):
_name = ‘test.model‘
字段定义模型可以存储什么以及在哪里存储, 字段在模型类中用属性来定义.
from openerp import models, fields
class LessMinimalModel(models.Model):
_name = ‘test.model2‘
name = fields.Char()
与模型类似, 字段也能够通过參数传递对其进行设定:
name = field.Char(required=True)
字段的经常使用属性有:
string (unicode, default: field’s name)
字段标签名称,会显示在界面上(对用户可见)。
required (bool, default: False)
假设值为True,此字段值不能为空,设置默认值或者在创建记录时提供。
help (unicode, default: ‘’)
界面上显示提示语。
index (bool, default: False)
假设值为True,创建表时将为此列加入索引。
字段能够分为两类: 简单字段和关系字段. 前者为原子值, 直接保存在模型相应的数据库表中; 后者连接到其它的记录上(能够是同样的模型也能够是不同的模型).
Boolean, Date, Char这些都是简单字段.
Odoo在模型中自己主动创建并维护一些字段, 这些字段就是保留字段, 这些字段数据不须要也不应该手动去改动.
一条记录的唯一id。
create_date (Datetime)
记录创建时间。
create_uid (Many2one)
谁创建的记录。
write_date (Datetime)
最后改动时间。
write_uid (Many2one)
谁最后改动的记录。
默认情况下, Odoo要求模型中有一个name字段, 用于显示和搜索, 通过设置_rec_name也能够达到这种目的.
在openacademy模块中定义一个新的模型Course, openacademy/models.py内容例如以下:
from openerp import models, fields, api
class Course(models.Model):
_name = ‘openacademy.course‘
name = fields.Char(string=&Title&, required=True)
description = fields.Text()
Odoo是一个高度数据驱动的系统, 尽管使用Python代码来定制模块行为, 但非常多模块数据是在其加载时setup的, 而且有些模块只为Odoo加入数据.
通过数据文件来定义模块数据, 比如能够使用XML文件里的&record&元素定义数据, 每个&record&元素创建或者更新数据库中的一条记录, 形式例如以下:
model=&{model name}& id=&{record identifier}&&
name=&{a field name}&&{a value}&
Odoo模型名.
外部ID(External Identifier), 通过它能够引用到记录(而且不须要知道记录所在的数据库ID).
name属性用于确定字段名称(比如description), 该元素的body给出字段的值.
数据文件必须在模块加载清单文件列表中, 也就是__openerp__.py的’data’列表(所有加载)或’demo’列表(仅仅有设定为加载演示数据才会加载)中.
创建一个数据文件来向Course中加入数据, 编辑openacademy/demo.xml, 并确认__openerp__.py的’demo’列表中有该文件.
model=&openacademy.course& id=&course0&&
name=&name&&Course 0&
name=&description&&Course 0‘s description
Can have multiple lines
model=&openacademy.course& id=&course1&&
name=&name&&Course 1&
model=&openacademy.course& id=&course2&&
name=&name&&Course 2&
name=&description&&Course 2‘s description&
在Odoo中, 动作和菜单都是定义在数据库中的数据记录, 一般通过数据文件来定义.
动作能够由三种方式触发:
点击菜单项(菜单项链接到特定动作)点击视图上的button(假设button连接到动作)作为对象的上下文动作
使用&menuitem&声明一个ir.ui.menu并将其连接到一个action, 能够用以下的形式的代码.
&record model=&ir.actions.act_window& id=&action_list_ideas&&
&field name=&name&&Ideas&/field&
&field name=&res_model&&idea.idea&/field&
&field name=&view_mode&&tree,form&/field&
&menuitem id=&menu_ideas& parent=&menu_root& name=&Ideas& sequence=&10&
action=&action_list_ideas&/&
注意: action必须先于menu的连接使用定义, 数据文件在加载时顺序地运行, 所以动作的ID必须首先存在于数据库中才干使用.
定义一个新的菜单项訪问OpenAcademy课程.
创建openacademy/views/openacademy.xml文件, 并在当中加入动作和菜单.
&?xml version=&1.0& encoding=&UTF-8&?&
model=&ir.actions.act_window& id=&course_list_action&&
name=&name&&Courses&
name=&res_model&&openacademy.course&
name=&view_type&&form&
name=&view_mode&&tree,form&
name=&help& type=&html&&
class=&oe_view_nocontent_create&&Create the first course
id=&main_openacademy_menu& name=&Open Academy&/&
id=&openacademy_menu& name=&Open Academy&
parent=&main_openacademy_menu&/&
id=&courses_menu& name=&Courses& parent=&openacademy_menu&
action=&course_list_action&/&
在__openerp__.py中加入这个数据文件名称到’data’.
‘data‘: [
# ‘security/ir.model.access.csv‘,
‘templates.xml‘,
‘views/openacademy.xml‘,
更新模块后能够看到菜单, 操作看看效果.
视图定义了模型数据怎样显示, 每种类型的视图代表一种数据可视化模式.
一个视图是以一条ir.ui.view模型数据的形式定义的.
model=&ir.ui.view& id=&view_id&&
name=&name&&view.name&
name=&model&&object_name&
name=&priority& eval=&16&/&
name=&arch& type=&xml&&
Tree view也被称为list views, 在一个表格中显示记录. 根元素是&tree&, 最简形式的tree view仅仅是简单地列出每条记录的多个字段, 每一个字段为一列.
string=&Idea list&&
name=&name&/&
name=&inventor_id&/&
Form用于创建或编辑单条记录, 根元素是&form&, 能够在form中组合各种高层结构元素(如groups, notebooks)以及交互元素(如buttons, fields).
string=&Idea form&&
colspan=&4&&
colspan=&2& col=&2&&
string=&General stuff& colspan=&2&/&
name=&name&/&
name=&inventor_id&/&
colspan=&2& col=&2&&
string=&Dates& colspan=&2&/&
name=&active&/&
name=&invent_date& readonly=&1&/&
colspan=&4&&
string=&Description&&
name=&description& nolabel=&1&/&
name=&state&/&
为openacademy创建form view, views/openacademy.xml数据文件里添加&record model=”ir.ui.view”…&内容.
&?xml version=&1.0& encoding=&UTF-8&?&
model=&ir.ui.view& id=&course_form_view&&
name=&name&&course.form&
name=&model&&openacademy.course&
name=&arch& type=&xml&&
string=&Course Form&&
name=&name&/&
name=&description&/&
更新模块, 创建一个Course, 能够看到form view变了.
使用notebook. 在form view中, 将description字段放在一个tab中, 方便随后加入其它tabs, 对练习#5的form view数据做例如以下改动.
name=&name&/&
string=&Description&&
name=&description&/&
string=&About&&
This is an example of notebooks
更新模块, 看效果.
还能够使用HTML为form view提供更加灵活的布局, 比如以下的样例.
&form string=&Idea Form&&
&button string=&Confirm& type=&object& name=&action_confirm&
states=&draft& class=&oe_highlight& /&
&button string=&Mark as done& type=&object& name=&action_done&
states=&confirmed& class=&oe_highlight&/&
&button string=&Reset to draft& type=&object& name=&action_draft&
states=&confirmed,done& /&
&field name=&state& widget=&statusbar&/&
&div class=&oe_title&&
&label for=&name& class=&oe_edit_only& string=&Idea Name& /&
&h1&&field name=&name& /&&/h1&
&separator string=&General& colspan=&2& /&
&group colspan=&2& col=&2&&
&field name=&description& placeholder=&Idea description...& /&
Search views用来自己定义list views及其他统计/多条记录视图中的搜索字段. 根元素为&search&, 其子元素定义了在哪些字段上进行搜索.
name=&name&/&
name=&inventor_id&/&
假设一个模型未定义相应的Search view, odoo自己主动创建一个仅搜索name字段的search view.
加入title以及description搜索, 在views/openacademy.xml中定义search view.
model=&ir.ui.view& id=&course_search_view&&
name=&name&&course.search&
name=&model&&openacademy.course&
name=&arch& type=&xml&&
name=&name&/&
name=&description&/&
更新模块, 搜索框输入字符后可以看到下方可以选择搜索description字段.
一个模型中的记录可能关联到其它模型的记录, 比如销售订单记录会关联到一个包括客户信息的客户记录.
为了说明数据关联, 首先添加新的模型.
Open Academy模块中, 一个session是一个在特定时间针对特定听众讲授课程的过程. 须要为session创建对应的模型.
session具有name, 開始日期, 持续时间以及座位数量等. 此外还须要加入对应的action和menuitem显示模型数据.
首先在openacademy/models.py中创建Session类.
class Session(models.Model):
_name = ‘openacademy.session‘
name = fields.Char(required=True)
start_date = fields.Date()
duration = fields.Float(digits=(6, 2), help=&Duration in days&)
seats = fields.Integer(string=&Number of seats&)
然后在openacademy/view/openacademy.xml中加入用于訪问session模型的action和menuitem定义.
model=&ir.ui.view& id=&session_form_view&&
name=&name&&session.form&
name=&model&&openacademy.session&
name=&arch& type=&xml&&
string=&Session Form&&
name=&name&/&
name=&start_date&/&
name=&duration&/&
name=&seats&/&
model=&ir.actions.act_window& id=&session_list_action&&
name=&name&&Sessions&
name=&res_model&&openacademy.session&
name=&view_type&&form&
name=&view_mode&&tree,form&
id=&session_menu& name=&Sessions&
parent=&openacademy_menu&
action=&session_list_action&/&
digits=(6,2)确定浮点数的精度, 6表示总的数字位数(不包含小数点), 2表示小数点后的位数. 所以, digits=(6,2)小数点前最多4位.
关联字段指向某些记录。或者是同样的model(模型),或者是不同的model(模型)。
关联字段类型:
使用many2one改动Course和Session模型(model),反映出与其它模型(model)的关联:每一个Course有一个负责人。other_model值为res.users&每一个Session有一个老师。other_model值为res.partner&一个Session关联一个Course,other_model值为openacademy.course,必填调整view。
1. 加入相关字段Many2One到model
2. 加入到view
openacademy/models.py
name = fields.Char(string=&Title&, required=True)
description = fields.Text()
responsible_id = fields.Many2one(‘res.users‘,
ondelete=‘set null‘, string=&Responsible&, index=True)
class Session(models.Model):
_name = ‘openacademy.session‘
start_date = fields.Date()
duration = fields.Float(digits=(6, 2), help=&Duration in days&)
seats = fields.Integer(string=&Number of seats&)
instructor_id = fields.Many2one(‘res.partner‘, string=&Instructor&)
course_id = fields.Many2one(‘openacademy.course‘,
ondelete=‘cascade‘, string=&Course&, required=True)
openacademy/views/openacademy.xml
&field name=&name&/&
&field name=&responsible_id&/&
&notebook&
&page string=&Description&&
&!-- override the automatically generated list view for courses --&
&record model=&ir.ui.view& id=&course_tree_view&&
&field name=&name&&course.tree&/field&
&field name=&model&&openacademy.course&/field&
&field name=&arch& type=&xml&&
&tree string=&Course Tree&&
&field name=&name&/&
&field name=&responsible_id&/&
&!-- window action --&
The following tag is an action definition for a &window action&,
&form string=&Session Form&&
&group string=&General&&
&field name=&course_id&/&
&field name=&name&/&
&field name=&instructor_id&/&
&group string=&Schedule&&
&field name=&start_date&/&
&field name=&duration&/&
&field name=&seats&/&
&!-- session tree/list view --&
&record model=&ir.ui.view& id=&session_tree_view&&
&field name=&name&&session.tree&/field&
&field name=&model&&openacademy.session&/field&
&field name=&arch& type=&xml&&
&tree string=&Session Tree&&
&field name=&name&/&
&field name=&course_id&/&
&record model=&ir.actions.act_window& id=&session_list_action&&
&field name=&name&&Sessions&/field&
&field name=&res_model&&openacademy.session&/field&
Inverse one2many relations
Using the inverse relational field one2many, modify the models to reflect the relation between courses and sessions.
Modify the&Course&class,
andadd the field in the course form view.
openacademy/models.py
responsible_id = fields.Many2one(‘res.users‘,
ondelete=‘set null‘, string=&Responsible&, index=True)
session_ids = fields.One2many(
‘openacademy.session‘, ‘course_id‘, string=&Sessions&)
class Session(models.Model):
openacademy/views/openacademy.xml
&page string=&Description&&
&field name=&description&/&
&page string=&Sessions&&
&field name=&session_ids&&
&tree string=&Registered sessions&&
&field name=&name&/&
&field name=&instructor_id&/&
&/notebook&
Multiple many2many relations
Using the relational field many2many, modify the&Session&model to relate every session to a set of&attendees. Attendees will be represented
by partner records, so we will relate to the built-in model&res.partner.
Adapt the views accordingly.
Modify the&Session&class,
andadd the field in the form view.
openacademy/models.py
instructor_id = fields.Many2one(‘res.partner‘, string=&Instructor&)
course_id = fields.Many2one(‘openacademy.course‘,
ondelete=‘cascade‘, string=&Course&, required=True)
attendee_ids = fields.Many2many(‘res.partner‘, string=&Attendees&)
openacademy/views/openacademy.xml
&field name=&seats&/&
&label for=&attendee_ids&/&
&field name=&attendee_ids&/&
Inheritance
Model inheritance
Odoo provides two&inheritance&mechanisms to extend an existing model in a modular way.
The first inheritance mechanism allows a module to modify the behavior of a model defined in another module:
add fields to a model,override the definition of fields on a model,add constraints to a model,add methods to a model,override existing methods on a model.
The second inheritance mechanism (delegation) allows to link every record of a model to a record in a parent model, and provides transparent access to the fields of the parent record.
View inheritance
Instead of modifying existing views in place (by overwriting them), Odoo provides view inheritance where children &extension& views are applied on top of root views, and can add or remove content from their parent.
An extension view references its parent using the&inherit_id&field,
and instead of a single view its&arch&field is composed
of any number of&xpath&elements selecting and altering
the content of their parent view:
&!-- improved idea categories list --&
&record id=&idea_category_list2& model=&ir.ui.view&&
&field name=&name&&id.category.list2&/field&
&field name=&model&&idea.category&/field&
&field name=&inherit_id& ref=&id_category_list&/&
&field name=&arch& type=&xml&&
&!-- find field description and add the field
idea_ids after it --&
&xpath expr=&//field[@name=‘description‘]& position=&after&&
&field name=&idea_ids& string=&Number of ideas&/&
exprAn&&expression selecting
a single element in the parent view. Raises an error if it matches no element or more than oneposition
Operation to apply to the matched element:
insideappends&xpath‘s
body at the end of the matched elementreplacereplaces the matched element by the&xpath‘s
bodybeforeinserts the&xpath‘s
body as a sibling before the matched elementafterinserts the&xpaths‘s
body as a sibling after the matched elementattributesalters the attributes of the matched element using special&attribute&elements
in the&xpath‘s body
Alter existing content
Using model inheritance, modify the existing&Partner&model to add an&instructor&boolean
field, and a many2many field that corresponds to the session-partner relationUsing view inheritance, display this fields in the partner form view
Create a file&openacademy/partner.py&and
import it in&__init__.pyCreate a file&openacademy/views/partner.xml&and
add it to&__openerp__.py
openacademy/__init__.py
# -*- coding: utf-8 -*-
from . import controllers
from . import models
from . import partner
openacademy/__openerp__.py
# ‘security/ir.model.access.csv‘,
‘templates.xml‘,
‘views/openacademy.xml‘,
‘views/partner.xml‘,
# only loaded in demonstration mode
‘demo‘: [
openacademy/partner.py
# -*- coding: utf-8 -*-
from openerp import fields, models
class Partner(models.Model):
_inherit = ‘res.partner‘
# Add a new column to the res.partner model, by default partners are not
# instructors
instructor = fields.Boolean(&Instructor&, default=False)
session_ids = fields.Many2many(‘openacademy.session‘,
string=&Attended Sessions&, readonly=True)
openacademy/views/partner.xml
&?xml version=&1.0& encoding=&UTF-8&?&
&!-- Add instructor field to existing view --&
&record model=&ir.ui.view& id=&partner_instructor_form_view&&
&field name=&name&&partner.instructor&/field&
&field name=&model&&res.partner&/field&
&field name=&inherit_id& ref=&base.view_partner_form&/&
&field name=&arch& type=&xml&&
&notebook position=&inside&&
&page string=&Sessions&&
&field name=&instructor&/&
&field name=&session_ids&/&
&/notebook&
&record model=&ir.actions.act_window& id=&contact_list_action&&
&field name=&name&&Contacts&/field&
&field name=&res_model&&res.partner&/field&
&field name=&view_mode&&tree,form&/field&
&menuitem id=&configuration_menu& name=&Configuration&
parent=&main_openacademy_menu&/&
&menuitem id=&contact_menu& name=&Contacts&
parent=&configuration_menu&
action=&contact_list_action&/&
&/openerp&
In Odoo,&&are
values that encode conditions on records. A domain is a list of criteria used to select a subset of a model‘s records. Each criteria is a triple with a field name, an operator and a value.
For instance, when used on the&Product&model the following domain selects all&services&with a unit price over&1000:
[(‘product_type‘, ‘=‘, ‘service‘), (‘unit_price‘, ‘&‘, 1000)]
By default criteria are combined with an implicit AND. The logical operators&&&(AND),&|&(OR)
and&!&(NOT) can be used to explicitly combine criteria.
They are used in prefix position (the operator is inserted before its arguments rather than between). For instance to select products &which are services&OR&have a unit price which is&NOT&between
1000 and 2000&:
(‘product_type‘, ‘=‘, ‘service‘),
‘!‘, ‘&‘,
(‘unit_price‘, ‘&=‘, 1000),
(‘unit_price‘, ‘&‘, 2000)]
A&domain&parameter
can be added to relational fields to limit valid records for the relation when trying to select records in the client interface.
Domains on relational fields
When selecting the instructor for a&Session, only instructors (partners with&instructor&set
to&True) should be visible.
openacademy/models.py
duration = fields.Float(digits=(6, 2), help=&Duration in days&)
seats = fields.Integer(string=&Number of seats&)
instructor_id = fields.Many2one(‘res.partner‘, string=&Instructor&,
domain=[(‘instructor‘, ‘=‘, True)])
course_id = fields.Many2one(‘openacademy.course‘,
ondelete=‘cascade‘, string=&Course&, required=True)
attendee_ids = fields.Many2many(‘res.partner‘, string=&Attendees&)
More complex domains
Create new partner categories&Teacher / Level 1&and&Teacher / Level 2. The instructor for a session can be either an instructor or a teacher
(of any level).
Modify the&Session&model‘s domainModify&openacademy/view/partner.xml&to
get access to&Partner categories:
openacademy/models.py
seats = fields.Integer(string=&Number of seats&)
instructor_id = fields.Many2one(‘res.partner‘, string=&Instructor&,
domain=[‘|‘, (‘instructor‘, ‘=‘, True),
(‘category_id.name‘, ‘ilike‘, &Teacher&)])
course_id = fields.Many2one(‘openacademy.course‘,
ondelete=‘cascade‘, string=&Course&, required=True)
attendee_ids = fields.Many2many(‘res.partner‘, string=&Attendees&)
openacademy/views/partner.xml
&menuitem id=&contact_menu& name=&Contacts&
parent=&configuration_menu&
action=&contact_list_action&/&
&record model=&ir.actions.act_window& id=&contact_cat_list_action&&
&field name=&name&&Contact Tags&/field&
&field name=&res_model&&res.partner.category&/field&
&field name=&view_mode&&tree,form&/field&
&menuitem id=&contact_cat_menu& name=&Contact Tags&
parent=&configuration_menu&
action=&contact_cat_list_action&/&
&record model=&res.partner.category& id=&teacher1&&
&field name=&name&&Teacher / Level 1&/field&
&record model=&res.partner.category& id=&teacher2&&
&field name=&name&&Teacher / Level 2&/field&
&/openerp&
Computed fields and default values
So far fields have been stored directly in and retrieved directly from the database. Fields can also be&computed. In that case, the field‘s value is not retrieved from the database
but computed on-the-fly by calling a method of the model.
To create a computed field, create a field and set its attribute&compute&to
the name of a method. The computation method should simply set the value of the field to compute on every record in&self.
a collection
The object&self&is
a&recordset, i.e., an ordered collection of records. It supports the standard Python operations on collections, like&len(self)&and&iter(self),
plus extra set operations like&recs1 + recs2.
Iterating over&self&gives
the records one by one, where each record is itself a collection of size 1. You can access/assign fields on single records by using the dot notation, like&record.name.
import random
from openerp import models, fields, api
class ComputedModel(models.Model):
_name = ‘puted‘
name = fields.Char(compute=‘_compute_name‘)
@api.multi
def _compute_name(self):
for record in self:
record.name = str(random.randint(1, 1e6))
Dependencies
The value of a computed field usually depends on the values of other fields on the computed record. The ORM expects the developer to specify those dependencies on the compute method with the decorator&.
The given dependencies are used by the ORM to trigger the recomputation of the field whenever some of its dependencies have been modified:
from openerp import models, fields, api
class ComputedModel(models.Model):
_name = ‘puted‘
name = fields.Char(compute=‘_compute_name‘)
value = fields.Integer()
@api.depends(‘value‘)
def _compute_name(self):
for record in self:
record.name = &Record with value %s& % record.value
Computed fields
Add the percentage of taken seats to the&Session&modelDisplay that field in the tree and form viewsDisplay the field as a progress bar
Add a computed field to&SessionShow the field in the&Session&view:
openacademy/models.py
course_id = fields.Many2one(‘openacademy.course‘,
ondelete=‘cascade‘, string=&Course&, required=True)
attendee_ids = fields.Many2many(‘res.partner‘, string=&Attendees&)
taken_seats = fields.Float(string=&Taken seats&, compute=‘_taken_seats‘)
@api.depends(‘seats‘, ‘attendee_ids‘)
def _taken_seats(self):
for r in self:
if not r.seats:
r.taken_seats = 0.0
r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
openacademy/views/openacademy.xml
&field name=&start_date&/&
&field name=&duration&/&
&field name=&seats&/&
&field name=&taken_seats& widget=&progressbar&/&
&label for=&attendee_ids&/&
&tree string=&Session Tree&&
&field name=&name&/&
&field name=&course_id&/&
&field name=&taken_seats& widget=&progressbar&/&
Default values
Any field can be given a default value. In the field definition, add the option&default=X&where&X&is
either a Python literal value (boolean, integer, float, string), or a function taking a recordset and returning a value:
name = fields.Char(default=&Unknown&)
user_id = fields.Many2one(‘res.users‘, default=lambda self: self.env.user)
Active objects – Default values
Define the start_date default value as today (see&).Add a field&active&in the
class Session, and set sessions as active by default.
openacademy/models.py
_name = ‘openacademy.session‘
name = fields.Char(required=True)
start_date = fields.Date(default=fields.Date.today)
duration = fields.Float(digits=(6, 2), help=&Duration in days&)
seats = fields.Integer(string=&Number of seats&)
active = fields.Boolean(default=True)
instructor_id = fields.Many2one(‘res.partner‘, string=&Instructor&,
domain=[‘|‘, (‘instructor‘, ‘=‘, True),
openacademy/views/openacademy.xml
&field name=&course_id&/&
&field name=&name&/&
&field name=&instructor_id&/&
&field name=&active&/&
&group string=&Schedule&&
&field name=&start_date&/&
The &onchange& mechanism provides a way for the client interface to update a form whenever the user has filled in a value in a field, without saving anything to the database.
For instance, suppose a model has three fields&amount,&unit_price&and&price,
and you want to update the price on the form when any of the other fields is modified. To achieve this, define a method where&self&represents
the record in the form view, and decorate it with&&to
specify on which field it has to be triggered. Any change you make on&self&will
be reflected on the form.
&!-- content of form view --&
&field name=&amount&/&
&field name=&unit_price&/&
&field name=&price& readonly=&1&/&
# onchange handler
@api.onchange(‘amount‘, ‘unit_price‘)
def _onchange_price(self):
# set auto-changing field
self.price = self.amount * self.unit_price
# Can optionally return a warning and domains
‘warning‘: {
‘title‘: &Something bad happened&,
‘message‘: &It was very bad indeed&,
For computed fields, valued&onchange&behavior
is built-in as can be seen by playing with the&Session&form: change the number of seats or participants, and the&taken_seats&progressbar
is automatically updated.
Add an explicit onchange to warn about invalid values, like a negative number of seats, or more participants than seats.
openacademy/models.py
r.taken_seats = 0.0
r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
@api.onchange(‘seats‘, ‘attendee_ids‘)
def _verify_valid_seats(self):
if self.seats & 0:
‘warning‘: {
‘title‘: &Incorrect ‘seats‘ value&,
‘message‘: &The number of available seats may not be negative&,
if self.seats & len(self.attendee_ids):
‘warning‘: {
‘title‘: &Too many attendees&,
‘message‘: &Increase seats or remove excess attendees&,
Model constraints
Odoo provides two ways to set up automatically verified invariants:&&and&.
A Python constraint is defined as a method decorated with&,
and invoked on a recordset. The decorator specifies which fields are involved in the constraint, so that the constraint is automatically evaluated when one of them is modified. The method is expected to raise an exception if its invariant is not satisfied:
from openerp.exceptions import ValidationError
@api.constrains(‘age‘)
def _check_something(self):
for record in self:
if record.age & 20:
raise ValidationError(&Your record is too old: %s& % record.age)
# all records passed the test, don‘t return anything
Add Python constraints
Add a constraint that checks that the instructor is not present in the attendees of his/her own session.
openacademy/models.py
# -*- coding: utf-8 -*-
from openerp import models, fields, api, exceptions
class Course(models.Model):
_name = ‘openacademy.course‘
‘message‘: &Increase seats or remove excess attendees&,
@api.constrains(‘instructor_id‘, ‘attendee_ids‘)
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&)
SQL constraints are defined through the model attribute&.
The latter is assigned to a list of triples of strings(name,
sql_definition, message), where&name&is a valid
SQL constraint name,&sql_definition&is a&expression,
and&message&is the error message.
Add SQL constraints
With the help of&&, add the following constraints:
CHECK that the course description and the course title are differentMake the Course‘s name UNIQUE
openacademy/models.py
session_ids = fields.One2many(
‘openacademy.session‘, ‘course_id‘, string=&Sessions&)
_sql_constraints = [
(‘name_description_check‘,
‘CHECK(name != description)‘,
&The title of the course should not be the description&),
(‘name_unique‘,
‘UNIQUE(name)‘,
&The course title must be unique&),
class Session(models.Model):
_name = ‘openacademy.session‘
Exercise 6 - Add a duplicate option
Since we added a constraint for the Course name uniqueness, it is not possible to use the &duplicate& function anymore (Form ? Duplicate).
Re-implement your own &copy& method which allows to duplicate the Course object, changing the original name into &Copy of [original name]&.
openacademy/models.py
session_ids = fields.One2many(
‘openacademy.session‘, ‘course_id‘, string=&Sessions&)
@api.multi
def copy(self, default=None):
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)
new_name = u&Copy of {} ({})&.format(self.name, copied_count)
default[‘name‘] = new_name
return super(Course, self).copy(default)
_sql_constraints = [
(‘name_description_check‘,
‘CHECK(name != description)‘,
Advanced Views
Tree views
Tree views can take supplementary attributes to further customize their behavior:
decoration-{$name}
allow changing the style of a row‘s text based on the corresponding record‘s attributes.
Values are Python expressions. For each record, the expression is evaluated with the record‘s attributes as context values and if&true,
the corresponding style is applied to the row. Other context values are&uid&(the
id of the current user) andcurrent_date&(the current
date as a string of the form&yyyy-MM-dd).
{$name}&can
be&bf&(font-weight:
bold),&it&(font-style:
italic), or any&&(danger,&info,muted,&primary,&success&or&warning).
&tree string=&Idea Categories& decoration-info=&state==‘draft‘&
decoration-danger=&state==‘trashed‘&&
&field name=&name&/&
&field name=&state&/&
editableEither&&top&&or&&bottom&.
Makes the tree view editable in-place (rather than having to go through the form view), the value is the position where new rows appear.
List coloring
Modify the Session tree view in such a way that sessions lasting less than 5 days are colored blue, and the ones lasting more than 15 days are colored red.
Modify the session tree view:
openacademy/views/openacademy.xml
&field name=&name&&session.tree&/field&
&field name=&model&&openacademy.session&/field&
&field name=&arch& type=&xml&&
&tree string=&Session Tree& decoration-info=&duration&5& decoration-danger=&duration&15&&
&field name=&name&/&
&field name=&course_id&/&
&field name=&duration& invisible=&1&/&
&field name=&taken_seats& widget=&progressbar&/&
Displays records as calendar events. Their root element is&&calendar&&and
their most common attributes are:
colorThe name of the field used for&color segmentation. Colors are automatically distributed to events, but events in the same color segment (records which have the same value for
their&@color&field) will be given the same color.date_startrecord‘s field holding the start date/time for the eventdate_stop&(optional)record‘s field holding the end date/time for the event
field (to define the label for each calendar event)
&calendar string=&Ideas& date_start=&invent_date& color=&inventor_id&&
&field name=&name&/&
&/calendar&
Calendar view
Add a Calendar view to the&Session&model enabling the user to view the events associated to the Open Academy.
Add an&end_date&field
computed from&start_date&and&duration
Add a calendar view to the&Session&modelAnd add the calendar view to the&Session&model‘s actions
openacademy/models.py
# -*- coding: utf-8 -*-
from datetime import timedelta
from openerp import models, fields, api, exceptions
class Course(models.Model):
attendee_ids = fields.Many2many(‘res.partner‘, string=&Attendees&)
taken_seats = fields.Float(string=&Taken seats&, compute=‘_taken_seats‘)
end_date = fields.Date(string=&End Date&, store=True,
compute=‘_get_end_date‘, inverse=‘_set_end_date‘)
@api.depends(‘seats‘, ‘attendee_ids‘)
def _taken_seats(self):
@api.depends(‘start_date‘, ‘duration‘)
def _get_end_date(self):
for r in self:
if not (r.start_date and r.duration):
r.end_date = r.start_date
# Add duration to start_date, but: Monday + 5 days = Saturday, so
# subtract one second to get on Friday instead
start = fields.Datetime.from_string(r.start_date)
duration = timedelta(days=r.duration, seconds=-1)
r.end_date = start + duration
def _set_end_date(self):
for r in self:
if not (r.start_date and r.end_date):
# Compute the difference between dates, but: Friday - Monday = 4 days,
# so add one day to get 5 days instead
start_date = fields.Datetime.from_string(r.start_date)
end_date = fields.Datetime.from_string(r.end_date)
r.duration = (end_date - start_date).days + 1
@api.constrains(‘instructor_id‘, ‘attendee_ids‘)
def _check_instructor_not_in_attendees(self):
for r in self:
openacademy/views/openacademy.xml
&!-- calendar view --&
&record model=&ir.ui.view& id=&session_calendar_view&&
&field name=&name&&session.calendar&/field&
&field name=&model&&openacademy.session&/field&
&field name=&arch& type=&xml&&
&calendar string=&Session Calendar& date_start=&start_date&
date_stop=&end_date&
color=&instructor_id&&
&field name=&name&/&
&/calendar&
&record model=&ir.actions.act_window& id=&session_list_action&&
&field name=&name&&Sessions&/field&
&field name=&res_model&&openacademy.session&/field&
&field name=&view_type&&form&/field&
&field name=&view_mode&&tree,form,calendar&/field&
&menuitem id=&session_menu& name=&Sessions&
Search views
Search view&&field&&elements
can have a&@filter_domain&that overrides the domain generated
for searching on the given field. In the given domain,&self&represents
the value entered by the user. In the example below, it is used to search on both fields&nameand&description.
Search views can also contain&&filter&&elements,
which act as toggles for predefined searches. Filters must have one of the following attributes:
domainadd the given domain to the current searchcontextadd some context t use the key&group_by&to
group results on the given field name
&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‘}&/&
To use a non-default search view in an action, it should be linked using the&search_view_id&field
of the action record.
The action can also set default values for search fields through its&context&field:
context keys of the formsearch_default_field_name&will
initialize&field_name&with the provided value. Search filters must have an optional&@name&to
have a default and behave as booleans (they can only be enabled by default).
Search views
Add a button to filter the courses for which the current user is the responsible in the course search view. Make it selected by default.Add a button to group courses by responsible user.
openacademy/views/openacademy.xml
&field name=&name&/&
&field name=&description&/&
&filter name=&my_courses& string=&My Courses&
domain=&[(‘responsible_id‘, ‘=‘, uid)]&/&
&group string=&Group By&&
&filter name=&by_responsible& string=&Responsible&
context=&{‘group_by‘: ‘responsible_id‘}&/&
&field name=&res_model&&openacademy.course&/field&
&field name=&view_type&&form&/field&
&field name=&view_mode&&tree,form&/field&
&field name=&context& eval=&{‘search_default_my_courses‘: 1}&/&
&field name=&help& type=&html&&
&p class=&oe_view_nocontent_create&&Create the first course
Horizontal bar charts typically used to show project planning and advancement, their root element is&&gantt&.
&gantt string=&Ideas&
date_start=&invent_date&
date_stop=&date_finished&
progress=&progress&
default_group_by=&inventor_id& /&
Gantt charts
Add a Gantt Chart enabling the user to view the sessions scheduling linked to the Open Academy module. The sessions should be grouped by instructor.
Create a computed field expressing the session‘s duration in hoursAdd the gantt view‘s definition, and add the gantt view to the&Session&model‘s action
openacademy/models.py
end_date = fields.Date(string=&End Date&, store=True,
compute=‘_get_end_date‘, inverse=‘_set_end_date‘)
hours = fields.Float(string=&Duration in hours&,
compute=‘_get_hours‘, inverse=‘_set_hours‘)
@api.depends(‘seats‘, ‘attendee_ids‘)
def _taken_seats(self):
for r in self:
end_date = fields.Datetime.from_string(r.end_date)
r.duration = (end_date - start_date).days + 1
@api.depends(‘duration‘)
def _get_hours(self):
for r in self:
r.hours = r.duration * 24
def _set_hours(self):
for r in self:
r.duration = r.hours / 24
@api.constrains(‘instructor_id‘, ‘attendee_ids‘)
def _check_instructor_not_in_attendees(self):
for r in self:
openacademy/views/openacademy.xml
&record model=&ir.ui.view& id=&session_gantt_view&&
&field name=&name&&session.gantt&/field&
&field name=&model&&openacademy.session&/field&
&field name=&arch& type=&xml&&
&gantt string=&Session Gantt& color=&course_id&
date_start=&start_date& date_delay=&hours&
default_group_by=‘instructor_id‘&
&field name=&name&/&
&record model=&ir.actions.act_window& id=&session_list_action&&
&field name=&name&&Sessions&/field&
&field name=&res_model&&openacademy.session&/field&
&field name=&view_type&&form&/field&
&field name=&view_mode&&tree,form,calendar,gantt&/field&
&menuitem id=&session_menu& name=&Sessions&
Graph views
Graph views allow aggregated overview and analysis of models, their root element is&&graph&.
Graph views have 4 display modes, the default mode is selected using the&@type&attribute.
Bar (default)
a bar chart, the first dimension is used to define groups on the horizontal axis, other dimensions define aggregated bars within each group.
By default bars are side-by-side, they can be stacked by using&@stacked=&True&&on
the&&graph&
Line2-dimensional line chartPie2-dimensional pie
Graph views contain&&field&&with
a mandatory&@type&attribute taking the values:
row&(default)the field should be aggregated by defaultmeasurethe field should be aggregated rather than grouped on
&graph string=&Total idea score by Inventor&&
&field name=&inventor_id&/&
&field name=&score& type=&measure&/&
Graph views perform aggregations on database values, they do not work with non-stored computed fields.
Graph view
Add a Graph view in the Session object that displays, for each course, the number of attendees under the form of a bar chart.
Add the number of attendees as a stored computed fieldThen add the relevant view
openacademy/models.py
hours = fields.Float(string=&Duration in hours&,
compute=‘_get_hours‘, inverse=‘_set_hours‘)
attendees_count = fields.Integer(
string=&Attendees count&, compute=‘_get_attendees_count‘, store=True)
@api.depends(‘seats‘, ‘attendee_ids‘)
def _taken_seats(self):
for r in self:
for r in self:
r.duration = r.hours / 24
@api.depends(‘attendee_ids‘)
def _get_attendees_count(self):
for r in self:
r.attendees_count = len(r.attendee_ids)
@api.constrains(‘instructor_id‘, ‘attendee_ids‘)
def _check_instructor_not_in_attendees(self):
for r in self:
openacademy/views/openacademy.xml
&record model=&ir.ui.view& id=&openacademy_session_graph_view&&
&field name=&name&&openacademy.session.graph&/field&
&field name=&model&&openacademy.session&/field&
&field name=&arch& type=&xml&&
&graph string=&Participations by Courses&&
&field name=&course_id&/&
&field name=&attendees_count& type=&measure&/&
&record model=&ir.actions.act_window& id=&session_list_action&&
&field name=&name&&Sessions&/field&
&field name=&res_model&&openacademy.session&/field&
&field name=&view_type&&form&/field&
&field name=&view_mode&&tree,form,calendar,gantt,graph&/field&
&menuitem id=&session_menu& name=&Sessions&
Used to organize tasks, production processes, etc… their root element is&&kanban&.
A kanban view shows a set of cards possibly grouped in columns. Each card represents a record, and each column the values of an aggregation field.
For instance, project tasks may be organized by stage (each column is a stage), or by responsible (each column is a user), and so on.
Kanban views define the structure of each card as a mix of form elements (including basic HTML) and&.
Kanban view
Add a Kanban view that displays sessions grouped by course (columns are thus courses).
Add an integer&color&field
to the&Session&modelAdd the kanban view and update the action
openacademy/models.py
duration = fields.Float(digits=(6, 2), help=&Duration in days&)
seats = fields.Integer(string=&Number of seats&)
active = fields.Boolean(default=True)
color = fields.Integer()
instructor_id = fields.Many2one(‘res.partner‘, string=&Instructor&,
domain=[‘|‘, (‘instructor‘, ‘=‘, True),
openacademy/views/openacademy.xml
&record model=&ir.ui.view& id=&view_openacad_session_kanban&&
&field name=&name&&openacad.session.kanban&/field&
&field name=&model&&openacademy.session&/field&
&field name=&arch& type=&xml&&
&kanban default_group_by=&course_id&&
&field name=&color&/&
&templates&
&t t-name=&kanban-box&&
t-attf-class=&oe_kanban_color_{{kanban_getcolor(record.color.raw_value)}}
oe_kanban_global_click_edit oe_semantic_html_override
oe_kanban_card {{record.group_fancy==1 ? ‘oe_kanban_card_fancy‘ : ‘‘}}&&
&div class=&oe_dropdown_kanban&&
&!-- dropdown menu --&
&div class=&oe_dropdown_toggle&&
&i class=&fa fa-bars fa-lg&/&
&ul class=&oe_dropdown_menu&&
&a type=&delete&&Delete&/a&
&ul class=&oe_kanban_colorpicker&
data-field=&color&/&
&div class=&oe_clear&&&/div&
&div t-attf-class=&oe_kanban_content&&
&!-- title --&
Session name:
&field name=&name&/&
Start date:
&field name=&start_date&/&
&field name=&duration&/&
&/templates&
&record model=&ir.actions.act_window& id=&session_list_action&&
&field name=&name&&Sessions&/field&
&field name=&res_model&&openacademy.session&/field&
&field name=&view_type&&form&/field&
&field name=&view_mode&&tree,form,calendar,gantt,graph,kanban&/field&
&menuitem id=&session_menu& name=&Sessions&
parent=&openacademy_menu&
Workflows are models associated to business objects describing their dynamics. Workflows are also used to track processes that evolve over time.
Almost a workflow
Add a&state&field
to the&Session&model. It will be used to define a workflow-ish.
A sesion can have three possible states: Draft (default), Confirmed and Done.
In the session form, add a (read-only) field to visualize the state, and buttons to change it. The valid transitions are:
Draft -& ConfirmedConfirmed -& DraftConfirmed -& DoneDone -& Draft
Add a new&state&fieldAdd state-transitioning methods, those can be called from view buttons to change the record‘s stateAnd add the relevant buttons to the session‘s form view
openacademy/models.py
attendees_count = fields.Integer(
string=&Attendees count&, compute=‘_get_attendees_count‘, store=True)
state = fields.Selection([
(‘draft‘, &Draft&),
(‘confirmed‘, &Confirmed&),
(‘done‘, &Done&),
], default=‘draft‘)
@api.multi
def action_draft(self):
self.state = ‘draft‘
@api.multi
def action_confirm(self):
self.state = ‘confirmed‘
@api.multi
def action_done(self):
self.state = ‘done‘
@api.depends(‘seats‘, ‘attendee_ids‘)
def _taken_seats(self):
for r in self:
openacademy/views/openacademy.xml
&field name=&model&&openacademy.session&/field&
&field name=&arch& type=&xml&&
&form string=&Session Form&&
&button name=&action_draft& type=&object&
string=&Reset to draft&
states=&confirmed,done&/&
&button name=&action_confirm& type=&object&
string=&Confirm& states=&draft&
class=&oe_highlight&/&
&button name=&action_done& type=&object&
string=&Mark as done& states=&confirmed&
class=&oe_highlight&/&
&field name=&state& widget=&statusbar&/&
&group string=&General&&
Workflows may be associated with any object in Odoo, and are entirely customizable. Workflows are used to structure and manage the lifecycles of business objects and documents, and define transitions, triggers, etc.
with graphical tools. Workflows, activities (nodes or actions) and transitions (conditions) are declared as XML records, as usual. The tokens that navigate in workflows are called workitems.
A workflow associated with a model is only created when the model‘s records are created. Thus there is no workflow instance associated with session instances created before the workflow‘s definition
Replace the ad-hoc&Session&workflow by a real workflow. Transform the&Session&form view so its buttons call the workflow instead of the model‘s
openacademy/__openerp__.py
‘templates.xml‘,
‘views/openacademy.xml‘,
‘views/partner.xml‘,
‘views/session_workflow.xml‘,
# only loaded in demonstration mode
‘demo‘: [
openacademy/models.py
(‘draft‘, &Draft&),
(‘confirmed‘, &Confirmed&),
(‘done‘, &Done&),
@api.multi
def action_draft(self):
openacademy/views/openacademy.xml
&field name=&arch& type=&xml&&
&form string=&Session Form&&
&button name=&draft& type=&workflow&
string=&Reset to draft&
states=&confirmed,done&/&
&button name=&confirm& type=&workflow&
string=&Confirm& states=&draft&
class=&oe_highlight&/&
&button name=&done& type=&workflow&
string=&Mark as done& states=&confirmed&
class=&oe_highlight&/&
&field name=&state& widget=&statusbar&/&
openacademy/views/session_workflow.xml
&record model=&workflow& id=&wkf_session&&
&field name=&name&&OpenAcademy sessions workflow&/field&
&field name=&osv&&openacademy.session&/field&
&field name=&on_create&&True&/field&
&record model=&workflow.activity& id=&draft&&
&field name=&name&&Draft&/field&
&field name=&wkf_id& ref=&wkf_session&/&
&field name=&flow_start& eval=&True&/&
&field name=&kind&&function&/field&
&field name=&action&&action_draft()&/field&
&record model=&workflow.activity& id=&confirmed&&
&field name=&name&&Confirmed&/field&
&field name=&wkf_id& ref=&wkf_session&/&
&field name=&kind&&function&/field&
&field name=&action&&action_confirm()&/field&
&record model=&workflow.activity& id=&done&&
&field name=&name&&Done&/field&
&field name=&wkf_id& ref=&wkf_session&/&
&field name=&kind&&function&/field&
&field name=&action&&action_done()&/field&
&record model=&workflow.transition& id=&session_draft_to_confirmed&&
&field name=&act_from& ref=&draft&/&
&field name=&act_to& ref=&confirmed&/&
&field name=&signal&&confirm&/field&
&record model=&workflow.transition& id=&session_confirmed_to_draft&&
&field name=&act_from& ref=&confirmed&/&
&field name=&act_to& ref=&draft&/&
&field name=&signal&&draft&/field&
&record model=&workflow.transition& id=&session_done_to_draft&&
&field name=&act_from& ref=&done&/&
&field name=&act_to& ref=&draft&/&
&field name=&signal&&draft&/field&
&record model=&workflow.transition& id=&session_confirmed_to_done&&
&field name=&act_from& ref=&confirmed&/&
&field name=&act_to& ref=&done&/&
&field name=&signal&&done&/field&
&/openerp&
Automatic transitions
Automatically transition sessions from&Draft&to&Confirmed&when more than half the session‘s seats are reserved.
openacademy/views/session_workflow.xml
&field name=&act_to& ref=&done&/&
&field name=&signal&&done&/field&
&record model=&workflow.transition& id=&session_auto_confirm_half_filled&&
&field name=&act_from& ref=&draft&/&
&field name=&act_to& ref=&confirmed&/&
&field name=&condition&&taken_seats & 50&/field&
&/openerp&
Server actions
Replace the Python methods for synchronizing session state by server actions.
Both the workflow and the server actions could have been created entirely from the UI.
openacademy/views/session_workflow.xml
&field name=&on_create&&True&/field&
&record model=&ir.actions.server& id=&set_session_to_draft&&
&field name=&name&&Set session to Draft&/field&
&field name=&model_id& ref=&model_openacademy_session&/&
&field name=&code&&
model.search([(‘id‘, ‘in‘, context[‘active_ids‘])]).action_draft()
&record model=&workflow.activity& id=&draft&&
&field name=&name&&Draft&/field&
&field name=&wkf_id& ref=&wkf_session&/&
&field name=&flow_start& eval=&True&/&
&field name=&kind&&dummy&/field&
&field name=&action&&&/field&
&field name=&action_id& ref=&set_session_to_draft&/&
&record model=&ir.actions.server& id=&set_session_to_confirmed&&
&field name=&name&&Set session to Confirmed&/field&
&field name=&model_id& ref=&model_openacademy_session&/&
&field name=&code&&
model.search([(‘id‘, ‘in‘, context[‘active_ids‘])]).action_confirm()
&record model=&workflow.activity& id=&confirmed&&
&field name=&name&&Confirmed&/field&
&field name=&wkf_id& ref=&wkf_session&/&
&field name=&kind&&dummy&/field&
&field name=&action&&&/field&
&field name=&action_id& ref=&set_session_to_confirmed&/&
&record model=&ir.actions.server& id=&set_session_to_done&&
&field name=&name&&Set session to Done&/field&
&field name=&model_id& ref=&model_openacademy_session&/&
&field name=&code&&
model.search([(‘id‘, ‘in‘, context[‘active_ids‘])]).action_done()
&record model=&workflow.activity& id=&done&&
&field name=&name&&Done&/field&
&field name=&wkf_id& ref=&wkf_session&/&
&field name=&kind&&dummy&/field&
&field name=&action&&&/field&
&field name=&action_id& ref=&set_session_to_done&/&
&record model=&workflow.transition& id=&session_draft_to_confirmed&&
Access control mechanisms must be configured to achieve a coherent security policy.
Group-based access control mechanisms
Groups are created as normal records on the model&res.groups,
and granted menu access via menu definitions. However even without a menu, objects may still be accessible indirectly, so actual object-level permissions (read, write, create, unlink) must be defined for groups. They are usually inserted via CSV files inside
modules. It is also possible to restrict access to specific fields on a view or object using the field‘s groups attribute.
Access rights
Access rights are defined as records of the model&ir.model.access.
Each access right is associated to a model, a group (or no group for global access), and a set of permissions: read, write, create, unlink. Such access rights are usually created by a CSV file named after its model:&ir.model.access.csv.
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_idea_idea,idea.idea,model_idea_idea,base.group_user,1,1,1,0
access_idea_vote,idea.vote,model_idea_vote,base.group_user,1,1,1,0
Add access control through the OpenERP interface
Create a new user &John Smith&. Then create a group &OpenAcademy / Session Read& with read access to the&Session&model.
Create a new user&John Smith&through&Settings ? Users ? UsersCreate a new group&session_read&through&Settings
? Users ? Groups, it should have read access on the&Session&modelEdit&John Smith&to make them a member of&session_readLog in as&John Smith&to check the access rights are correct
Add access control through data files in your module
Using data files,
Create a group&OpenAcademy / Manager&with full access to all OpenAcademy modelsMake&Session&and&Course&readable by all users
Create a new file&openacademy/security/security.xml&to
hold the OpenAcademy Manager groupEdit the file&openacademy/security/ir.model.access.csv&with
the access rights to the modelsFinally update&openacademy/__openerp__.py&to
add the new data files to it
openacademy/__openerp__.py
# always loaded
‘data‘: [
‘security/security.xml‘,
‘security/ir.model.access.csv‘,
‘templates.xml‘,
‘views/openacademy.xml‘,
‘views/partner.xml‘,
openacademy/security/ir.model.access.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
course_manager,course manager,model_openacademy_course,group_manager,1,1,1,1
session_manager,session manager,model_openacademy_session,group_manager,1,1,1,1
course_read_all,course all,model_openacademy_course,,1,0,0,0
session_read_all,session all,model_openacademy_session,,1,0,0,0
openacademy/security/security.xml
&record id=&group_manager& model=&res.groups&&
&field name=&name&&OpenAcademy / Manager&/field&
&/openerp&
Record rules
A record rule restricts the access rights to a subset of records of the given model. A rule is a record of the model&ir.rule,
and is associated to a model, a number of groups (many2many field), permissions to which the restriction applies, and a domain. The domain specifies to which records the access rights are limited.
Here is an example of a rule that prevents the deletion of leads that are not in state&cancel.
Notice that the value of the fieldgroups&must follow
the same convention as the method&&of
&record id=&delete_cancelled_only& model=&ir.rule&&
&field name=&name&&Only cancelled leads may be deleted&/field&
&field name=&model_id& ref=&crm.model_crm_lead&/&
&field name=&groups& eval=&[(4, ref(‘base.group_sale_manager‘))]&/&
&field name=&perm_read& eval=&0&/&
&field name=&perm_write& eval=&0&/&
&field name=&perm_create& eval=&0&/&
&field name=&perm_unlink& eval=&1& /&
&field name=&domain_force&&[(‘state‘,‘=‘,‘cancel‘)]&/field&
Record rule
Add a record rule for the model Course and the group &OpenAcademy / Manager&, that restricts&write&andunlink&accesses
to the responsible of a course. If a course has no responsible, all users of the group must be able to modify it.
Create a new rule in&openacademy/security/security.xml:
openacademy/security/security.xml
&record id=&group_manager& model=&res.groups&&
&field name=&name&&OpenAcademy / Manager&/field&
&record id=&only_responsible_can_modify& model=&ir.rule&&
&field name=&name&&Only Responsible can modify Course&/field&
&field name=&model_id& ref=&model_openacademy_course&/&
&field name=&groups& eval=&[(4, ref(‘openacademy.group_manager‘))]&/&
&field name=&perm_read& eval=&0&/&
&field name=&perm_write& eval=&1&/&
&field name=&perm_create& eval=&0&/&
&field name=&perm_unlink& eval=&1&/&
&field name=&domain_force&&
[‘|‘, (‘responsible_id‘,‘=‘,False),
(‘responsible_id‘,‘=‘,user.id)]
&/openerp&
Wizards describe interactive sessions with the user (or dialog boxes) through dynamic forms. A wizard is simply a model that extends the class&TransientModel&instead
The class&TransientModel&extends&&and
reuse all its existing mechanisms, with the following particularities:
Wizard records are not me they are automatically deleted from the database after a certain time. This is why they are called&transient.Wizard models do not require explicit access rights: users have all permissions on wizard records.Wizard records may refer to regular records or wizard records through many2one fields, but regular records&cannot&refer to wizard records through a many2one field.
We want to create a wizard that allow users to create attendees for a particular session, or for a list of sessions at once.
Define the wizard
Create a wizard model with a many2one relationship with the&Session&model and a many2many relationship with the&Partner&model.
Add a new file&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 openerp 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&)
Launching wizards
Wizards are launched by&ir.actions.act_window&records,
with the field&target&set to the value&new.
The latter opens the wizard view into a popup window. The action may be triggered by a menu item.
There is another way to launch the wizard: using an&ir.actions.act_window&record
like above, but with an extra field&src_modelthat specifies
in the context of which model the action is available. The wizard will appear in the contextual actions of the model, above the main view. Because of some internal hooks in the ORM, such an action is declared in XML with the tag&act_window.
&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&/&
Wizards use regular views and their buttons may use the attribute&special=&cancel&&to
close the wizard window without saving.
Launch the wizard
Define a form view for the wizard.Add the action to launch it in the context of the&Session&model.Define a default value for the session use the context parameter&self._context&to
retrieve the current session.
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&/&
&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&&
&field name=&session_id&/&
&field name=&attendee_ids&/&
&act_window id=&launch_session_wizard&
name=&Add Attendees&
src_model=&openacademy.session&
res_model=&openacademy.wizard&
view_mode=&form&
target=&new&
key2=&client_action_multi&/&
&/openerp&
Register attendees
Add buttons to the wizard, and implement the corresponding method for adding the attendees to the given session.
openacademy/views/openacademy.xml
&field name=&attendee_ids&/&
&button name=&subscribe& type=&object&
string=&Subscribe& class=&oe_highlight&/&
&button special=&cancel& string=&Cancel&/&
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
Register attendees to multiple sessions
Modify the wizard model so that attendees can be registered to multiple sessions.
openacademy/views/openacademy.xml
&form string=&Add Attendees&&
&field name=&session_ids&/&
&field name=&attendee_ids&/&
&button name=&subscribe& type=&object&
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
Internationalization
Each module can provide its own translations within the i18n directory, by having files named LANG.po where LANG is the locale code for the language, or the language and country combination when they differ (e.g.
pt.po or pt_BR.po). Translations will be loaded automatically by Odoo for all enabled languages. Developers always use English when creating a module, then export the module terms using Odoo‘s gettext POT export feature (Settings
? Translations ? Import/Export ? Export Translation&without specifying a language), to create the module template POT file, and then derive the translated PO files. Many IDE‘s have plugins or modes for editing and merging PO/POT files.
|- idea/ # The module directory
|- i18n/ # Translation files
| - idea.pot # Translation Template (exported from Odoo)
| - fr.po # French translation
| - pt_BR.po # Brazilian Portuguese translation
Translate a module
Choose a second language for your Odoo installation. Translate your module using the facilities provided by Odoo.
Create a directory&openacademy/i18n/Install whichever language you want (&Administration ? Translations ? Load an Official Translation)Synchronize translatable terms (Administration ? Translations ? Application Terms ? Synchronize Translations)Create a template translation file by exporting (&Administration ? Translations -& Import/Export ? Export Translation) without specifying a language, save in&openacademy/i18n/Create a translation file by exporting (&Administration ? Translations ? Import/Export ? Export Translation) and specifying a language. Save it in&openacademy/i18n/Open the exported translation file (with a basic text editor or a dedicated PO-file editor e.g.&&and
translate the missing termsIn&models.py, add an import
statement for the function&openerp._&and mark missing
strings as translatableRepeat steps 3-6
openacademy/models.py
# -*- coding: utf-8 -*-
from datetime import timedelta
from openerp 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)
new_name = _(u&Copy of {} ({})&).format(self.name, copied_count)
default[‘name‘] = new_name
return super(Course, self).copy(default)
if self.seats & 0:
‘warning‘: {
‘title‘: _(&Incorrect ‘seats‘ value&),
‘message‘: _(&The number of available seats may not be negative&),
if self.seats & len(self.attendee_ids):
‘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&))
Printed reports
Odoo 8.0 comes with a new report engine based on&,&&and&.
A report is a combination two elements:
an&ir.actions.report.xml,
for which a&&report&&shortcut element is provided, it
sets up various basic parameters for the report (default type, whether the report should be saved to the database after generation,…)
id=&account_invoices&
model=&account.invoice&
string=&Invoices&
report_type=&qweb-pdf&
name=&account.report_invoice&
file=&account.report_invoice&
attachment_use=&True&
attachment=&(object.state in (‘open‘,‘paid‘)) and
(‘INV‘+(object.number or ‘‘).replace(‘/‘,‘‘)+‘.pdf‘)&
A standard&&for the actual report:
&t t-call=&report.html_container&&
&t t-foreach=&docs& t-as=&o&&
&t t-call=&report.external_layout&&
&div class=&page&&
&h2&Report title&/h2&
the standard rendering context provides a number of elements, the most
important being:
the records for which the report is printed
the user printing the report
Because reports are standard web pages, they are available through a URL and output parameters can be manipulated through this URL, for instance the HTML version of the&Invoice&report
is available through&(if&account&is
installed) and the PDF version through.
If it appears that your PDF report is missing the styles (i.e. the text appears but the style/layout is different from the html version), probably your&&process
cannot reach your web server to download them.
If you check your server logs and see that the CSS styles are not being downloaded when generating a PDF report, most surely this is the problem.
The&&process
will use the&web.base.url&system parameter as the&root
path&to all linked files, but this parameter is automatically updated each time the Administrator is logged in. If your server resides behind some kind of proxy, that could not be reachable. You can fix this by adding one of these system parameters:
report.url, pointing to an
URL reachable from your server (probably&http://localhost:8069&or
something similar). It will be used for this particular purpose only.web.base.url.freeze, when
set to&True, will stop the automatic updates to&web.base.url.
Create a report for the Session model
For each session, it should display session‘s name, its start and end, and list the session‘s attendees.
openacademy/__openerp__.py
‘views/openacademy.xml‘,
‘views/partner.xml‘,
‘views/session_workflow.xml‘,
‘reports.xml‘,
# only loaded in demonstration mode
‘demo‘: [
openacademy/reports.xml
id=&report_session&
model=&openacademy.session&
string=&Session Report&
name=&openacademy.report_session_view&
file=&openacademy.report_session&
report_type=&qweb-pdf& /&
&template id=&report_session_view&&
&t t-call=&report.html_container&&
&t t-foreach=&docs& t-as=&doc&&
&t t-call=&report.external_layout&&
&div class=&page&&
&h2 t-field=&doc.name&/&
&p&From &span t-field=&doc.start_date&/& to &span t-field=&doc.end_date&/&&/p&
&h3&Attendees:&/h3&
&t t-foreach=&doc.attendee_ids& t-as=&attendee&&
&li&&span t-field=&attendee.name&/&&/li&
&/template&
&/openerp&
Dashboards
Define a Dashboard
Define a dashboard containing the graph view you created, the sessions calendar view and a list view of the courses (switchable to a form view). This dashboard should be available through a menuitem in the menu,
and automatically displayed in the web client when the OpenAcademy main menu is selected.
Create a file&openacademy/views/session_board.xml.
It should contain the board view, the actions referenced in that view, an action to open the dashboard and a re-definition of the main menu item to add the dashboard action
Update&openacademy/__openerp__.py&to
reference the new data file
openacademy/__openerp__.py
‘version‘: ‘0.1‘,
# any module necessary for this one to work correctly
‘depends‘: [‘base‘, ‘board‘],
# always loaded
‘data‘: [
‘views/openacademy.xml‘,
‘views/partner.xml‘,
‘views/session_workflow.xml‘,
‘views/session_board.xml‘,
‘reports.xml‘,
# only loaded in demonstration mode
openacademy/views/session_board.xml
&?xml version=&1.0&?&
&record model=&ir.actions.act_window& id=&act_session_graph&&
&field name=&name&&Attendees by course&/field&
&field name=&res_model&&openacademy.session&/field&
&field name=&view_type&&form&/field&
&field name=&view_mode&&graph&/field&
&field name=&view_id&
ref=&openacademy.openacademy_session_graph_view&/&
&record model=&ir.actions.act_window& id=&act_session_calendar&&
&field name=&name&&Sessions&/field&
&f}

我要回帖

更多关于 jupyter notebook .py 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信