10 Odoo开发基础: 请假模块第二谈
__init__.py
文件没啥好改动的,然后我们再看到main_model.py文件,这一次进行了较多地方的改动。
from openerp import models, fields, apiimport loggingclass Qingjd(models.Model):_name = 'qingjia.qingjd'name = fields.Many2one('hr.employee', string="申请人", readonly=True)manager = fields.Many2one('hr.employee', string="主管",readonly=True)beginning = fields.Datetime(string="开始时间", required=True,default = fields.Datetime.now())ending = fields.Datetime(string="结束时间", required=True)reason = fields.Text(string="请假事由",required=True)accept_reason = fields.Text(string="同意理由",default="同意。")#########compute 没有写入数据库 on the fly 可以被workflow的condition调用current_name = fields.Many2one('hr.employee', string="当前登录人",compute="_get_current_name")is_manager = fields.Boolean(compute='_get_is_manager')######state = fields.Selection([('draft', "草稿"),('confirmed', '待审核'),('accepted', '批准'),('rejected', '拒绝'),],string='状态',default='draft',readonly=True)@api.model#使用新的apidef _get_default_name(self):uid = self.env.uidres = self.env['resource.resource'].search([('user_id','=',uid)])name = res.nameemployee = self.env['hr.employee'].search([('name_related','=',name)])# for i in self.env.user:# 说明其是recordset# print('hello')return employee@api.modeldef _get_default_manager(self):#单记录recordset可以直接用点记号读取属性值uid = self.env.uidres = self.env['resource.resource'].search([('user_id','=',uid)])name = res.nameemployee = self.env['hr.employee'].search([('name_related','=',name)])logging.info("myinfo {}".format(employee.parent_id))return employee.parent_id # 似乎有这种数字引用方法值得我们注意_defaults = {'name' : _get_default_name ,'manager' : _get_default_manager ,}def _get_is_manager(self):###这里return不起作用print('----------test')print(self.current_name, self.manager,self.env.uid)if self.current_name == self.manager:self.is_manager = Trueelse:self.is_manager = Falsedef _get_current_name(self):uid = self.env.uidres = self.env['resource.resource'].search([('user_id','=',uid)])name = res.nameemployee = self.env['hr.employee'].search([('name_related','=',name)])self.current_name = employee##############################def draft(self, cr, uid, ids, context=None):if context is None:context={}self.write(cr,uid,ids,{'state':'draft'},context=context)return Truedef confirm(self, cr, uid, ids, context=None):if context is None:context={}self.write(cr,uid,ids,{'state':'confirmed'},context=context)return Truedef accept(self, cr, uid, ids, context=None):if context is None:context={}self.write(cr,uid,ids,{'state':'accepted'},context=context)print('你的请假单被批准了')return Truedef reject(self, cr, uid, ids, context=None):if context is None:context={}self.write(cr,uid,ids,{'state':'rejected'},context=context)print('抱歉,你的请假单没有被批准。')return True
读者不要着急,这里的内容我会慢慢讲解的。然后views.xml改成了这个样子:
<?xml version="1.0"?><openerp><data><!-- 打开请假单动作--><act_window id="action_qingjia_qingjd"name="请假单"res_model="qingjia.qingjd"view_mode="tree,form" /><!--表单视图--><record id="qingjia_qingjd_form" model="ir.ui.view"><field name="name">qing jia dan form</field><field name="model">qingjia.qingjd</field><field name="arch" type="xml"><form><header><button name="btn_confirm" type="workflow" states="draft"string="发送" class="oe_highlight" /><button name="btn_accept" type="workflow" states="confirmed"string="批准" class="oe_highlight"/><button name="btn_reject" type="workflow" states="confirmed"string="拒绝" class="oe_highlight"/><field name="state" widget="statusbar" statusbar_visible="draft,confirmed,accepted,rejected" class="oe_highlight" type="workflow"/></header><sheet><group name="group_top" string="请假单"><group name="group_left"><field name="name"/><field name="beginning"/></group><group name="group_right"><field name="manager"/><field name="ending"/></group></group><group name="group_below"><field name="reason"/></group></sheet></form></field></record><!--tree视图--><record id="qingjia_qingjd_tree" model="ir.ui.view"><field name="name">qing jia dan tree</field><field name="model">qingjia.qingjd</field><field name="arch" type="xml"><tree><field name="name"/><field name="beginning"/><field name="ending"/><field name="state"/></tree></field></record><!--加入菜单--><menuitem id="menu_qingjia" name="请假" sequence="0"></menuitem><menuitem id="menu_qingjia_qingjiadan" name="请假单" parent="menu_qingjia"></menuitem><menuitem id="menu_qingjia_qingjiadan_qingjiadan" parent="menu_qingjia_qingjiadan" action="action_qingjia_qingjd"></menuitem></data></openerp>
除了跟着main_model.py文件里面的一些修改而更改外,最值得一提的就是button的 type 属性设置为了 "workflow" 。如果还是设置为 "object" ,当你点击按钮的时候,该模型就应该提供对应按钮name 的一个方法,这个方法将执行某些动作。这里要强调的就是这样简单的object按钮是不和后面谈到的工作流的概念兼兼容的。如果按钮的type还是设置为object,那么其是不发送工作流的signal的。更多工作流的细节等下再谈。
然后还编写了一个工作流的workflow.xml文件:
<?xml version="1.0" ?><openerp><data noupdate="0"><record id="wkf_qingjia" model="workflow" ><field name="name">wkf.qingjia</field><field name="osv">qingjia.qingjd</field><field name="on_create">True</field></record><record id="act_draft" model="workflow.activity" ><field name="wkf_id" ref="wkf_qingjia" /><field name="name">draft</field><field name="flow_start" eval="True"/><field name="kind">function</field><field name="action">draft()</field></record><record id="act_confirm" model="workflow.activity" ><field name="wkf_id" ref="wkf_qingjia" /><field name="name">confirm</field><field name="kind">function</field><field name="action">confirm()</field></record><record id="act_accept" model="workflow.activity" ><field name="wkf_id" ref="wkf_qingjia" /><field name="name">accept</field><field name="kind">function</field><field name="flow_stop">True</field><field name="action">accept()</field></record><record id="act_reject" model="workflow.activity" ><field name="wkf_id" ref="wkf_qingjia" /><field name="name">reject</field><field name="kind">function</field><field name="action">reject()</field></record><record model="workflow.transition" id="qingjia_draft2confirm"><field name="act_from" ref="act_draft" /><field name="act_to" ref="act_confirm" /><field name="signal">btn_confirm</field></record><record model="workflow.transition" id="qingjia_confirm2accept"><field name="act_from" ref="act_confirm" /><field name="act_to" ref="act_accept" /><field name="signal">btn_accept</field><field name="condition">is_manager</field></record><record model="workflow.transition" id="qingjia_confirm2reject"><field name="act_from" ref="act_confirm" /><field name="act_to" ref="act_reject" /><field name="signal">btn_reject</field><field name="condition">is_manager</field></record></data></openerp>
然后 __openerp__.py
文件没做什么修改,就把上面的workflow.xml加进来,然后depends将base改为了hr,因为等下一些功能是依赖官方内置模块hr(人资管理模块)的。
读者可以按照上面的描述先看看这个请假模块第二版是什么样子,下面是一个简单的示意图:
Figure 29: 请假模块第二版
这么一张简单的图并没很好地第二版的加强功能说明出来,建议读者新建几个用户,然后在人力资源那里把员工和部门设置好,尤其是部门主管等信息。然后读者可以尝试以一个普通员工来新建一个请假单,编辑,保存,发送。然后接下来的批准和拒绝按钮该普通员工是不能点击的(工作流的condition控制的),而只有主管才可以正常点击批准或拒绝按钮。
10.1 本例涉及到的数据库表格简介
这里我只进行简略的讨论,具体该表格的细节请读者自己用pgadmin3软件查看之。
- res.uses
- 在网页视图下对应的菜单是: 设置→用户→用户。 这个表格(或说模型)存储着一些登录用户的信息,比如用户名或密码等。
- res.groups
-
在网页视图下对应的菜单是: 设置→用户→组。 权限管理就是利用了这个表格建立的群组概念,比如最常使用的群组,人力资源/雇员就是这个表格的第五条记录,其外部id是
base.group_user
。 - resource.resource
- res.uses存储的只是用户的登录名和密码等,而在网页上具体显示的是该用户更人类易读的名字,其就是根据这个表格来完成的。这个表格有user_id,该id就对应res.uses的某条记录,而name就是对应的人类易读的名字。
- hr.employee
- 在网页视图下对应的菜单是: 人力资源→人力资源→员工。这个表格存储着员工的一些信息,其中name_related属性就对应前面resource.resource的name,然后parent_id对应该员工的上一级也就是主管,具体就是本表格的对应id记录,然后department是该员工所属部门,具体是hr.department表格中的对应id的记录。
- hr.department
- 在网页视图下对应的菜单是: 人力资源→设置→部门。本表格记录了公司的部门信息(支持多公司概念,有company_id标识),还有该部门的主管对应的员工id。
10.2 工作流概念入门
工作流对象算是Odoo框架里面颇具特色的一部分,其在网页视图中对应的菜单是: 设置→工作流→工作流。对应的模型是 workflow ,但并没有workflow这个表格,要说对应的话应该是wkf这个表格,然后还有wkf_activity表格等。
Odoo工作流的图标视图如下图所示是一个不错的查看和管理当前工作流程的工具:
Figure 30: 工作流图表视图示意图
其中节点叫做“活动(activity)”,然后弧线连接叫做“转变(transition)”。活动描述了Odoo服务器应该完成的一些工作,比如:改变某些记录的状态,发送email等等。转变则控制了从活动到活动之间的工作流。
转变可以增加属性如条件、信号、触发器等。这样工作流的行为是取决于用户的动作(如点击某个按钮),或某个记录值的更改或任意的python代码。总而言之,Odoo工作流系统提供了:
- 关于记录如何演变的描述
- 根据多样的弹性的条件建立自动化行动机制
- 管理公司流程和确认规则
- 管理对象间的互动
- 在他们的生命期内一个可视化的流程图
工作流和模型关联在一起,具体某个模型新建一个对象就会是实例化一个工作流对象。然后该工作流对象应该有个状态记录,服务器重启之后工作流还是在那个状态下,也就是已经执行了的都不会执行了。
10.2.1 定义工作流对象
<record id="wkf_qingjia" model="workflow" > <field name="name">wkf.qingjia</field> <field name="osv">qingjia.qingjd</field> <field name="on_create">True</field> </record>
首先是定义了本工作流对象,model=workflow,id是多少,这个id很重要,等下本工作流对象里面的活动和转变都要用到这个id。
然后field定义了
- name
- 本工作流的name,随意。
- osv
- 这个属性很重要,是具体的那个模型,其和本工作流关联起来了。
- (no term)
- on_create 一般设置为True,工作流会根据每一个模型新建一个对象再实例化一次。
10.2.2 创建节点
<record id="act_draft" model="workflow.activity" > <field name="wkf_id" ref="wkf_qingjia" /> <field name="name">draft</field> <field name="flow_start" eval="True"/> <field name="kind">function</field> <field name="action">draft()</field> </record>
这里创建了一个节点活动对象:
- flow_start
- 如果设置为True 工作流会从这里开始
- wkt_id
- 属于某个工作流对象 我们看到这里的ref语法如果是引用本模型内的对象则可以省略。
kind 有四种 dummy function subflow stop all
action 具体的动作(于模型上)
flow_stop 一个工作流的完成就是所有的活动有flow_stop属性的都设置为了 True
10.2.3 创建连接
<record model="workflow.transition" id="holiday_draft2confirm"> <!-- 1. draft->submitted (confirm signal) --> <field name="act_from" ref="act_draft" /> <field name="act_to" ref="act_confirm" /> <field name="signal">confirm</field> <field name="condition">can_reset</field> <field name="group_id" ref="base.group_user"/> </record>
转变对象属于 workflow.transition 模型。
act_from 何活动出发 act_to 到何活动去 signal
condition
2.1.2 Accessing Current User self.env.user
'lang' ('self.env.lang').
default 使用lambda语句 其默认接受self参数, 即本模型具体的对象。
ir.module.category