odoo12官方文档翻译--Building a Module
<div id="post_detail">
odoo模块的创建 openacademy学习笔记
odoo -u academy -d academy
openacademy官网完整教程
- 练习创建模块
使用上面的命令行创建一个空模块Open Academy,并将其安装在Odoo中。
odoo scaffold openacademy myaddons
- 练习定义模型,
在openacademy模块中定义新的数据模型课程,每门课程包含两个字段,标题和描述,其中标题是必填字段。编辑文件openacademy/models/models.py
以包含Course
类。
openacademy/models/models.py
from odoo import models, fields, api
class Course(models.Model):
#定义了两个字段name,description
_name = 'openacademy.course'
name = fields.Char(string=<span class="hljs-string">"Title"</span>, required=<span class="hljs-keyword">True</span>)
description = fields.Text()
- 练习定义演示数据,
添加演示数据以填充Course模型的数据,编辑文件openacademy/demo/demo.xml
来添加演示数据
openacademy/demo/demo.xml
<odoo>
<data>
<record model="openacademy.course" id="course0">
<field name="name">Course 0</field>
<field name="description">Course 0's description
Can have multiple lines
</field>
</record>
<record model="openacademy.course" id="course1">
<field name="name">Course 1</field>
<!-- no description for this one -->
</record>
<record model="openacademy.course" id="course2">
<field name="name">Course 2</field>
<field name="description">Course 2's description</field>
</record>
</data>
</odoo>
demo.xml如何起作用?
必须添加到data中
'data': [
# 'security/ir.model.access.csv',
'views/views.xml',
'views/templates.xml',
'demo/demo.xml',
],
添加到demo中不起作用
- 练习定义新菜单项,
在开放学院菜单项下定义新菜单项来访问课程。用户应该能够:
显示所有课程的列表
建立或编辑课程
1.建立openacademy/views/openacademy.xml
以创建操作和能够触发操作的菜单项。
2.添加这个文件到openacademy/__manifest__.py
下的data列表。
编写菜单(menuitem)和行为(action)
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- window action -->
<!--
The following tag is an action definition for a "window action",
that is an action opening a view or a set of views
-->
<record model="ir.actions.act_window" id="course_list_action">
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">Create the first course
</p>
</field>
</record>
<span class="hljs-comment"><!-- top level menu: no parent --></span>
<span class="hljs-tag"><<span class="hljs-name">menuitem</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"main_openacademy_menu"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Open Academy"</span>/></span>
<span class="hljs-comment"><!-- A first level in the left side menu is needed
before using action= attribute --></span>
<span class="hljs-tag"><<span class="hljs-name">menuitem</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"openacademy_menu"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Open Academy"</span>
<span class="hljs-attr">parent</span>=<span class="hljs-string">"main_openacademy_menu"</span>/></span>
<span class="hljs-comment"><!-- the following menuitem should appear *after*
its parent openacademy_menu and *after* its
action course_list_action --></span>
<span class="hljs-tag"><<span class="hljs-name">menuitem</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"courses_menu"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Courses"</span> <span class="hljs-attr">parent</span>=<span class="hljs-string">"openacademy_menu"</span>
<span class="hljs-attr">action</span>=<span class="hljs-string">"course_list_action"</span>/></span>
<span class="hljs-comment"><!-- Full id location:
action="openacademy.course_list_action"
It is not required when it is the same module --></span>
<span class="hljs-tag"></<span class="hljs-name">data</span>></span>
</odoo>
- 练习使用XML定制窗体视图
建立课程对象的表单视图,显示课程的名称和描述字段。
- 练习notebook结构元素
在课程的表单视图中,将描述字段放在一个选项卡中,然后再添加选项卡放置其它字段。修改后的课程表单视图如下:
openacademy/views/openacademy.xml
<!--form视图 Course-->
<record model="ir.ui.view" id="view_form_openacademy_course">
<field name="name">course.form</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<form string="Course Form">
<sheet>
<group>
<field name="name"/>
<field name="description"/>
</group>
<!--notebook标签页-->
<notebook>
<!--描述-->
<page string="Description">
<field name="description"/>
</page>
<span class="hljs-tag"></<span class="hljs-name">notebook</span>></span>
<span class="hljs-tag"></<span class="hljs-name">sheet</span>></span>
<span class="hljs-tag"></<span class="hljs-name">form</span>></span>
<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span></code></pre>
notebook效果:
当然也可以用纯HTML页面写
- 练习搜索课程
通过标题和描述来搜索课程。
<!--搜索视图 Course-->
<record model="ir.ui.view" id="course_search_view">
<field name="name">course.search</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<search>
<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'}"/>
</group>
</search>
</field>
</record>
- 练习建立一个授课模型
在开放学院模块中,我们考虑一个授课模型:一个授课是在给定的时间中对给定的受众教授指定的课程。为授课建立模型,授课包括名称、开始时间、持续时间和席位数。添加操作和菜单项来显示新的模型。
openacademy/models.py
class Session(models.Model):
_name = 'openacademy.session'
name = fields.<span class="hljs-built_in">Char</span>(required=<span class="hljs-literal">True</span>)
start_date = fields.<span class="hljs-built_in">Date</span>()
duration = fields.Float(digits=(<span class="hljs-number">6</span>, <span class="hljs-number">2</span>), help=<span class="hljs-string">"Duration in days"</span>)
seats = fields.<span class="hljs-built_in">Integer</span>(<span class="hljs-built_in">string</span>=<span class="hljs-string">"Number of seats"</span>)
openacademy/views/openacademy.xml
<!-- session form view -->
<record model="ir.ui.view" id="session_form_view">
<field name="name">session.form</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<form string="Session Form">
<sheet>
<group>
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
</group>
</sheet>
</form>
</field>
</record>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.act_window"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"session_list_action"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Sessions<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"res_model"</span>></span>openacademy.session<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"view_type"</span>></span>form<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"view_mode"</span>></span>tree,form<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"><<span class="hljs-name">menuitem</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"session_menu"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Sessions"</span>
<span class="hljs-attr">parent</span>=<span class="hljs-string">"openacademy_menu"</span>
<span class="hljs-attr">action</span>=<span class="hljs-string">"session_list_action"</span>/></span>
- 练习Many2one关联
编辑Course和Session模型以反映他们与其它模型的关联:
- 课程有一个负责的用户;该字段的值是内置模型
res.users
的记录
#class Course(models.Model)
responsible_id = fields.Many2one('res.users',
ondelete='set null', string="Responsible", index=True)
- 一个授课有一个教师;该字段的值是内置模型
res.partner
的记录
#class Session(models.Model)
instructor_id = fields.Many2one('res.partner', string="Instructor")
- 授课与课程相关;该字段的值是
openacademy.course
模型的记录,并且是必填项
#class Session(models.Model)
course_id = fields.Many2one('openacademy.course',
ondelete='cascade', string="Course", required=True)
- 在模型中添加Many2one关联,并在视图显示
#form视图
<record model="ir.ui.view" id="view_form_openacademy_course">
<field name="name">course.form</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<form string="Course Form">
<sheet>
<group>
<field name="name"/>
<field name="description"/>
<field name="responsible_id"/>
</group>
<!--notebook标签页-->
<notebook>
<!--描述-->
<page string="Description">
<field name="description"/>
</page>
<span class="hljs-tag"></<span class="hljs-name">notebook</span>></span>
<span class="hljs-tag"></<span class="hljs-name">sheet</span>></span>
<span class="hljs-tag"></<span class="hljs-name">form</span>></span>
<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span></code></pre>
![](http://img0.ph.126.net/JmsGlBtv6KtFa384jKQ5EQ==/6597951574984339803.png)
<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="description"/>
<field name="responsible_id"/>
</tree>
</field>
</record>
多对一关系
![](http://img1.ph.126.net/pmj5BGARsrcgJ8NZK9HthA==/6597611825891349565.png)
关联字段
Many2one(other_model, ondelete='set null')
多对一字段
一个链接到其它对象的简单示例是这样的:
print foo.other_id.name
One2many(other_model, related_field)
一对多模型,通过for循环遍历
这是一个虚拟的关联,是Many2one的逆,One2many作为记录的容器,访问它将返回一个记录集(也可能是一个空记录集):
for other in foo.other_ids:
print other.name
练习逆关联One2many
使用逆关联字段one2many,编辑模型以反映课程和授课之间的关系。
编辑Course类,并且加入字段到它的表单视图
#class Course(models.Model)
session_ids = fields.One2many(
'openacademy.session', 'course_id', string="Sessions")
<!--form视图 Course-->
<record model="ir.ui.view" id="course_form_view">
<field name="name">course.form</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<form string="Course Form">
<sheet>
<group>
<field name="name"/>
<field name="responsible_id"/>
</group>
<!--notebook标签页-->
<notebook>
<!--描述-->
<page string="Description">
<field name="description"/>
</page>
<!--域-->
<page string="Sessions">
<field name="session_ids">
<tree string="Registered sessions">
<field name="name"/>
<!--instructor_id授课教师与课程的捆绑-->
<field name="instructor_id"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
notebook中添加了session
![](http://img2.ph.126.net/qL4q96NcpNhaUKiBbYOQlw==/6631585635679937972.png)
![](http://img1.ph.126.net/-5CR3YQD-7Swa21-lDSO-w==/2594917810315835968.png)
练习多对多关联many2many
在授课模型中添加关联字段many2many,将每次授课和参与的听众做关联,听众来自于内置模型res.partner
。相应的调整对应的视图。
- 修改Session类并且加入字段到它的表单视图中
# class Session(models.Model)
attendee_ids = fields.Many2many('res.partner', string="Attendees")
视图
<record model="ir.ui.view" id="session_form_view">
<field name="name">session.form</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<form string="Session Form">
<sheet>
<group>
<field name="name"/>
<field name="start_date"/>
<field name="duration"/>
<field name="seats"/>
</group>
<!--授课和参与的听众做关联,听众来自于内置模型res.partner-->
<label for="attendee_ids"/>
<field name="attendee_ids"/>
</sheet>
</form>
</field>
</record>
![](http://img0.ph.126.net/ULrHilR4Hsh5TicPaXS5iw==/5717512141482179649.png)
![](http://img1.ph.126.net/MrNCdC7JgNOlNNRBsEinGQ==/3897584002613023177.png)
练习更改现有内容
- 使用模型继承,修改现有partner模型,添加
instructor
布尔字段,以及对应表示"授课-讲师"关联的many2many字段
使用视图继承在partner的表单视图中显示这个字段
注意,这里是通过开发人员模式来查找视图外部ID并放置新字段的。
- 创建文件openacademy/models/partner.py并将其导入__init__.py
- 创建文件openacademy/views/partner.xml并将其添加到__manifest__.py
res.partner表的路径
\odoo\odoo10\odoo\addons\base\res\res_partner.py
\odoo\odoo10\odoo\addons\product\views\res_partner_views.xml
openacademy/models/init.py
from . import models
from . import partner
openacademy/manifest.py
# always loaded
'data': [
# 'security/ir.model.access.csv',
'views/openacademy.xml',
'views/templates.xml',
'views/partner.xml',
'demo/demo.xml',
],
openacademy/partner.py
# -*- coding: utf-8 -*-
from odoo import fields, models
class Partner(models.Model):
_inherit = 'res.partner'
<span class="hljs-comment"># Add a new column to the res.partner model, by default partners are not</span>
<span class="hljs-comment"># instructors</span>
instructor = fields.Boolean(<span class="hljs-string">"Instructor"</span>, default=<span class="hljs-keyword">False</span>)
session_ids = fields.Many2many(<span class="hljs-string">'openacademy.session'</span>,
string=<span class="hljs-string">"Attended Sessions"</span>, readonly=<span class="hljs-keyword">True</span>)
openacademy/views/partner.xml
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<!-- Add instructor field to existing view -->
<!--form视图-->
<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">
<group>
<field name="instructor"/>
<field name="session_ids"/>
</group>
</page>
</notebook>
</field>
</record>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.act_window"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"contact_list_action"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Contacts<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"res_model"</span>></span>res.partner<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"view_mode"</span>></span>tree,form<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"><<span class="hljs-name">menuitem</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"configuration_menu"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Configuration"</span>
<span class="hljs-attr">parent</span>=<span class="hljs-string">"main_openacademy_menu"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">menuitem</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"contact_menu"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Contacts"</span>
<span class="hljs-attr">parent</span>=<span class="hljs-string">"configuration_menu"</span>
<span class="hljs-attr">action</span>=<span class="hljs-string">"contact_list_action"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.act_window"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"contact_cat_list_action"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Contact Tags<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"res_model"</span>></span>res.partner.category<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"view_mode"</span>></span>tree,form<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"><<span class="hljs-name">menuitem</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"contact_cat_menu"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Contact Tags"</span>
<span class="hljs-attr">parent</span>=<span class="hljs-string">"configuration_menu"</span>
<span class="hljs-attr">action</span>=<span class="hljs-string">"contact_cat_list_action"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"res.partner.category"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"teacher1"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Teacher / Level 1<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"res.partner.category"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"teacher2"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Teacher / Level 2<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"></<span class="hljs-name">data</span>></span>
</odoo>
<field name="inherit_id" ref="base.view_partner_form"/>
视图继承
\odoo\odoo10\odoo\addons\product\views\res_partner_views.xml
- 练习在关联字段上使用Domain,
当为授课选取讲师时,只有instructor值为True的讲师会被显示出来。
注意
声明为文字列表的domain会在服务端进行计算,不会出现在右侧的动态列表中,而声明为字符串的domain是在客户端进行计算的,字段名将出现在右侧列表。
#class Session(models.Model)
instructor_id = fields.Many2one('res.partner', string="Instructor",
domain=[('instructor', '=', True)])
- 练习更复杂的domain,创建新的partner类别Techer/Level1和Techer/Level2.一个授课的教授人可以是讲师或者任意级别的教师。
<!-- Add instructor field to existing view -->
<!--form视图-->
<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">
<group>
<field name="instructor"/>
<field name="session_ids"/>
</group>
</page>
</notebook>
</field>
</record>
<!--定义行为-->
<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>
</record>
<!--菜单-->
<menuitem id="configuration_menu" name="Configuration"
parent="main_openacademy_menu"/>
<menuitem id="contact_menu" name="Contacts"
parent="configuration_menu"
action="contact_list_action"/>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.act_window"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"contact_cat_list_action"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Contact Tags<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"res_model"</span>></span>res.partner.category<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"view_mode"</span>></span>tree,form<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"><<span class="hljs-name">menuitem</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"contact_cat_menu"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Contact Tags"</span>
<span class="hljs-attr">parent</span>=<span class="hljs-string">"configuration_menu"</span>
<span class="hljs-attr">action</span>=<span class="hljs-string">"contact_cat_list_action"</span>/></span>
<span class="hljs-comment"><!-- 定义分类--></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"res.partner.category"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"teacher1"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Teacher / Level 1<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"res.partner.category"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"teacher2"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Teacher / Level 2<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span></code></pre>
视图继承路径\odoo\odoo10\odoo\addons\product\views\res_partner_views.xml
![](http://img2.ph.126.net/F0_0P8hjYr0vqgLl4salRg==/6631518565470851192.png)
![](http://img1.ph.126.net/ZSU5OAulzTcG2y9lM4k4MQ==/6597662403426614536.png)
![](http://img1.ph.126.net/XcFf-ue79XUy1TXGTItZQQ==/836825105861350081.png)
![](http://img1.ph.126.net/VN60KG3oBfSispg0lp4GCw==/6597957072542479201.png)
练习计算字段
- 加入座席占用百分比字段到授课模型。
- 在列表视图和表单视图中显示这个字段
以进度条的方式显示这个字段
#class Session(models.Model)
taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')
@api.depends(<span class="hljs-string">'seats'</span>, <span class="hljs-string">'attendee_ids'</span>) <span class="hljs-comment">#依赖的任一字段变化时(ORM or Form),触发该函数执行</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_taken_seats</span><span class="hljs-params">(<span class="hljs-keyword">self</span>)</span></span>:
<span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> <span class="hljs-symbol">self:</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> r.<span class="hljs-symbol">seats:</span>
r.taken_seats = <span class="hljs-number">0</span>.<span class="hljs-number">0</span>
<span class="hljs-symbol">else:</span>
r.taken_seats = <span class="hljs-number">100.0</span> * len(r.attendee_ids) / r.seats
<field name="taken_seats" widget="progressbar"/>
progresbar进度条
![](http://img0.ph.126.net/_8LiHKW4nGYuirbNUMtH0Q==/5717505544412413509.png)
- 练习默认值
定义start_date
默认值为今天
在授课类添加字段active
,并且设置其默认值为True
class Session(models.Model):
start_date = fields.Date(default=fields.Date.today)
active = fields.Boolean(default=True)
注意
Odoo 有内置规则:active字段值为False时记录不可见
- 练习
通过"onchange"机制显示的验证无效值,例如座位数为负数或者座位数多与参与者。
#class Session(models.Model)
@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",
},
}
![](http://img2.ph.126.net/y7Ltkyjpu3xhKTvJ7DN04w==/6631727472679714865.png)
![](http://img1.ph.126.net/uhRwwkcml9XSUcpzN_b0yw==/6597745966309938904.png)
- 练习添加Python约束,讲师不能在自己的授课出席人中
import exceptions
#class Session(models.Model):
@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")
![](http://img1.ph.126.net/E0u8UM1n4JMUF30o6ovaSw==/765048986800098469.png)
- 练习添加重复项,因为我们为课程名称添加了唯一性约束,所以不能再使用"复制"功能(表单->复制)。重写"复制"方法,允许复制课程对象,将原始名称更改为"原始名称的副本"。
#class Course(models.Model)
@api.multi
def copy(self, default=None):
default = dict(default or {})
print '--------------'
print self.name
print '--------------'
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[<span class="hljs-string">'name'</span>] = new_name
<span class="hljs-keyword">return</span> super(Course, self).copy(default)</code></pre>
<record model="ir.ui.view" id="session_form_view">
<field name="name">session.form</field>
<field name="model">openacademy.session</field>
<field name="arch" type="xml">
<form string="Session Form">
<!--<sheet>-->
<!--<group>-->
<!--<field name="course_id"/>-->
<!--<field name="name"/>-->
<!--<field name="start_date"/>-->
<!--<field name="duration"/>-->
<!--<field name="seats"/>-->
<!--<field name="instructor_id"/>-->
<!--</group>-->
<!--<!–授课和参与的听众做关联,听众来自于内置模型res.partner–>-->
<!--<label for="attendee_ids"/>-->
<!--<field name="attendee_ids"/>-->
<!--</sheet>-->
<sheet>
<group>
<group string="General">
<field name="course_id"/>
<field name="name"/>
<field name="instructor_id"/>
<field name="active"/>
</group>
<group string="Schedule">
<field name="start_date"/>
<!--<field name="end_date"/>-->
<field name="duration"/>
<!--<field name="hours"/>-->
<field name="seats"/>
<field name="taken_seats" widget="progressbar"/>
</group>
</group>
<label for="attendee_ids"/>
<field name="attendee_ids"/>
</sheet>
</form>
</field>
</record>
打印出来的name,default
--------------
语文
--------------
{'name': u'Copy of \u8bed\u6587'}
![](http://img0.ph.126.net/_8LiHKW4nGYuirbNUMtH0Q==/5717505544412413509.png)
![](http://img0.ph.126.net/IyFxfrUoArAArWmx3-C7YQ==/1637058464649567732.png)
![](http://img0.ph.126.net/67HfOzjIH1JQva6pQIgWXg==/6631469087447611122.png)
-----
高级视图
- 练习列表着色
编辑授课的树视图,使得持续时间少于5天的授课以蓝色显示,持续时间超过15天的授课以红色显示。编辑授课的树视图:
<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" 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"/>
<field name="state"/>
</tree>
</field>
</record>
![](http://img0.ph.126.net/R5WjsudP2tvAy375Klow1g==/1416945032861879718.png)
练习日历视图
给授课模型添加一个日历视图,使用户可以查看与开放学院相关联的事件。
- 添加一个计算字段
end_date
,通过start_date
和duration
计算获得。
- 反函数使字段可写,并允许在日历视图中移动授课(通过拖放操作)
- 向授课模型添加日历视图
添加日历视图到授课模型的动作中
models.py
from datetime import timedelta
# class Session(models.Model)
end_date = fields.Date(string="End Date", store=True,
compute='_get_end_date', inverse='_set_end_date') #结束日期
@api.depends('start_date', 'duration') #计算end_date结束日期
def _get_end_date(self):
for r in self:
if not (r.start_date and r.duration):
r.end_date = r.start_date
continue
<span class="hljs-comment"># Add duration to start_date, but: Monday + 5 days = Saturday, so</span>
<span class="hljs-comment"># subtract one second to get on Friday instead</span>
start = fields.Datetime.from_string(r.start_date)
duration = timedelta(days=r.duration, seconds=-<span class="hljs-number">1</span>)
r.end_date = start + duration
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_set_end_date</span><span class="hljs-params">(<span class="hljs-keyword">self</span>)</span></span>:
<span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> <span class="hljs-symbol">self:</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> (r.start_date <span class="hljs-keyword">and</span> r.end_date):
continue
<span class="hljs-comment"># Compute the difference between dates, but: Friday - Monday = 4 days,</span>
<span class="hljs-comment"># so add one day to get 5 days instead</span>
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 + <span class="hljs-number">1</span>
![](http://img1.ph.126.net/G1gQ_OLnydGor2oZX69TAQ==/6597946077426264970.png)
练习搜索视图
- 在课程搜索视图中添加按钮,用以筛选当前用户负责的课程,并且作为默认选择。
再添加一个分组按钮,用于对当前用户负责的课程进行分组。
<!--搜索视图 Course-->
<record model="ir.ui.view" id="course_search_view">
<field name="name">course.search</field>
<field name="model">openacademy.course</field>
<field name="arch" type="xml">
<search>
<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'}"/>
</group>
</search>
</field>
</record>
<!--动作定义-->
<record model="ir.actions.act_window" id="course_list_action">
<field name="name">Courses</field>
<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
</p>
</field>
</record>
![](http://img1.ph.126.net/FyLpIyqxCBhjJNb-HI4Raw==/6631736268772737415.png)
- 练习甘特图
添加甘特图使用户可以查看授课的日程排期,授课将按讲师分组。
- 创建一个计算字段,表示以小时计算的授课持续时间
- 添加甘特图,并且将甘特图添加到授课模型的action上。
<!--甘特图-->
<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"/>
</gantt>
</field>
</record>
![](http://img0.ph.126.net/UX2QzXi57vesfxh_rNeEkw==/2038160306462320074.png)
练习图形视图
在授课对象中添加图形视图,为每个课程在条形视图下显示出席人数。
- 添加字段将出席人数这计算字段存储在数据库
添加相关图形视图
<!--图形视图-->
<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"/>
</graph>
</field>
</record>
![](http://img1.ph.126.net/oVFG2I_I6F6Wr8VLlubnzQ==/1387953110260642558.png)
练习看板视图
添加显示按课程分组的授课看板视图(列是课程)
- 授课模型中添加整型字段color
- 添加看板视图并更新action
color = fields.Integer()
<!--看板视图-->
<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">
<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">
<!-- dropdown menu -->
<div class="oe_dropdown_toggle">
<i class="fa fa-bars fa-lg"/>
<ul class="oe_dropdown_menu">
<li>
<a type="delete">Delete</a>
</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">
<!-- title -->
Session name:
<field name="name"/>
<br/>
Start date:
<field name="start_date"/>
<br/>
duration:
<field name="duration"/>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.act_window"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"session_list_action"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Sessions<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"res_model"</span>></span>openacademy.session<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"view_type"</span>></span>form<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"view_mode"</span>></span>tree,form,calendar,gantt,graph,kanban<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span></span></code></pre>
![](http://img1.ph.126.net/bkYfeG-fonBunNIOtuf96g==/5717512141482180373.png)
----
工作流和安全
- 练习伪工作流
在授课模型上添加一个字段state,用于定义一个工作流程。授课存在三个可能的状态:Draft(草稿,默认值)、Confirmed(已确认)、Done(已完成)。在授课的form视图中,添加一个只读字段用于显示课程状态,并可以通过按钮来改变状态。有效的状态值迁移包括:
- Draft->Confirmed
- Confirmed->Draft
- Confirmed->Done
- Done->Draft
- 添加一个新的字段state
- 添加状态迁移方法,这个方法可以被form表单的按钮所调用,用以更改授课的状态
- 将相关按钮添加到授课的form视图
- openacademy/models.py
#class Session(models.Model):
state = fields.Selection([
('draft', "Draft"),
('confirmed', "Confirmed"),
('done', "Done"),
], default='draft') #工作流状态
<odoo>
<data>
<!--工作流-->
<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>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.server"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"set_session_to_draft"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Set session to Draft<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"model_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"model_openacademy_session"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"code"</span>></span>
model.search([('id', 'in', context['active_ids'])]).action_draft()
<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-comment"><!--工作流活动--></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"workflow.activity"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"draft"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Draft<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"wkf_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"wkf_session"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"flow_start"</span> <span class="hljs-attr">eval</span>=<span class="hljs-string">"True"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"kind"</span>></span>dummy<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"action"</span>></span><span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"action_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"set_session_to_draft"</span>/></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-comment"><!--服务器--></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.server"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"set_session_to_confirmed"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Set session to Confirmed<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"model_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"model_openacademy_session"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"code"</span>></span>
model.search([('id', 'in', context['active_ids'])]).action_confirm()
<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"workflow.activity"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"confirmed"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Confirmed<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"wkf_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"wkf_session"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"kind"</span>></span>dummy<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"action"</span>></span><span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"action_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"set_session_to_confirmed"</span>/></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.server"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"set_session_to_done"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Set session to Done<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"model_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"model_openacademy_session"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"code"</span>></span>
model.search([('id', 'in', context['active_ids'])]).action_done()
<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"workflow.activity"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"done"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Done<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"wkf_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"wkf_session"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"kind"</span>></span>dummy<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"action"</span>></span><span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"action_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"set_session_to_done"</span>/></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<!-- 工作流流转 -->
<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>
<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>
<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>
<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>
</record>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"workflow.transition"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"session_auto_confirm_half_filled"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"act_from"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"draft"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"act_to"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"confirmed"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"condition"</span>></span>taken_seats &gt; 50<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"></<span class="hljs-name">data</span>></span>
</odoo>
练习工作流
使用真正的授课工作流替换之前的伪工作流。修改授课的form视图,按钮将调用工作流而不是调用模型的方法。
练习自动状态迁移
当超过一半座席被保留时,自动将授课的状态从Draft迁移到Confirmed。
当定义了工作流,需要卸载模块,否则原来定义好的数据无法进入工作流(可以直接修改数据库)
<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>
</record>
</data>
</odoo>
- 练习服务器动作
用服务器动作替换用于同步授课状态的Python方法。工作流和服务器动作都可以从UI创建。
<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()
</field>
</record>
<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>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.server"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"set_session_to_confirmed"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Set session to Confirmed<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"model_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"model_openacademy_session"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"code"</span>></span>
model.search([('id', 'in', context['active_ids'])]).action_confirm()
</field>
</record>
<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>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.actions.server"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"set_session_to_done"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Set session to Done<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"model_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"model_openacademy_session"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"code"</span>></span>
model.search([('id', 'in', context['active_ids'])]).action_done()
</field>
</record>
<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>
![](http://img2.ph.126.net/TOoOtW2PqdUjnCPNXlYJ3g==/797700084098495045.png)
![](http://img1.ph.126.net/Iul1lsFdp_Ul0hahDxwQkw==/6631617521517010408.png)
- 练习
通过Odoo界面添加访问控制权限
建立一个新用户John Smit
,然后建立OpenAcademy/Session Read
组,并赋予这个组对授课模型的读权限。
- 建立一个新用户
John Smit
通过 设置->用户->用户
- 建立一个新组
session_read
通过 设置->用户->组,这个组拥有对授课模型的读权限
- 编辑
John Smith
用户,把他加入到session_read
组
- 以
John Smith
身份登录系统,检查权限是否正确。
![](http://img2.ph.126.net/ycIEOIRCZZKTr36Zpx4ELA==/2019864432976220705.png)
![](http://img1.ph.126.net/nYN8TL5CPLsgVw2Jb23lbg==/6599297377216290166.png)
![](http://img2.ph.126.net/hsjhKZXJP9SKsX0Hm9FVVg==/6597397421124215402.png)
![](http://img2.ph.126.net/hsjhKZXJP9SKsX0Hm9FVVg==/6597397421124215402.png)
练习
通过数据文件添加访问控制权限:
- 建立一个组OpenAcademy / Manager,这个组对开放学院的所有模型都有完全权限。
- 让Session和Course对所有用户可读
- 建立新的文件openacademy/security/security.xml用来定义OpenAcademy Manager组
- 编辑文件openacademy/security/ir.model.access.csv来添加对模型的访问权限
- 最后更新openacademy/manifest.py来添加新的数据文件
openacademy/__manifest__.py
注意security.xml顺序,优先加载security.xml 然后加载csv,否则报错
Exception: Module loading openacademy failed: file openacademy/security/ir.model.access.csv could not be processed:
在字段'Group'中没找到匹配的记录外部id 'group_manager'
在字段'Group'中没找到匹配的记录外部id 'group_manager'
在字段'Object'中没找到匹配的记录外部id 'model_scheduler_demo'
在字段'Group'中没找到匹配的记录外部id 'group_manager'
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'views/openacademy.xml',
'views/templates.xml',
'views/partner.xml',
'views/session_workflow.xml',
'demo/demo.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
<odoo>
<data>
<record id="group_manager" model="res.groups">
<field name="name">OpenAcademy / Manager</field>
</record>
</data>
</odoo>
只能可读,不可修改
![](http://img1.ph.126.net/tpmKaTlsv2-jKrSohOqDQg==/3254132205852355468.png)
![](http://img0.ph.126.net/yHFtNZ_q5W2Oq9RNvInYeg==/6597559049333229711.png)
- 练习记录规则
为授课模型和OpenAcademy / Manager组添加记录规则,这个记录规则限制只有课程负责人可以对课程进行write和unlink操作,如果课程还没有负责人,这个组的所有用户都可以编辑它。在openacademy/security/security.xml
文件中创建新的规则:
openacademy/security/security.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="group_manager" model="res.groups">
<field name="name">OpenAcademy / Manager</field>
</record>
<span class="hljs-tag"><<span class="hljs-name">record</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"only_responsible_can_modify"</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"ir.rule"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span>></span>Only Responsible can modify Course<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"model_id"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"model_openacademy_course"</span>/></span> #模型课程分组
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"groups"</span> <span class="hljs-attr">eval</span>=<span class="hljs-string">"[(4, ref('openacademy.group_manager'))]"</span>/></span> # (4,id,_) 连接一个已经存在的记录
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"perm_read"</span> <span class="hljs-attr">eval</span>=<span class="hljs-string">"0"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"perm_write"</span> <span class="hljs-attr">eval</span>=<span class="hljs-string">"1"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"perm_create"</span> <span class="hljs-attr">eval</span>=<span class="hljs-string">"0"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"perm_unlink"</span> <span class="hljs-attr">eval</span>=<span class="hljs-string">"1"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">field</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"domain_force"</span>></span>
['|', ('responsible_id','=',False),
('responsible_id','=',user.id)]
<span class="hljs-tag"></<span class="hljs-name">field</span>></span>
<span class="hljs-tag"></<span class="hljs-name">record</span>></span>
<span class="hljs-tag"></<span class="hljs-name">data</span>></span>
</odoo>
只有添加到用户组里面才可以进行读写
向导与国际化
-------
- 练习定义向导
创建一个向导模型,这个向导模型通过many2one关联授课模型,并通过many2many关联合作伙伴模型。添加新文件openacademy/wizard.py
openacademy/models/__init__.py
from . import models
from . import partner
from . import wizard
openacademy/wizard.py
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class Wizard(models.TransientModel):
_name = 'openacademy.wizard'
session_ids = fields.Many2one(<span class="hljs-string">'openacademy.session'</span>,
string=<span class="hljs-string">"Session"</span>, required=<span class="hljs-keyword">True</span>)
attendee_ids = fields.Many2many(<span class="hljs-string">'res.partner'</span>, string=<span class="hljs-string">"Attendees"</span>)
- 练习启动向导
- 为向导定义一个form视图
- 在授课模型的上下文中添加action用于启动向导
- 给向导的session字段定义默认值;使用上下文参数self._context来获取当前授课
openacademy/wizard.py
class Wizard(models.TransientModel):
_name = 'openacademy.wizard'
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_default_session</span><span class="hljs-params">(<span class="hljs-keyword">self</span>)</span></span>:
<span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>.env[<span class="hljs-string">'openacademy.session'</span>].browse(<span class="hljs-keyword">self</span>._context.get(<span class="hljs-string">'active_id'</span>))
session_ids = fields.Many2one(<span class="hljs-string">'openacademy.session'</span>,
string=<span class="hljs-string">"Session"</span>, required=True, default=_default_session)
attendee_ids = fields.Many2many(<span class="hljs-string">'res.partner'</span>, string=<span class="hljs-string">"Attendees"</span>)
openacademy/views/openacademy.xml
<!--定义向导视图-->
<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">
<group>
<field name="session_ids"/>
<field name="attendee_ids"/>
</group>
<!--底部-->
<footer>
<!--订阅按钮-->
<button name="subscribe" type="object"
string="Subscribe" class="oe_highlight"/>
<!--取消按钮-->
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
<span class="hljs-comment"><!--定义向导行为--></span>
<span class="hljs-comment"><!--关联session 定义wizard--></span>
<span class="hljs-tag"><<span class="hljs-name">act_window</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"launch_session_wizard"</span>
<span class="hljs-attr">name</span>=<span class="hljs-string">"Add Attendees"</span>
<span class="hljs-attr">src_model</span>=<span class="hljs-string">"openacademy.session"</span>
<span class="hljs-attr">res_model</span>=<span class="hljs-string">"openacademy.wizard"</span>
<span class="hljs-attr">view_mode</span>=<span class="hljs-string">"form"</span>
<span class="hljs-attr">target</span>=<span class="hljs-string">"new"</span>
<span class="hljs-attr">key2</span>=<span class="hljs-string">"client_action_multi"</span>/></span></code></pre>
![](http://img1.ph.126.net/7ycdjTWT7xaavJcD-kQVBQ==/3018819125322303808.png)
![](http://img0.ph.126.net/uaJl36D1zia3Nb2A95V0JQ==/1612851616652379198.png)
- 练习注册与会者
给向导添加按钮,并且实现相应的方法,将与会者添加到给定的授课。
<footer>
<!--订阅按钮-->
<button name="subscribe" type="object"
string="Subscribe" class="oe_highlight"/>
<!--取消按钮-->
<button special="cancel" string="Cancel"/>
</footer>
- 练习与会者注册多个授课
修改向导模型,以便与会者可以注册到多个授课
class Wizard(models.TransientModel):
_name = 'openacademy.wizard'
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_default_sessions</span><span class="hljs-params">(<span class="hljs-keyword">self</span>)</span></span>:
<span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>.env[<span class="hljs-string">'openacademy.session'</span>].browse(<span class="hljs-keyword">self</span>._context.get(<span class="hljs-string">'active_ids'</span>))
print <span class="hljs-string">'------------------------'</span>
print dir(<span class="hljs-keyword">self</span>)
print <span class="hljs-string">'------------------------'</span>
<span class="hljs-comment">#字段</span>
session_ids = fields.Many2one(<span class="hljs-string">'openacademy.session'</span>,
string=<span class="hljs-string">"Session"</span>, required=True)
attendee_ids = fields.Many2many(<span class="hljs-string">'res.partner'</span>, string=<span class="hljs-string">"Attendees"</span>)
@api.multi
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">subscribe</span><span class="hljs-params">(<span class="hljs-keyword">self</span>)</span></span>:
<span class="hljs-keyword">for</span> session <span class="hljs-keyword">in</span> <span class="hljs-keyword">self</span>.<span class="hljs-symbol">session_ids:</span>
session.attendee_ids <span class="hljs-params">|= <span class="hljs-keyword">self</span>.attendee_ids
<span class="hljs-keyword">return</span> {}
- 练习
翻译一个模块
为已经安装的Odoo模块选择第二语言。使用Odoo提供的功能对模块进行翻译。
- 创建目录
openacademy/i18n/
- 安装任意一种你希望的语言 (设置->翻译->加载翻译 )
- 同步翻译术语(设置->翻译->应用程序术语->同步术语)
- 导出不指定语言的翻译模板文件(设置->翻译->导入/导出->导出翻译),保存在
openacademy/i18n/
- 导出指定语言的翻译文件(设置->翻译->导入/导出->导出翻译),保存在
openacademy/i18n/
- 打开导出的翻译文件(使用任意一款文本编辑软件或者专用的PO文件编辑器,例如POEdit然后翻译其中的术语)
- 在
models.py
文件中,为odoo._
方法添加一个导入声明,并且标记需要翻译的字符串
- 重复3-6的步骤
实际上添加i18n这个文件夹,放入导出的po文件zh_cn.po文件,必须重新安装模块后,整个翻译才会生效
openacademy/models.py
下划线翻译相应的浏览器提示
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"))
![](http://img1.ph.126.net/yQR5Yye2-PKyDwONmO4--A==/1285777693634599636.png)
-----
报表和WebService
- 练习创建授课模型的报表
对于每个授课,报表都将显示它的授课名称,开始和结束时间,以及课程出席人列表。
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<!--定义报表-->
<report
id="report_session"
model="openacademy.session"
string="Session Report"
name="openacademy.report_session_view"
file="openacademy.report_session"
report_type="qweb-pdf" />
<span class="hljs-tag"><<span class="hljs-name">template</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"report_session_view"</span>></span>
<span class="hljs-comment"><!--调用子模板--></span>
<span class="hljs-tag"><<span class="hljs-name">t</span> <span class="hljs-attr">t-call</span>=<span class="hljs-string">"report.html_container"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">t</span> <span class="hljs-attr">t-foreach</span>=<span class="hljs-string">"docs"</span> <span class="hljs-attr">t-as</span>=<span class="hljs-string">"doc"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">t</span> <span class="hljs-attr">t-call</span>=<span class="hljs-string">"report.external_layout"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page"</span>></span>
<span class="hljs-comment"><!--只能用于格式化记录字段--></span>
<span class="hljs-tag"><<span class="hljs-name">h2</span> <span class="hljs-attr">t-field</span>=<span class="hljs-string">"doc.name"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">p</span>></span>From <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">t-field</span>=<span class="hljs-string">"doc.start_date"</span>/></span> to <span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">t-field</span>=<span class="hljs-string">"doc.end_date"</span>/></span><span class="hljs-tag"></<span class="hljs-name">p</span>></span>
<span class="hljs-tag"><<span class="hljs-name">h3</span>></span>Attendees:<span class="hljs-tag"></<span class="hljs-name">h3</span>></span>
<span class="hljs-tag"><<span class="hljs-name">ul</span>></span>
<span class="hljs-tag"><<span class="hljs-name">t</span> <span class="hljs-attr">t-foreach</span>=<span class="hljs-string">"doc.attendee_ids"</span> <span class="hljs-attr">t-as</span>=<span class="hljs-string">"attendee"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">li</span>></span><span class="hljs-tag"><<span class="hljs-name">span</span> <span class="hljs-attr">t-field</span>=<span class="hljs-string">"attendee.name"</span>/></span><span class="hljs-tag"></<span class="hljs-name">li</span>></span>
<span class="hljs-tag"></<span class="hljs-name">t</span>></span>
<span class="hljs-tag"></<span class="hljs-name">ul</span>></span>
<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"></<span class="hljs-name">t</span>></span>
<span class="hljs-tag"></<span class="hljs-name">t</span>></span>
<span class="hljs-tag"></<span class="hljs-name">t</span>></span>
<span class="hljs-tag"></<span class="hljs-name">template</span>></span>
<span class="hljs-tag"></<span class="hljs-name">data</span>></span>
</odoo>
- 练习定义一个仪表盘
定义一个仪表盘,这个仪表盘包含了已经建立的图形视图、授课日历视图、课程列表视图(可以选择form视图)。这个仪表盘可以通过菜单中的菜单项使用,并且当选择开放学院主菜单时自动显示在web客户端。
'version': '0.1',
<span class="hljs-comment"># any module necessary for this one to work correctly</span>
<span class="hljs-string">'depends'</span>: [<span class="hljs-string">'base'</span>, <span class="hljs-string">'board'</span>],
<span class="hljs-comment"># always loaded</span>
<span class="hljs-string">'data'</span>: [
<span class="hljs-string">'views/openacademy.xml'</span>,
<span class="hljs-string">'views/partner.xml'</span>,
<span class="hljs-string">'views/session_workflow.xml'</span>,
<span class="hljs-string">'views/session_board.xml'</span>,
<span class="hljs-string">'reports.xml'</span>,
],
<?xml version="1.0"?>
<odoo>
<data>
<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>
<record model="ir.actions.act_window" id="act_session_calendar">
<field name="name">Sessions</field>
<field name="res_model">openacademy.session</field>
<field name="view_type">form</field>
<field name="view_mode">calendar</field>
<field name="view_id" ref="openacademy.session_calendar_view"/>
</record>
<record model="ir.actions.act_window" id="act_course_list">
<field name="name">Courses</field>
<field name="res_model">openacademy.course</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<record model="ir.ui.view" id="board_session_form">
<field name="name">Session Dashboard Form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Session Dashboard">
<board style="2-1">
<column>
<action
string="Attendees by course"
name="%(act_session_graph)d"
height="150"
width="510"/>
<action
string="Sessions"
name="%(act_session_calendar)d"/>
</column>
<column>
<action
string="Courses"
name="%(act_course_list)d"/>
</column>
</board>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="open_board_session">
<field name="name">Session Dashboard</field>
<field name="res_model">board.board</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="usage">menu</field>
<field name="view_id" ref="board_session_form"/>
</record>
<menuitem
name="Session Dashboard" parent="base.menu_reporting_dashboard"
action="open_board_session"
sequence="1"
id="menu_board_session" icon="terp-graph"/>
</data>
</odoo>
![](http://img0.ph.126.net/PU6_mdXMaL-sQf2qwDQgvQ==/6597990057891325350.png)
- 练习在客户端添加新服务
编写Python程序,用来发送XML-RPC请求到运行Odoo的PC。这个程序将显示全部授课,以及所对应的座位数。也可以为课程创建新的授课。
</div>
<div class="postDesc">posted @ <span id="post-date">2018-07-06 23:19</span> <a href="https://www.cnblogs.com/myt2000/">苦苦思索的吃瓜群众</a> 阅读(<span id="post_view_count">145</span>) 评论(<span id="post_comment_count">0</span>) <a href="https://i.cnblogs.com/EditPosts.aspx?postid=9275891" rel="nofollow">编辑</a> <a href="#" onclick="AddToWz(9275891);return false;">收藏</a></div>
</div>
<script src="//common.cnblogs.com/highlight/9.12.0/highlight.min.js"></script><script>markdown_highlight();</script><script type="text/javascript">var allowComments=true,cb_blogId=442695,cb_entryId=9275891,cb_blogApp=currentBlogApp,cb_blogUserGuid='6031fa4c-c565-4e1f-9ae2-08d5866282ce',cb_entryCreatedDate='2018/7/6 23:19:00';loadViewCount(cb_entryId);var cb_postType=1;var isMarkdown=true;</script>
</div><!--end: forFlow -->
</div>
昵称:
不改了 退出 订阅评论
[Ctrl+Enter快捷键提交]
【前端】SpreadJS表格控件,可嵌入系统开发的在线Excel
【推荐】码云企业版,高效的企业级软件协作开发管理平台
【推荐】程序员问答平台,解决您开发中遇到的技术难题
· (04)odoo视图操作
· odoo的一些orm操作
· Odoo10学习笔记三:模型(结构化的应用数据)、视图(用户界面设计)
· 转:Odoo(OpenERP)视图继承总结
· odoo之model参数属性1
· 美国太空中的“太阳光帆”发回信号!
· Jim Keller 称摩尔定律没有死亡
· 比特币的耗电量超过了瑞士
· 入选“最聪明公司”的依图,如何打开算法芯片强耦合的新局面?
· 三星:7nm EUV工艺明年1月量产 已完成5nm研发
» 更多新闻...