学习笔记

内容均来自http://blog.sunansheng.com/python/odoo/odoo.html

odoo深入浅出来发教程

----Odoo开发基础: 请假模块进阶

    __init__.py 文件没啥好改动的,然后我们再看到main_model.py文件,这一次进行了较多地方的改动。

from openerp import models, fields, api
import logging
class 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#使用新的api
        
        def _get_default_name(self):
            uid = self.env.uid
            res = self.env['resource.resource'].search([('user_id','=',uid)])
            name = res.name
            
            employee = self.env['hr.employee'].search(
                [('name_related','=',name)])              
#   for i in self.env.user:# 说明其是recordset
#        print('hello')
            return employee
         
 #------------   
        @api.model    
        def _get_default_manager(self):#单记录recordset可以直接用点记号读取属性值
            uid = self.env.uid
            res = self.env['resource.resource'].search([('user_id','=',uid)])
            name = res.name
            
            employee = 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 = True
            else:
                self.is_manager = False
         
         #-----       
        def _get_current_name(self):
            uid = self.env.uid
            res = self.env['resource.resource'].search([('user_id','=',uid)])
            name = res.name
            
            employee = 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 True
        
        #-----
        def confirm(self, cr, uid, ids, context=None):
            if context is None:
                context={}
            self.write(cr,uid,ids,{'state':'confirmed'},context=context)
            return True
         
         #-----   
        def accept(self, cr, uid, ids, context=None):
            if context is None:
                context={}
            self.write(cr,uid,ids,{'state':'accepted'},context=context)
            
            print('你的请假单被批准了')
            return True
            
        #-----    
        def 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(人资管理模块)的。

    如图image.png

    这么一张简单的图并没很好地第二版的加强功能说明出来,建议读者新建几个用户,然后在人力资源那

  里把员工和部门设置好,尤其是部门主管等信息。然后读者可以尝试以一个普通员工来新建一个请单,

  编辑,保存,发送。然后接下来的批准和拒绝按钮该普通员工是不能点击的(工作流的condition控制

  的),而只有主管才可以正常点击批准或拒绝按钮。


----10.1 本例涉及到的数据库表格简介

        这里我只进行简略的讨论,具体该表格的细节请读者自己用pgadmin3软件查看之。

    res.uses:在网页视图下对应的菜单是: 设置→用户→用户.这个表格(或说模型)存储着一些登录

      用户的信息,比如用户名或密码等。

    res.groups:在网页视图下对应的菜单是: 设置→用户→组.权限管理就是利用了这个表格建立的群

      组概念,比如最常使用的群组,人力资源/雇员就是这个表格的第五条记录,其外部是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.


----工作流概念入门

工作流对象算是Odoo框架里面颇具特色的一部分,其在网页视图中对应的菜单是: 设置→工作流→工作流。对应的模型是 workflow ,但并没有workflow这个表格,要说对应的话应该是wkf这个表格,然后还有wkf_activity表格等。

Odoo工作流的图标视图如下图所示是一个不错的查看和管理当前工作流程的工具:

image.png


    


其中节点叫做“活动(activity)”,然后弧线连接叫做“转变(transition)”。活动描述了Odoo服务器应该完成的一些工作,比如:改变某些记录的状态,发送email等等。转变则控制了从活动到活动之间的工作流。

转变可以增加属性如条件、信号、触发器等。这样工作流的行为是取决于用户的动作(如点击某个按钮),或某个记录值的更改或任意的python代码。总而言之,Odoo工作流系统提供了:

  • 关于记录如何演变的描述

  • 根据多样的弹性的条件建立自动化行动机制

  • 管理公司流程和确认规则

  • 管理对象间的互动

  • 在他们的生命期内一个可视化的流程图

    工作流和模型关联在一起,具体某个模型新建一个对象就会是实例化一个工作流对象。然后该工作流对象应该有个状态记录,服务器重启之后工作流还是在那个状态下,也就是已经执行了的都不会执行了



--------定义工作流对象

<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,工作流会根据每一个模型新建一个对象再实例化一次。----创建节点

<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


--------创建链接

<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:信号,触发本迁移的信号,如果定义了信号,则只有在系统收到signal定义的信号后,才会触

  发本迁移。即使本次迁移满足条件,但如果没有收到信号,也不会触发。

condition:迁移的条件,是一段Python代码,通常是一个函数调用。当系统收到signal中定义的信号时

  候,会在收到信号后检查此处的条件,条件为真则触发迁移。如果没有定义信号,但定义了条件时,

  在满足条件的情况下才会触发迁移。如果不满足条件,系统会在相关数据发生变化后再次检查该条件

  是否满足




--Odoo模型层详解

    经过前面得介绍,我们也确实感觉到Odoo得ORM层得API应该是Odoo技术框架得最核心部分,如果我们翻

  翻Odoo框架源码,也会看到models.py那个文件有六七千行代码,这也说明Odoo得设计者在编写ORM这块

 花费了很多精力,所以我想我们把Odoo得ORM层API这部分知识掌握了,Odoo框架得神秘面纱也基本上被掀开一大半了

  本章在前面得基础上继续详细介绍Odoo ORM API细节.

----_name

    定义了本模型具体对应SQL表格得名字,比如前面定义得mymdule.fruits对应数据库中得表格名是

  public.mymodule_fruits

----各个表头属性

class Fruits(models.Model):
    _name = 'mymodule.fruits'

    name = fields.Char()

    如上所示这样定义一个name属性就对应SQL中得一个表头名,即名叫name得一列.这个我们在前面看到了

    然后上面得fields.char()具体定义了一个字符串输入字段,类似得还有fields.Boolean布尔值;

  fields.Integer()整数值;fields.Float浮点数值;fields.Text和char类似,但通常用于多行文本输入;

  fields.Selection几个值得选择;fields.Html;fields.Date;fields.Datetime等.这些都是所谓得简单

  字段输入,此外还有一种关系字段,其是用于描述表格之间得关系得(相同或者不同模型)

    fields.Char()函数可以接受一些可选参数,比如string表示本模型为用户可见得名字;required接收

  一个布尔值,默认False,若是True,则该字段不可以为空。help在用户UI界面下得帮助信息;index布尔值

  默认False,若为True则要在数据库中为这列创建一个索引(index)

    此外还有一些其他表头字段:

        ID:在表格中一条记录独特id

                 create_uid:谁创建的

        create_date:创建日期

        write_date:最后修改日期

        write_uid:谁修改得

----name字段

    Odoo中得模型一般都还需要name字段,用于各种搜索或者显示行为.

----具体模型得数据

    具体模型数据用xml文件来声明

<openerp>
    <data>
        <record id="apple" model="mymodule.fruits">
            <field name="name">apple</field>
        </record>
    </data>
</openerp>

    这里得record元素你可以理解为SQL表格得一条记录,或者Odoo模型具体得一个实例对象,然后id属性

  特别记录了这条数据(说可被外调用,具体还不清楚);model属性就是这个对象具体对应那个模型

    然后里面得field元素你可以看作记录具体某个name表头得字段,field body里面就放着这个字段得值

    这里得record在视图中对应得应该是basic view;此外还有tree对应得是列表视图;form对应表单视图

----模型间得关系

    many2one

    one2many

    many2many

----工作流

一个工作流模型 在Session模型上都加入了state 字段 : 有三种字段, Draft Confirmed Done

有效的转变有:

Draft → Confirmed Confirmed → Draft Confirmed → Done Done → Draft