创建模板
警告
本教程需要安装Odoo
Odoo使用客户端/服务器架构,客户端是通过RPC访问Odoo服务器的Web浏览器。
业务逻辑和扩展通常在服务器端执行,尽管可以向客户端添加支持客户端功能(如交互式地图等新数据表示)。
为了启动服务器,只需在shell中调用odoo-bin命令即可,如果需要,将完整路径添加到该文件中:
odoo - bin
通过Ctrl-C从终端击中两次或通过杀死相应的操作系统进程来停止服务器。
服务器和客户端扩展都被打包为可选地加载到数据库中的模块。
Odoo模块可以向Odoo系统添加全新的业务逻辑,也可以改变和扩展现有的业务逻辑:可以创建一个模块,将您所在国家的会计规则添加到Odoo的通用会计支持中,而下一个模块增加了对实时可视化的支持的公交车队。
Odoo中的所有内容都以模块开始和结束。
Odoo模块可以包含许多元素:
业务对象
这些资源被声明为Python类,这些资源将根据Odoo的配置自动保留
数据文件
声明元数据(视图或工作流),配置数据(模块参数化),演示数据等的XML或CSV文件
Web控制器
处理来自网络浏览器的请求
静态网页数据
Web界面或网站使用的图片,CSS或JavaScript文件
每个模块都是模块目录中的一个目录。使用该--addons-path 选项指定模块目录。
小费
大多数命令行选项也可以使用配置文件进行设置
Odoo模块由其清单声明。请参阅清单文档。
模块也是 Python包 用__init__.py文件,其中包含用于将模块以各种Python文件导入说明。
例如,如果模块有单个mymodule.py文件__init__.py 可能包含:
from . import mymodule
Odoo提供了一种机制,以帮助建立一个新的模块,odoo斌有一个子支架创建一个空的模块:
$ odoo-bin scaffold
该命令为您的模块创建一个子目录,并为模块自动创建一堆标准文件。他们大多数只是包含注释的代码或XML。大多数这些文件的使用将在本教程中解释。
行使
模块创建
使用上面的命令行创建一个空模块Open Academy,并将其安装在Odoo中。
1. 调用命令odoo-bin scaffold openacademy addons。
2. 将清单文件修改到模块。
3. 不要打扰其他文件。
openacademy/__manifest__.py
# -*- coding: utf-8 -*-
{ 'name': "Open Academy",
'summary': """Manage trainings""",
'description': """
Open Academy module for managing trainings:
- training courses
- training sessions
- attendees registration
""",
'author': "My Company",
'website': "http://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/master/odoo/addons/base/module/module_data.xml
# for the full list 'category': 'Test', 'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
'templates.xml', ],
# only loaded in demonstration mode
'demo': [
'demo.xml',
],}
openacademy/__init__.py
# -*- coding: utf-8 -*-
from . import controllers
from . import models
openacademy/controllers.py
# -*- coding: utf-8 -*-
from odoo import http
# class Openacademy(http.Controller):
# @http.route('/openacademy/openacademy/', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/openacademy/openacademy/objects/', auth='public')
# def list(self, **kw):
# return http.request.render('openacademy.listing', {
# 'root': '/openacademy/openacademy',
# 'objects': http.request.env['openacademy.openacademy'].search([]),
# })
# @http.route('/openacademy/openacademy/objects/
# def object(self, obj, **kw):
# return http.request.render('openacademy.object', {
# 'object': obj
# })
openacademy/demo.xml
openacademy/models.py
# -*- coding: utf-8 -*-
from odoo import models, fields, api
# class openacademy(models.Model):
# _name = 'openacademy.openacademy'
# name = fields.Char()
openacademy/security/ir.model.access.csv
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0
openacademy/templates.xml
Odoo的关键组件是ORM层。该层避免 了手工编写大部分SQL,并提供可扩展性和安全性服务2。
业务对象被声明为扩展的Python类 Model,将它们集成到自动持久性系统中。
可以通过在其定义中设置多个属性来配置模型。最重要的属性是 _name必需的,并且在Odoo系统中定义模型的名称。这是模型的最低限度定义:
from odoo import modelsclass
MinimalModel(models.Model):
_name = 'test.model'
字段用于定义模型可以存储的位置和位置。字段被定义为模型类的属性:
from odoo import models, fields
class LessMinimalModel(models.Model):
_name = 'test.model2'
name = fields.Char()
很像模型本身,它的字段可以通过配置属性作为参数进行配置:
name = field.Char(required=True)
一些属性可用于所有字段,这里是最常见的属性:
string(unicode,默认值:字段的名称)
UI中的字段标签(用户可见)。
required(bool默认:False)
如果True,该字段不能为空,则它必须具有默认值,或者在创建记录时始终被赋予一个值。
help(unicode默认:'')
长形式,在用户界面中为用户提供帮助工具提示。
index(bool默认:False)
请求Odoo 在列上创建数据库索引。
有两个广泛的领域:“简单”字段,它们是直接存储在模型表中的原子值和链接相同模型或不同模型的记录的“关系”字段。
简单字段的例子是Boolean, Date,Char。
Odoo在所有型号中创建了几个字段1。这些字段由系统管理,不应写入。如果有用或必要,可以阅读它们:
id(Id)
其模型中的记录的唯一标识符。
create_date(Datetime)
记录的创建日期。
create_uid(Many2one)
创建记录的用户。
write_date(Datetime)
记录的最后修改日期。
write_uid(Many2one)
用户最后修改记录。
默认情况下,Odoo还需要name所有型号的字段用于各种显示和搜索行为。用于这些目的的字段可以通过设置来覆盖_rec_name。
行使
定义一个模型
在openacademy模块中定义新的数据模型课程。课程有标题和描述。课程必须有标题。
编辑文件openacademy/models/models.py以包括Course 类。
openacademy/models.py
from odoo import models, fields, api
class Course(models.Model):
_name = 'openacademy.course'
name = fields.Char(string="Title", required=True)
description = fields.Text()
Odoo是一个高度数据驱动的系统。虽然行为是使用Python代码定制的 ,但是模块的一部分值在加载时设置的数据中。
小费
一些模块仅存在于向Odoo添加数据
模块数据通过数据文件,带有
· model 是Odoo模型的名称,用于记录。
· id是一个外部标识符,它允许引用该记录(而不必知道其数据库内标识符)。
·
必须在清单文件中声明数据文件才能加载,它们可以在'data'列表中(始终加载)或'demo'列表中声明(仅在演示模式下加载)。
行使
定义演示数据
创建演示数据填充课程模型与几个示范课程。
编辑文件openacademy/demo/demo.xml以包含一些数据。
openacademy/demo.xml
Course 0's descriptionCan have multiple lines
操作和菜单是数据库中的常规记录,通常通过数据文件声明。可以通过三种方式触发操作:
1. 通过点击菜单项(链接到具体操作)
2. 通过点击视图中的按钮(如果这些连接到动作)
3. 作为对象的上下文动作
因为菜单有些复杂,因为声明有一个 快捷方式来声明一个ir.ui.menu更容易连接到相应的动作。
action="action_list_ideas"/>
危险
必须在XML文件中的相应菜单之前声明该操作。
数据文件依次执行,操作id必须存在于数据库中,才能创建菜单。
行使
定义新菜单项
定义新的菜单条目以访问OpenAcademy菜单项下的课程。用户应该能够:
· 显示所有课程的列表
· 创建/修改课程
1. 创建openacademy/views/openacademy.xml一个动作和菜单触发动作
2. 将其添加到data列表中openacademy/__manifest__.py
openacademy/__manifest__.py
'data': [
# 'security/ir.model.access.csv',
'templates.xml', 'views/openacademy.xml',
],
# only loaded in demonstration mode
'demo': [
openacademy/views/openacademy.xml
class="oe_view_nocontent_create">
Create the first course
parent="main_openacademy_menu"/>
action="course_list_action"/>
视图定义了显示模型记录的方式。每种类型的视图都表示可视化模式(记录列表,其聚合图形...)。视图可以通过它们的类型(例如,合作伙伴列表)或者通过其id来特别地请求。对于通用请求,将使用具有正确类型和最低优先级的视图(因此每种类型的最低优先级视图是该类型的默认视图)。
查看继承允许更改其他地方声明的视图(添加或删除内容)。
视图被声明为模型的记录ir.ui.view。视图类型由arch字段的根元素暗示:
<
危险
视图的内容是XML。
因此,arch该字段必须被声明为type="xml"正确解析。
Tree views(也称为列表视图)以表格形式显示记录。
它们的根元素是
tree>
From views
表单用于创建和编辑单个记录。
它们的根元素是。它们由高级结构元素(groups, notebooks)和交互元素(buttons and fields)组成:
Search views
搜索视图自定义与列表视图(and other aggregated views)相关联的搜索字段。它们的根元素是
search>
如果模型没有搜索视图,Odoo会生成一个仅允许在该name字段上进行搜索的视图。
Exercise
Search courses
允许根据他们的标题或描述搜索courses。
openacademy/views/openacademy.xml
parent="openacademy_menu"
action="session_list_action"/>
注意
digits=(6, 2)指定浮点数的精度:6是总位数,而2是逗号后的位数。请注意,它会导致逗号之前的数字最多为4
关系字段链接相同模型(层次结构)或不同模型之间的记录。
关系字段类型有:
Many2one(other_model, ondelete='set null')
与其他对象的简单链接:
print foo.other_id.name
One2many(other_model, related_field)
虚拟关系,一个倒数Many2one。A One2many作为记录的容器,访问它会导致一个(可能是空的)记录集:
for other in foo.other_ids:
print other.name
危险
因为a One2many是一个虚拟关系,所以必须有一个Many2one字段 other_model,其名称必须是related_field
Many2many(other_model)
双向多重关系,一方的任何记录可以与另一方的任何数量的记录相关。作为记录的容器,访问它也会导致一组可能为空的记录集:
for other in foo.other_ids:
print other.name
Exercise
很多人关系
使用many2one,修改课程和会话模型以反映与其他模型的关系:
· 课程有一个负责任的用户; 该字段的值是内置模型的记录res.users。
· 会话有一个教练 ; 该字段的值是内置模型的记录res.partner。
· 一个课程与课程有关 ; 该字段的值是模型的记录,openacademy.course是必需的。
· 适应观点。
1. 将相关Many2one字段添加到模型中
2. 将它们添加到视图中。
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
Exercise
反向一个关系
使用反向关系字段one2many,修改模型以反映课程和会话之间的关系。
1. 修改Course类,和
2. 在课程表单视图中添加该字段。
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
行使
多重多关系
使用关系字段many2many,修改会话模型以将每个会话关联到一组与会者。参加者将由合作伙伴记录代表,因此我们将与内置模型相关res.partner。相应地调整意见。
1. 修改Session类,和
2. 在窗体视图中添加该字段。
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 /视图/ openacademy.xml
group>
group>
sheet>
form>
field>
Odoo提供了两种继承机制,以模块化方式扩展现有的模型。
第一个继承机制允许模块修改在另一个模块中定义的模型的行为:
· 向模型添加字段,
· 覆盖模型上字段的定义,
· 对模型添加约束,
· 向模型添加方法,
· 覆盖模型上的现有方法。
第二个继承机制(委托)允许将模型的每个记录链接到父模型中的记录,并提供对父记录的字段的透明访问。
也可以看看
· _inherit
· _inherits
Odoo不需要修改现有的视图(通过覆盖它们),而是在根视图之上应用子扩展视图的视图继承,并可以从父级添加或删除内容。
扩展视图使用inherit_id字段引用其父级,而不是单个视图,其arch字段由任意数量的xpath元素组成, 选择和更改其父视图的内容:
expr
一个的XPath表达式中选择父视图的单个元素。如果不匹配任何元素或多于一个元素,则会引发错误
position
适用于匹配元素的操作:
inside
xpath在匹配的元素的末尾附加身体
replace
用xpath“body” 替换匹配的元素,用$0原始元素替换新主体中的任何节点
before
xpath在匹配的元素之前插入身体作为兄弟姐妹
after
xpaths在匹配的元素之后插入身体作为兄弟姐妹
attributes
改变使用特殊的匹配元素的属性 attribute的元素的xpath的身体
小费
当匹配单个元素时,position可以在要查找的元素上直接设置该属性。以下两个遗产都会得到相同的结果。
行使
更改现有内容
· 使用模型继承,修改现有的合作伙伴模型以添加 instructor布尔字段,以及对应于会话伙伴关系的many2many字段
· 使用视图继承,在合作伙伴表单视图中显示此字段
注意
这是介绍开发人员模式检查视图,找到其外部ID和放置新字段的位置的机会。
1. 创建一个文件openacademy/models/partner.py并将其导入 __init__.py
2. 创建一个文件openacademy/views/partner.xml并将其添加到 __manifest__.py
openacademy/__init__.py
# -*- coding: utf-8 -*-
from . import controllersfrom . import models
from . import partner
openacademy/__manifest__.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 odoo 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
parent="main_openacademy_menu"/>
parent="configuration_menu"
action="contact_list_action"/>
在Odoo中,域是编码记录条件的值。域是用于选择模型记录子集的标准列表。每个标准是一个带有字段名称,运算符和值的三元组。
例如,当在产品型号上使用时,以下域选择单价超过1000的所有服务:
[('product_type' , '=' , 'service' ), ('unit_price' , '>' , 1000 )]
默认情况下,条件与隐式AND组合。逻辑运算符 &(AND),|(OR)和!(NOT)可用于明确组合条件。它们用于前缀位置(操作符在其参数之前插入,而不是之间)。例如选择产品“这是服务或有单位价格是不是 1000 - 2000年间”:
[ '|' ,
('product_type' , '=' , 'service' ),
'!' , '&' ,
('unit_price' , '> =' , 1000 ),
('unit_price' , '<' , 2000 )]
domain当尝试在客户端界面中选择记录时,可以将一个参数添加到关系字段中以限制关系的有效记录。
行使
关系领域
当选择会话的教师时,只有教师(具有instructor设置的合作伙伴True)应该是可见的。
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")
注意
声明为文字列表的域被评估为服务器端,并且不能引用右侧的动态值,声明为字符串的域被评估为客户端,并允许右侧的字段名称
行使
更复杂的域名
创建新的合作伙伴类别教师/ 1级和教师/ 2级。会话的教练可以是教练或教师(任何级别)。
1. 修改会话模型的域
2. 修改openacademy/view/partner.xml以访问 合作伙伴类别:
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
parent="configuration_menu"
action="contact_list_action"/>
parent="configuration_menu"
action="contact_cat_list_action"/>
到目前为止,字段已直接存储在数据库中并直接从数据库中检索。也可以计算字段。在这种情况下,字段的值不会从数据库中检索,而是通过调用模型的方法在运行中计算。
要创建计算字段,请创建一个字段并将其属性设置 compute为方法的名称。计算方法应该简单地设置字段的值来计算每个记录 self。
危险
self 是一个集合
对象self是一个记录集,即一个有序的记录集合。它支持标准的Python操作的集合,像 len(self)和iter(self),加上额外的一套操作,比如recs1 + recs2。
迭代过来self给出记录一个一个,其中每个记录本身是大小为1的集合。您可以使用点符号来访问/分配单个记录上的字段,如record.name。
import randomfrom odoo import models, fields, api
class ComputedModel(models.Model):
_name = 'test.computed'
name = fields.Char(compute='_compute_name')
@api.multi
def _compute_name(self):
for record in self:
record.name = str(random.randint(1, 1e6))
计算字段的值通常取决于计算记录上其他字段的值。ORM希望开发人员使用装饰器来指定计算方法的依赖关系depends()。ORM使用给定的依赖关系来触发字段的重新计算,只要某些依赖关系被修改:
from odoo import models, fields, api
class ComputedModel(models.Model):
_name = 'test.computed'
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
行使
计算字段
· 将占席席位的百分比添加到会议模型中
· 在树中显示该字段并形成视图
· 将该字段显示为进度条
1. 将计算字段添加到会话
2. 在会话视图中显示该字段:
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
else:
r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
openacademy/views/openacademy.xml
任何字段都可以被赋予默认值。在字段定义中,添加选项 default=X,其中X是Python文字值(boolean,integer,float,string),或者使用记录集并返回值的函数:
name = fields.Char(default="Unknown")user_id = fields.Many2one('res.users', default=lambda self: self.env.user)
注意
该对象self.env允许访问请求参数和其他有用的东西:
· self.env.cr或者self._cr是数据库游标对象; 它用于查询数据库
· self.env.uid或者self._uid是当前用户的数据库ID
· self.env.user 是当前用户的记录
· self.env.context或者self._context是上下文字典
· self.env.ref(xml_id) 返回对应于XML标识的记录
· self.env[model_name] 返回给定模型的实例
行使
活动对象 - 默认值
· 将start_date默认值定义为今天(见 Date)。
· active在类Session中添加一个字段,默认情况下将会话设置为活动状态。
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
注意
Odoo内置规则制作字段active设置为False不可见。
“onchange”机制为客户端界面提供了一种方式,只要用户填写一个字段中的值,就不用保存任何数据就可以更新表单。
例如,假设模型有三个字段amount,unit_price并且 price当您修改任何其他字段时,您希望更新表单上的价格。为了实现这一点,定义一个方法,它self表示窗体视图中的记录,并用它onchange() 来对其进行装饰,以指定哪个字段必须被触发。你所做的任何改变 self都会反映在表格上。
# 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
return {
'warning': {
'title': "Something bad happened",
'message': "It was very bad indeed",
}
}
对于计算的字段,onchange内置有价值的行为,可以通过使用会话表单来看到:更改席位或参与者的数量,并且taken_seats进度条将自动更新。
行使
警告
添加一个明确的onchange来警告有关无效值,比如负数的席位,或比座席多的参与者。
openacademy / models.py
r.taken_seats = 0.0
else:
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:
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",
},
}
Odoo提供两种方式来设置自动验证的不变量: Python constraints和 SQL constraints。
Python约束被定义为用constrains()记录集装饰并在记录集上调用的方法 。装饰器指定约束中涉及哪些字段,以便在其中一个被修改时自动计算约束。如果该方法的不变量不满足,则该方法将会引发异常:
from odoo.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
行使
添加Python约束
添加一个约束,该约束检查教师不在他/她自己的会话的与会者中。
openacademy/models.py
# -*- coding: utf-8 -*-
from odoo 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")
通过model属性定义SQL约束 _sql_constraints。后者被分配给一个三元组的列表(name, sql_definition, message),其中name有一个有效的SQL约束名称,sql_definition是一个table_constraint表达式,并且message是错误消息。
行使
添加SQL约束
在PostgreSQL文档的帮助下,添加以下约束:
1. 检查课程描述和课程名称是不同的
2. 使课程名称独一无二
openacademy / models.py
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'
行使
练习6 - 添加重复选项
由于我们为课程名称唯一性添加了约束,所以不可能再使用“重复”函数(Form‣Duplicate)。
重新实现自己的“复制”方法,允许复制课程对象,将原始名称更改为“原始名称的副本”。
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)
else:
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)',
树视图可以采取补充属性来进一步自定义他们的行为:
decoration-{$name}
允许根据相应记录的属性更改行的文本的样式。
值是Python表达式。对于每个记录,表达式将使用记录的属性作为上下文值进行计算,如果true相应的样式应用于该行。其他上下文值是 uid(当前用户的id)和current_date(当前日期作为表单的字符串yyyy-MM-dd)。
{$name}可以是bf(font-weight: bold), (it ),font-style: italic或任何自举上下文颜色(danger,info,muted,primary,success或warning)。
decoration-danger = “state =='trashed'” >
tree>
editable
无论是"top"或"bottom"。使树视图可以原位编辑(而不必通过窗体视图),该值是新行显示的位置。
行使
列表着色
修改会话树视图,使持续时间少于5天的会话为蓝色,持续超过15天的会话为红色。
修改会话树视图:
openacademy /视图/ openacademy.xml
decoration-danger="duration>15">
将记录显示为日历事件。它们的根元素是
color
用于颜色分割的字段的名称。颜色会自动分配到事件中,但同一颜色段中的事件(对于其@color字段具有相同值的记录)将被赋予相同的颜色。
date_start
记录的字段保存事件的开始日期/时间
date_stop (可选的)
记录的字段保存事件的结束日期/时间
字段(定义每个日历事件的标签)
行使
日历视图
将日历视图添加到会话模型,使用户能够查看与开放学院相关联的事件。
1.添加end_date从start_date和 计算的字段duration
逆函数使字段可写,并允许在日历视图中移动会话(通过拖放)
1. 将日历视图添加到会话模型
2. 并将日历视图添加到会话模型的操作
openacademy/models.py
# -*- coding: utf-8 -*-
from datetime import timedelta
from odoo 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
Continue
# 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):
Continue
# 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
date_stop="end_date"
color="instructor_id">
搜索视图
搜索视图还可以包含
domain
将给定的域添加到当前搜索
context
为当前搜索添加一些上下文; 使用密钥group_by对给定字段名称的结果进行分组
filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
domain="[('inventor_id', '=', uid)]"/>
context="{'group_by': 'inventor_id'}"/>
要在操作中使用非默认搜索视图,应使用search_view_id操作记录的字段进行链接 。
该操作还可以通过其context字段设置搜索字段的默认值 :窗体的上下文键 将使用提供的值初始化field_name。搜索过滤器必须具有可选功能以具有默认值,并且表现为布尔值(默认情况下只能启用)。search_default_field_name@name
行使
搜索视图
1. 添加一个按钮来过滤当前用户在课程搜索视图中负责的课程。默认选择它。
2. 添加按钮,由负责的用户对课程进行分组。
openacademy/views/openacademy.xml
domain="[('responsible_id', '=', uid)]"/>
context="{'group_by': 'responsible_id'}"/>
class="oe_view_nocontent_create">Create the first course
警告
Cantt视图需要企业版本中存在的web_gantt模块 。
水平条形图通常用于显示项目规划和进步,它们的根本要素是
date_start="invent_date"
date_stop="date_finished"
progress="progress"
default_group_by="inventor_id" />
行使
甘特图
添加甘特图,使用户能够查看与Open Academy模块链接的会话调度。会议应由教师分组。
1. 创建表示会话持续时间(以小时为单位)的计算字段
2. 添加甘特图视图的定义,并将甘特图视图添加到 会话模型的操作
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
date_start="start_date" date_delay="hours"
default_group_by='instructor_id'>
图形视图允许对模型进行聚合概述和分析,它们的根元素是
注意
数据透视图(元素
图形视图具有4种显示模式,使用该@type属性选择默认模式 。
栏(默认)
条形图,第一个维度用于在横轴上定义组,其他维度定义每个组中的聚合条。
默认情况下条是并排的一面,他们可以通过堆叠 @stacked="True"的
线
二维线图
馅饼
二维派
图形视图包含
row (默认)
该字段应该默认合并
measure
该字段应该聚合而不是分组
<
警告
图表视图对数据库值执行聚合,但它们不适用于未存储的计算字段。
行使
图形视图
在会话对象中添加一个图形视图,每个课程显示条形图形式的与会者人数。
1. 将出席人数作为存储的计算字段添加
2. 然后添加相关视图
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
用于组织任务,生产过程等...其根元素是
看板视图显示了可能按列分组的一组卡。每个卡表示一个记录,每个列表示聚合字段的值。
例如,项目任务可以按阶段组织(每列是一个阶段),或者由负责人(每列是用户)等组织。
看板视图将每张卡片的结构定义为表单元素(包括基本HTML)和QWeb的组合。
行使
看板视图
添加一个看板视图,显示按课程分组的会话(列因此是课程)。
1. color向会话模型添加一个整数字段
2. 添加看板视图并更新操作
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>
<record model="ir.ui.view" id="view_openacad_session_kanban">
<field name="name">openacad.session.kanbanfield>
<field name="model">openacademy.sessionfield>
<field name="arch" type="xml">
<kanban default_group_by="course_id">
<field name="color"/>
<templates>
<t t-name="kanban-box">
<div
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">
<div class="oe_dropdown_toggle">
<i class="fa fa-bars fa-lg"/>
<ul class="oe_dropdown_menu">
<li>
<a type="delete">Deletea>
li>
<li>
<ul class="oe_kanban_colorpicker"
data-field="color"/>
li>
ul>
div>
<div class="oe_clear">div>
div>
<div t-attf-class="oe_kanban_content">
Session name:
<field name="name"/>
<br/>
Start date:
<field name="start_date"/>
<br/>
duration:
<field name="duration"/>
div>
div>
t>
templates>
kanban>
field>
record>
<record model="ir.actions.act_window" id="session_list_action">
<field name="name">Sessionsfield>
<field name="res_model">openacademy.sessionfield>
<field name="view_type">formfield>
<field name="view_mode">tree,form,calendar,gantt,graph,kanbanfield>
record>
<menuitem id="session_menu" name="Sessions"
parent="openacademy_menu"
工作流程是与描述其动态的业务对象相关联的模型。工作流也用于跟踪随时间推移的进程。
行使
几乎是一个工作流程
state向会话模型添加一个字段。它将被用来定义一个工作流。
可以有三种可能的状态:草案(默认),已确认和完成。
在会话表单中,添加(只读)字段可视化状态,按钮更改它。有效的转换是:
· 草稿 - >确认
· 已确认 - >草稿
· 已确认 - >完成
· 完成 - >草稿
1. 添加一个新的state字段
2. 添加状态转换方法,可以从视图按钮调用这些方法来更改记录的状态
3. 并将相关按钮添加到会话的窗体视图中
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 /视图/ openacademy.xml
string="Reset to draft"
states="confirmed,done"/>
string="Confirm" states="draft"
class="oe_highlight"/>
string="Mark as done" states="confirmed"
class="oe_highlight"/>
工作流程可能与Odoo中的任何对象相关联,并且可以完全自定义。工作流用于构建和管理业务对象和文档的生命周期,并使用图形工具定义转换,触发器等。工作流,活动(节点或动作)和转换(条件)都像往常一样被声明为XML记录。在工作流中导航的令牌称为工作项。
警告
与模型相关联的工作流仅在创建模型记录时创建。因此,在工作流定义之前没有与会话实例关联的工作流实例
行使
工作流程
通过真实的工作流替换特设会话工作流程。转换 会话表单视图,使其按钮调用工作流而不是模型的方法。
openacademy/__manifest__.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
string="Reset to draft"
states="confirmed,done"/>
string="Confirm" states="draft"
class="oe_highlight"/>
string="Mark as done" states="confirmed"
class="oe_highlight"/>
openacademy/views/session_workflow.xml
小费
为了检查工作流的实例是否正确地与会话一起创建,请转到设置‣技术‣工作流程‣实例
行使
自动转换
当会议席位超过一半时,会自动将会话从“ 草稿”转为“ 已确认”。
openacademy/views/session_workflow.xml
行使
服务器操作
替换用于通过服务器动作同步会话状态的Python方法。
工作流和服务器操作都可以从UI完全创建。
openacademy/views/session_workflow.xml
访问控制机制必须配置为实现一致的安全策略。
组作为模型上的常规记录创建res.groups,并通过菜单定义授予菜单访问权限。但即使没有菜单,对象仍然可以间接访问,因此必须为组定义实际的对象级权限(读取,写入,创建,取消链接)。它们通常通过模块中的CSV文件插入。也可以使用字段的groups属性来限制对视图或对象的特定字段的访问。
访问权限定义为模型的记录ir.model.access。每个访问权限与模型,组(或不用于全局访问的组)相关联,以及一组权限:读取,写入,创建,取消链接。这种访问权限通常由以其模型命名的CSV文件创建: 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
行使
通过Odoo界面添加访问控制
创建一个新用户“John Smith”。然后创建一个“OpenAcademy / Session Read”组,对Session模型进行读访问。
1. 通过设置创建新用户John Smith通过 用户‣用户
2. session_read通过 设置创建新组‣用户‣组,它应该具有会话模型的读访问权限
3. 编辑约翰·史密斯,使他们成为session_read
4. 以约翰·史密斯身份登录,以检查访问权限是否正确
行使
通过模块中的数据文件添加访问控制
使用数据文件,
· 创建一个OpenAcademy / Manager组,可以完全访问所有OpenAcademy模型
· 让会话和课程由所有用户可读
1. 创建一个新文件openacademy/security/security.xml来保存OpenAcademy Manager组
2. openacademy/security/ir.model.access.csv使用模型的访问权限编辑文件
3. 最后更新openacademy/__manifest__.py以添加新的数据文件
openacademy/__manifest__.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,0session_read_all,session
all,model_openacademy_session,,1,0,0,0
openacademy/security/security.xml
/ data> odoo>
记录规则限制对给定模型的记录子集的访问权限。规则是模型的记录ir.rule,并且与模型,多个组(许多字段),适用该限制的权限以及域相关联。域指定访问权限受限于哪些记录。
以下是防止未处于状态的引线的删除的规则示例cancel。请注意,该字段的值groups必须遵循与write()ORM 方法相同的约定。
行使
记录规则
为模型课程和“OpenAcademy / Manager”组添加记录规则,限制write和unlink访问课程负责人。如果课程没有责任,组中的所有用户都必须能够修改它。
创建新规则openacademy/security/security.xml:
openacademy/security/security.xml
['|', ('responsible_id','=',False),
('responsible_id','=',user.id)]
向导通过动态形式描述与用户(或对话框)的交互式会话。一个向导只是一个扩展类TransientModel而不是 类的模型Model。该类 TransientModel扩展Model 和重用其现有的所有机制,具有以下特点:
· 向导记录不是永久性的; 它们会在一段时间后自动从数据库中删除。这就是为什么它们被称为 短暂的原因。
· 向导模型不需要显式访问权限:用户拥有向导记录的所有权限。
· 向导记录可以通过many2one领域引用常规记录或向导记录,但经常记录无法通过many2one场参考向导记录。
我们希望创建一个向导,允许用户为特定会话创建与会者,或者同时创建一个会话列表。
行使
定义向导
创建与会话 模型具有many2one关系的向导模型,并与Partner模型建立许多关系。
添加新文件openacademy/wizard.py:
openacademy/__init__.py
from . import controllersfrom . import models
from . import partnerfrom . 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。
“act_window id =”launch_the_wizard“
name =”启动向导“
src_model =”context.model.name“
res_model =”wizard.model.name“
view_mode =”form“
target =”new“
key2 =”client_action_multi“/>
向导使用常规视图,其按钮可以使用属性 special="cancel"来关闭向导窗口而不保存。
行使
启动向导
1. 为向导定义窗体视图。
2. 添加操作以在会话模型的上下文中启动它。
3. 在向导中定义会话字段的默认值; 使用上下文参数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"/>
name="Add Attendees"
src_model="openacademy.session"
res_model="openacademy.wizard"
view_mode="form"
target="new"
key2="client_action_multi"/>
行使
注册与会者
向向导添加按钮,并实现相应的方法,将与会者添加到给定的会话。
openacademy/views/openacademy.xml
string="Subscribe" class="oe_highlight"/>
Or
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的gettext 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代码中的字段定义内容,但是通过使用函数odoo._() (例如_("Label"))可以将任何Python字符串进行翻译,
行使
翻译一个模块
为您的Odoo安装选择第二种语言。使用Odoo提供的设施翻译您的模块。
1. 创建一个目录 openacademy/i18n/
2. 安装你想要的语言( 管理‣翻译‣加载官方翻译)
3. 同步翻译术语(管理‣翻译‣应用术语‣同步翻译)
4. 通过导出(管理‣翻译 - >导入/导出‣导出翻译)创建模板翻译文件, 而不指定语言,保存openacademy/i18n/
5. 通过导出(Administration‣Translations‣导入/导出‣导出翻译)创建翻译文件 并指定一种语言。保存在 openacademy/i18n/
6. 打开导出的翻译文件(使用基本的文本编辑器或专用的PO文件编辑器,例如POEdit,并翻译缺少的术语
7. 在其中models.py,为函数添加import语句, odoo._并将缺少的字符串标记为可翻译
8. 重复步骤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"))
Odoo 8.0附带了一个基于QWeb, Twitter Bootstrap和Wkhtmltopdf的新的报告引擎。
报告是一个组合的两个要素:
a ir.actions.report.xml,为其提供
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')"/>
实际报告的标准QWeb视图:
Report title
the standard rendering context provides a number of elements, the most
important being:
``docs``
the records for which the report is printed
``user``
the user printing the report
·
由于报表是标准网页,因此可以通过网址获得,并且可以通过此URL操纵输出参数,例如,通过http:// localhost:8069 / report / html / account.report_invoice可以使用HTML版本的“ 发票”报告 / 1(如果account已安装)和PDF版本通过http:// localhost:8069 / report / pdf / account.report_invoice / 1。
危险
如果您的PDF报告似乎缺少样式(即文本显示,但样式/布局与html版本不同),可能您的wkhtmltopdf进程无法访问您的Web服务器以进行下载。
如果检查您的服务器日志,并看到生成PDF报告时没有下载CSS样式,那么这绝对是问题所在。
该wkhtmltopdf过程将使用web.base.url系统参数的根路径如果您的服务器位于后面的一些类型的代理,这可能是不可到达的所有链接的文件,但此参数是每个管理员的登录时间自动更新。您可以通过添加以下系统参数来解决此问题:
· report.url指向可从您的服务器访问的URL(可能http://localhost:8069或类似的东西)。它将仅用于此特定目的。
· web.base.url.freeze,当设置为True,将停止自动更新web.base.url。
行使
为会话模型创建报告
对于每个会话,它应显示会话的名称,其开始和结束,并列出会话的与会者。
openacademy/__manifest__.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" />
id="report_session_view">
From t-field="doc.start_date"/> To t-field="doc.end_date"/> Attendees: t-field="doc.name"/>
行使
定义仪表板
定义包含您创建的图形视图,会话日历视图和课程列表视图(可切换到表单视图)的仪表板。该仪表板应通过菜单中的菜单进行显示,并在选择OpenAcademy主菜单时自动显示在Web客户端中。
1.
创建一个文件openacademy/views/session_board.xml。它应该包含板视图,该视图中引用的操作,打开仪表板的操作以及重新定义主菜单项以添加仪表板操作
注意
可用仪表盘样式是1,1-1,1-2, 2-1和1-1-1
2. 更新openacademy/__manifest__.py以引用新的数据文件
openacademy/__manifest__.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
ref="openacademy.openacademy_session_graph_view"/>
string="Attendees by course"
name="%(act_session_graph)d"
height="150"
width="510"/>
string="Sessions"
name="%(act_session_calendar)d"/>
string="Courses"
name="%(act_course_list)d"/>
name="Session Dashboard" parent="base.menu_reporting_dashboard"
action="open_board_session"
sequence="1"
id="menu_board_session" icon="terp-graph"/>
网络服务模块为所有网络服务提供通用接口:
· XML-RPC
· JSON-RPC
业务对象也可以通过分布式对象机制访问。它们都可以通过客户端界面进行上下文视图修改。
Odoo可以通过XML-RPC / JSON-RPC接口访问,对于哪些库以多种语言存在。
以下示例是与Odoo服务器与库进行交互的Python程序xmlrpclib:
import xmlrpclib
root = 'http://%s:%d/xmlrpc/' % (HOST, PORT)
uid = xmlrpclib.ServerProxy(root + 'common').login(DB, USER, PASS)
print "Logged in as %s (uid: %d)" % (USER, uid)
# Create a new note
sock = xmlrpclib.ServerProxy(root + 'object')
args = {
'color' : 8,
'memo' : 'This is a note',
'create_uid': uid,
}
note_id = sock.execute(DB, uid, PASS, 'note.note', 'create', args)
行使
向客户端添加新服务
编写一个能够将XML-RPC请求发送到运行Odoo(您的或您的教师)的PC的Python程序。这个程序应该显示所有的会话和他们相应的座位数。它也应该为其中一个课程创建一个新的课程。
import functools
import xmlrpclib
HOST = 'localhost'
PORT = 8069
DB = 'openacademy'
USER = 'admin'
PASS = 'admin'
ROOT = 'http://%s:%d/xmlrpc/' % (HOST,PORT)
# 1. Login
uid = xmlrpclib.ServerProxy(ROOT + 'common').
login(DB,USER,PASS)print "Logged in as %s (uid:%d)" % (USER,uid)
call = functools.partial(
xmlrpclib.ServerProxy(ROOT + 'object').execute,
DB, uid, PASS)
# 2. Read the sessions
sessions = call('openacademy.session','search_read', [], ['name','seats'])for session in sessions:
print "Session %s (%s seats)" % (session['name'], session['seats'])
# 3.create a new session
session_id = call('openacademy.session', 'create', {
'name' : 'My session',
'course_id' : 2,})
而不是使用硬编码的课程ID,代码可以通过名称查找课程:
# 3.create a new session for the "Functional" course
course_id = call('openacademy.course', 'search', [('name','ilike','Functional')])[0]
session_id = call('openacademy.session', 'create', {
'name' : 'My session',
'course_id' : course_id,})
rse_id' : course_id ,})
以下示例是使用标准Python库urllib2与Odoo服务器进行交互的Python程序json:
import json
import random
import urllib2
def json_rpc(url, method, params):
data = {
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": random.randint(0, 1000000000),
}
req = urllib2.Request(url=url, data=json.dumps(data), headers={
"Content-Type":"application/json",
})
reply = json.load(urllib2.urlopen(req))
if reply.get("error"):
raise Exception(reply["error"])
return reply["result"]
def call(url, service, method, *args):
return json_rpc(url, "call", {"service": service, "method": method, "args": args})
# log in the given database
url = "http://%s:%s/jsonrpc" % (HOST, PORT)uid = call(url, "common", "login", DB, USER, PASS)
# create a new note
args = {
'color' : 8,
'memo' : 'This is another note',
'create_uid': uid,}
note_id = call(url, "object", "execute", DB, uid, PASS, 'note.note', 'create', args)
这是同一个程序,使用库 jsonrpclib:
import jsonrpclib
# server proxy object
url = "http://%s:%s/jsonrpc" % (HOST, PORT)
server = jsonrpclib.Server(url)
# log in the given database
uid = server.call(service="common", method="login", args=[DB, USER, PASS])
# helper function for invoking model methods
def invoke(model, method, *args):
args = [DB, uid, PASS, model, method] + list(args)
return server.call(service="object", method="execute", args=args)
# create a new note
args = {
'color' : 8,
'memo' : 'This is another note',
'create_uid': uid,}
note_id = invoke('note.note', 'create', args)
XML-RPC到JSON-RPC可以轻松实现示例。
注意
有许多各种语言的高级API可以访问Odoo系统,而无需明确地通过XML-RPC或JSON-RPC,例如:
· https://github.com/akretion/ooor
· https://github.com/syleam/openobject-library
· https://github.com/nicolas-van/openerp-client-lib
· http://pythonhosted.org/OdooRPC
· https://github.com/abhishek-jaiswal/php-openerp-lib
[1]可以disable the automatic creation of some fields
[2]编写原始SQL查询是可行的,但需要小心,因为它绕过了所有的Odoo身份验证和安全机制。