odoo12从零开始:三、1)创建你的第一个应用模块(module)

前言

      以前,我一直都不知道为什么好多框架的入门都是“hello world”开始,当我思前想后我要如何介绍odoo的model、record、template等继承等高级特性时,发现在那之前便需要清楚地介绍什么是模型(model),什么是记录(record),什么是模板(template),以及他们到底是干什么用以及是怎么用的?想要知道它们是怎么用的,就得介绍odoo的一个应用模块(module)的结构是什么样的。那么了解一个应用结构最快的办法,那就是我们自己去完成一个。Let's do it!

Tips: 初学者容易搞混模块(module)模型(model)。
模块(module):是一个odoo应用,包含模型(models)、控制器(controllers)、视图(views)、权限(ir.rule,ir.group, ir.model.access)、初始化数据(data)、报表(report)、向导(wizard)、静态文件(static)等。
模型(model):是模块(module)的一部分,是odoo的ORM的描述对象,它的工作是帮我们将内存中的对象反映为数据库中的关系数据。

 模块结构(Module Structure)

主要目录:
       data/ : demo和数据的xml文件
     models/ : models定义
controllers/ : 包含controllers (HTTP路由等)
      views/ : 包含视图(views)和模板(templates)
     static/ : 包含web资源, 分为css/, js/, img/, lib/等
其他可选目录结构:
     wizard/ : 向导,由瞬时模型(models.TransientModel)构成,以及向导的视图(views)
     report/ : 报表,包含含有sql的模型,XML文件等
      tests/ : 测试代码

项目结构示意图

addons/模块名/
|-- __init__.py
|-- __manifest__.py (描述文件)
|-- controllers/
|   |-- __init__.py
|   |-- main.py
|   |-- *****.py
|-- data/
|   |-- *****_data.xml
|   |-- *****_demo.xml
|-- models/
|   |-- __init__.py
|   |-- *****.py
|-- report/
|   |-- __init__.py
|   |-- *****_report.py
|   |-- *****_report_views.xml
|   |-- *****_reports.xml (report actions, paperformat, ...)
|   |-- *****_templates.xml (xml report templates)
|-- security/
|   |-- ir.model.access.csv
|   |-- *****_groups.xml
|   |-- *****_security.xml
|-- static/
|   |-- description/
|   |   |-- icon.png(模块的icon)
|   |-- img/
|   |   |-- *****.png
|   |   |-- *****.jpg
|   |-- lib/
|   |   |-- external_lib/
|   |-- src/
|   |   |-- js/
|   |   |   |-- widget_a.js
|   |   |   |-- widget_b.js
|   |   |-- scss/
|   |   |   |-- widget_a.scss
|   |   |   |-- widget_b.scss
|   |   |-- xml/
|   |   |   |-- widget_a.xml
|   |   |   |-- widget_a.xml
|-- views/
|   |-- assets.xml
|   |-- **********.xml
|-- wizard/
|   |--*****.py
|   |--*****.xml

本节代码

git clone -b v3.1 https://github.com/lingjiawen/odoo_project.git

开发模块(module)

我们基于上一节的代码版本(V2.1)中进行开发:

git clone -b v2.1 https://github.com/lingjiawen/odoo_project.git

我们先来尝试仿照官方的hr模块开发一个简易版的员工模块,包含员工基本信息,员工部门和员工职位管理。

1、创建用户目录

首先,我们在my_addons/下新建employee/目录,在目录下新建以下文件:

my_addons/employee/
|-- __init__.py
|-- __manifest__.py
|-- models/
|   |-- __init__.py
|-- views/

2、填写描述文件__manifest__.py

# -*- coding: utf-8 -*-
{
    'name': 'Employee',
    'version': '12.0.1.0',
    'summary': '对员工的基本信息,部门和职位进行管理',
    'description': '''
        员工管理模块
    ''',
    'author': 'misterling',
    'sequence': 15,
    'category': 'Uncategorized',
    'license': 'LGPL-3',
    'depends': ['base'],
    'data': [],
    'demo': [],
    'qweb': [],
    'installable': True,
    'application': True,
    'auto_install': False,
    # 'pre_init_hook': '',
    # 'post_init_hook': '',
    # 'uninstall_hook': '',
}

以上描述便是常用的配置项,我们来看看它们都代表着什么:

          name: 模块的标题
       version: 版本号
       summary: 模块的子标题
   description: 模块的描述性文字
        author: 作者
      sequence: 模块在apps中的排列的序号,影响展示顺序。
      category: 模块的分类,在设置->用户&公司->群组中"应用"字段中可以看到
       license: 代表着你的开源协议。
       depends: 依赖的模块,在安装当前模块时,如果依赖模块未安装,将会自动安装;升级依赖的模块时,所有依赖它的模块也将会跟着升级。
data: 加载XML文件。 demo: 加载demo文件。 qweb: 加载qweb template文件。 installable: 是否可以安装。 application: 是否是应用,在应用列表中,被应用筛选隔离,好的开发习惯应该谨慎考虑是否是应用。 auto_install: 是否自动安装,设为True的应用将在数据库初始化时自动安装 pre_init_hook: 顾名思义,模块安装前的钩子,指定方法名即可 post_init_hook: 模块安装完成后的钩子 uninstall_hook: 模块卸载时的钩子
Tips:我们在书写python文件时,不用忘记在首部添加
# -*- coding: utf-8 -*-
以支持中文编码

 3、创建员工对象

 我们在models/下面新建employee.py文件,编写以下内容:

# -*- coding: utf-8 -*-
import base64
import logging

from odoo import api, fields, models
from odoo.modules.module import get_module_resource
from odoo import tools, _

_logger = logging.getLogger(__name__)

GENDER = [
    ('male', u''),
    ('female', u''),
    ('other', u'其他')
]

MARITAL = [
    ('single', u'单身'),
    ('married', '已婚'),
    ('cohabitant', '合法同居'),
    ('widower', '丧偶'),
    ('divorced', '离婚')
]


class Employee(models.Model):
    _name = "ml.employee"
    _description = '''
        员工信息
    '''

    @api.model
    def _default_image(self):
        image_path = get_module_resource('hr', 'static/src/img', 'default_image.png')
        return tools.image_resize_image_big(base64.b64encode(open(image_path, 'rb').read()))

    name = fields.Char(string=u'姓名')

    # image
    image = fields.Binary(string=u"照片", default=_default_image, attachment=True, help=u"上传员工照片,<1024x1024px")
    image_medium = fields.Binary(string=u"中尺寸照片", attachment=True, help="128x128px照片")
    image_small = fields.Binary(string=u"小尺寸照片", attachment=True, help="64x64px照片")

    company_id = fields.Many2one('res.company', string=u'公司')

    gender = fields.Selection(GENDER, string=u'性别')
    country_id = fields.Many2one('res.country', string=u'国籍')
    birthday = fields.Date(string=u'生日')
    marital = fields.Selection(MARITAL, string=u'婚姻状况', default='single')

    # work
    address = fields.Char(string=u'家庭住址')
    mobile_phone = fields.Char(string=u'手机号码')
    work_email = fields.Char(string=u'工作邮箱')
    leader_id = fields.Many2one('ml.employee', string=u'所属上级')
    subordinate_ids = fields.One2many('ml.employee', 'leader_id', string=u'下属')

    note = fields.Text(string=u'备注信息')

    @api.model
    def create(self, values):
        tools.image_resize_images(values)
        return super(Employee, self).create(values)

    @api.multi
    def write(self, values):
        tools.image_resize_images(values)
        return super(Employee, self).write(values)

我们一起来梳理一下类文件的主要内容:

from odoo import api, fields, models 引入 api, fields, models

1、class类
odoo的class继承了models.Model类,是odoo最常用的模型类,其他的还有 models.TransientModel,瞬时模型,用于向导(wizard),系统会在一定时间后自动清除模型的记录 models.AbstractModel,抽象模型,和抽象类是一样的概念,系统不会为该模型建立数据库表
2、内部标识 _name = "ml.employee",为odoo类的唯一标识,如果没有指定_table属性,那么系统将会为该模型建立数据库表名为ml_employee的数据表。 _description:主要为描述信息 3、使用的字段 odoo模型的字段使用fields.xxx来声明。 1)Char:文本字段 2)Binary:二进制字段,通常用于图片、附件等文件读写 3)Many2one:多对一关系字段,如: company_id = fields.Many2one('res.company', string=u'公司') 表现为多个员工可以对应同一个公司,'res.company'是odoo内置公司模型 4)Selection:列表选择字段,第一个参数为元组列表,表示可选列表 5)Date: 日期控件字段 6)One2many:一对多关系字段,如: subordinate_ids = fields.One2many('ml.employee', 'leader_id', string=u'下属') 表示一个员工可以有多个下属 7)Text: 文本字段,在前端表现为textarea,char在前端表现为input 4、属性 string:表示字段的显示名称 default:表示字段的默认值 attachment:binary字段的特有属性,表现为是否以附件的形式存储,设为True时,将会存储到ir.attachment中 5、ORM 方法修饰器 @api.model:模型修饰器,相当于静态方法,方法将为模型类共有,而不是每个实例。 @api.multi:对记录集执行一些操作,方法的逻辑通常会包含对 self 的遍历 6、方法 1)_default_image:我们获取hr模块目录下的图片,赋值给image字段作为默认值 2)create、write:重写记录的创建、编辑方法,使用odoo自带的工具image_resize_images对image_medium,image_small进行赋值

写完employee类之后,我们在与其同级的__init__.py中引入:

# -*- coding: utf-8 -*-

from . import employee

再在与models/目录同级的__init__.py中引入models:

# -*- coding: utf-8 -*-

from . import models

到此,我们就已经创建好了employee的类模型,接着我们要为其写视图(views)

 4、编写视图

 我们先来看看odoo最常用的三种视图:树形(tree),表单(form),搜索(search),它们存储于odoo内置的ir.ui.view模型中,其他还有图形(graph)、透视表(pivot)、日历(calendar)、图标(diagram)、甘特图(gantt)、看板(kanban)、QWEB、活动(activity),是odoo的最主要的页面展现形式。

1)tree视图

 tree视图为模型记录(record)的列表展示形式

2)form视图

 form视图为表单展现形式,主要用于odoo记录的创建,编辑。

3)search视图

search视图主要用于在tree、kanban等视图中进行搜索、过滤、分组记录以方便查看。

我们为我们的员工模型书写这三种视图:

新建views/employee.xml文件,加入odoo data标签:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data>
    </data>
</odoo>

先在data内增加一个form视图:

<record id="view_ml_employee_form" model="ir.ui.view">
    <field name="name">员工信息表单</field>
    <field name="model">ml.employee</field>
    <field name="arch" type="xml">
        <form string="员工信息">
            <sheet>
                <field name="image" widget='image' class="oe_avatar"
                       options='{"preview_image":"image_medium"}'/>
                <div class="oe_title">
                    <label for="name" class="oe_edit_only"/>
                    <h1>
                        <field name="name" placeholder="员工姓名" required="True"/>
                    </h1>
                </div>
                <notebook>
                    <page string="员工信息">
                        <group>
                            <group string="基本信息">
                                <field name="gender" required="True"/>
                                <field name="country_id"/>
                                <field name="birthday"/>
                                <field name="marital"/>
                            </group>
                            <group string="工作信息">
                                <field name="company_id" options="{'no_open': True, 'no_create': True}" groups="base.group_multi_company"/>
                                <field name="address"/>
                                <field name="mobile_phone" widget="phone"/>
                                <field name="work_email" widget="email"/>
                                <field name="leader_id" options="{'no_open': True, 'no_create': True}"/>
                            </group>
                        </group>
                    </page>
                    <page string="下属信息">
                        <field name="subordinate_ids">
                            <tree editable="bottom">
                                <field name="name" attrs="{'required': True}"/>
                                <field name="gender" required="True"/>
                                <field name="country_id"/>
                                <field name="mobile_phone"/>
                                <field name="work_email"/>
                            </tree>
                        </field>
                    </page>
                </notebook>
            </sheet>
        </form>
    </field>
</record> 

 我们来看看form表单的写法:

写一个form表单,实质上在为模型ir.ui.view增加一条记录,odoo中为模型增加一条记录可以使用record标签,我们为它取了唯一的id:view_ml_employee_form(我们约定,记录的书写使用view_模型名_form/tree的命名方式),然后使用record内的model属性指定增加的记录属于ir.ui.view模型。

我们先使用field标签插入name和model,

<field name="name">员工信息表单</field>
<field name="model">ml.employee</field>

代表我们是为ml.employee模型书写的form视图

我们在系统设置中打开“开发者模式”,然后打开设置->技术->用户界面->视图,可以看到系统中现在已有的记录(完成开发并升级后):

我们使用

<field name="arch" type="xml">
</field>

插入form内容。

其中:

1、使用<form></form>标签包裹表示记录类型为form视图
2、使用<field name="属性名" />的方式显示字段
3、<notebook>
        <page>
        </page>
        <page>
        </page>
        ……
    </notebook>
为翻页标签
4、widget='image'为显示类型为图片
5、required="True"为必填,常用的还有invisible、readonly等
6、需要使用group包裹field以正常显示字段的string值

One2many字段有特定的写法:

<field name="subordinate_ids">
    <tree editable="bottom">
        <field name="name" attrs="{'required': True}"/>
        <field name="gender" required="True"/>
        <field name="country_id"/>
        <field name="mobile_phone"/>
        <field name="work_email"/>
    </tree>
</field>

字段内嵌tree视图来自定义显示方式,editable="bottom"表示不弹出新窗口来创建明细记录。

细心的朋友可能看到必填有 required=True 和 attrs="{'required': True}"两种写法,事实上,invisible, readonly也有这两种写法。

他们的区别在于:

required=True:这个写法是死的,在视图加载时就已经确定。
attrs="{"required": [('name', '!=', False)]}: 这种写法可以书写domain来过滤(上面的写法也可以)。最重要的是,它会随着name的变化来动态改变required的值。

更多详细的介绍我们将会在后面views的专章介绍,这里只要了解大概就可以了。

options="{'no_open': True, 'no_create': True}"

其主要作用在对于Many2one字段,不允许其打开和新建它的专有视图。

Tips:
我们约定:many2one字段的命名使用xxx_id,如leader_id;
        One2many字段的命名我们使用xxx_ids,如subordinate_ids;

我们再为它书写tree视图:

<record id="view_ml_employee_tree" model="ir.ui.view">
    <field name="name">员工信息列表</field>
    <field name="model">ml.employee</field>
    <field name="arch" type="xml">
        <tree string="员工信息">
            <field name="name"/>
            <field name="company_id"/>
            <field name="gender"/>
            <field name="country_id"/>
            <field name="mobile_phone"/>
            <field name="work_email"/>
            <field name="leader_id"/>
        </tree>
    </field>
</record> 

Tree视图相对比较简单:

1、<tree></tree>包裹表示为tree视图
2、罗列字段以确定列表的显示字段以及显示顺序

我们再为其添加Search视图:

<record id="view_ml_employee_filter" model="ir.ui.view">
    <field name="name">员工搜索视图</field>
    <field name="model">ml.employee</field>
    <field name="arch" type="xml">
        <search string="员工">
            <!--用于搜索的字段-->
            <field name="name" string="员工"
                   filter_domain="['|',('work_email','ilike',self),('name','ilike',self)]"/>
            <field name="gender" string="性别"/>
            <separator/>
            <!--定义好的过滤器-->
            <filter string="男员工" name="gender_male"
                    domain="[('gender', '=', 'male')]"/>
            <filter string="女员工" name="gender_female"
                    domain="[('gender', '=', 'female')]"/>
            <separator/>
            <!--分组-->
            <group expand="0" string="分组">
                <filter name="group_leader" string="领导" domain="[]" context="{'group_by':'leader_id'}"/>
                <filter name="group_company" string="Company" domain="[]" context="{'group_by':'company_id'}"
                        groups="base.group_multi_company"/>
            </group>
        </search>
    </field>
</record>

我们可以看到:

1、<search></search>包裹表示为search视图
2、<field name="XXX" />声明可以用于搜索的字段
3、<filter string="XXX" name="XXX" domain="XXX" />表示系统定义的过滤器
4、使用<group></group>包裹filter可以进行分组

实际效果如下:

1)搜索

2)过滤器

 

 3)分组

 

 5、编写动作和菜单

我们写好了tree、form和search视图,我们需要编写动作和菜单来定义行为:

点击菜单->触发菜单对应的action动作->展示action中绑定的视图

 

我们继续在data内增加:

<record model="ir.actions.act_window" id="view_ml_employee_action">
    <field name="name">员工信息</field>
    <field name="res_model">ml.employee</field>
    <field name="view_type">form</field>
    <field name="view_mode">tree,form</field>
    <field name="view_id" ref="view_ml_employee_tree"/>
    <field name="search_view_id" ref="view_ml_employee_filter"/>
</record>

我们可以看到,动作对应的系统model为ir.action.act_window,我们一样可以在技术->动作->动作下找到我们定义的动作。

      view_mode:表示我们需要展示的视图(有先后顺序),tree视图在最前面,我们触发动作时首先展示的就是tree视图;

           view_id:表示我们引用的视图ref="view_ml_employee_tree",也就是我们在前面定义的tree视图;

search_view_id:表示我们引用的过滤器为"view_ml_employee_filter";

根据上面需要引用tree和search我们不难推断,可以为model定义多个tree、form和search视图,通过不同的action,绑定不同的菜单,可以触发同一模型不同的展示视图。

 

最后,我们为action添加一个菜单,我们习惯于将菜单使用单独的文件保存,所以我们新建views/menu.xml文件,书写下列内容:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data>
        <!--一级菜单-->
        <menuitem
                id="menu_employee_root"
                name="员工"
                web_icon="hr,static/description/icon.png"
                sequence="1"/>
        <!--二级菜单 -->
        <menuitem
                id="menu_employee_info"
                name="员工信息"
                parent="menu_employee_root"
                sequence="1"/>
        <!--三级菜单 -->
        <menuitem
                id="menu_view_ml_employee_tree"
                name="员工档案"
                action="view_ml_employee_action"
                parent="menu_employee_info"
                sequence="1"/>
    </data>
</odoo>

可以看到,我们使用menuitem标签定义菜单:

web_icon::一级菜单特有属性,表示展示的图标,这里我们借用hr模块的图标

sequence:菜单的展示顺序

parent:上级菜单,没有定义则为一级菜单

action:菜单对应的动作,我们在三级菜单中添加我们刚才编写的action:view_ml_employee_action

 6、引用并安装模块

在__manifest__.py->data中引用views:

'data': [
        'views/employee.xml',
        'views/menu.xml',
],

然后我们重启服务器,再打开“开发者模式”,在应用页面中刷新本地列表

 再搜索employee,点击安装

 安装之后我们就可以看到菜单了:

 

 撒花!!!等等!看不到?为什么呢!

Tips:Odoo12之前,admin用户就是root用户。Odoo12新增了root用户,在用户列表中不显示,只在框架需要使用sudo增加权限时才使用。admin依然可以登入系统并拥有所有功能的访问权限,但不再能绕过访问限制

那我们有没有办法进入root用户模式呢?有的:

首先,我们登入admin用户,然后"激活开发者模式",右上角进行"登出",你会发现登录页面多了一个"以超级用户登录"的方式

 

点击登录进去发现右上角有花纹,代表已经进入root模式,此时发现已经可以看到"员工模块"信息。

但是我们不能每次都通过这种方式来访问,而且其他用户也没有办法对这个页面进行访问,所以我们要为它写访问权限:

新建employee/security目录,在security目录下新建ir.model.access.csv文件,增加内容:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ml_employee,员工档案权限,employee.model_ml_employee,,1,1,1,1

这样查看比较混乱,我们再pycharm文件内->右键->edit as table->确定:

id:唯一标识
name:名称
model_id:id:对应model,使用model_+下划线格式模型_name作为标识
group_id:id:所属群组信息,这里我们置空
perm_read、perm_write、perm_create、perm_unlink分别为读、写、创、删四个权限

在manifest中加上引用:

'data': [
    'security/ir.model.access.csv',

    'views/employee.xml',
    'views/menu.xml',
]

在应用界面中升级应用,ok~

 参考

1、Odoo官网引导: http://www.odoo.com/documentation/12.0/reference/guidelines.html

2、《Odoo12 Development Essentials --Fourth Edition》 --Daniel Reis

声明

原文来自于博客园(https://www.cnblogs.com/ljwTiey/p/11486885.html)

转载请注明文章出处,文章如有任何版权问题,请联系作者删除。

有任何问题,联系邮箱:26476395@qq.com

posted @ 2019-09-09 14:34  Tiey  阅读(6574)  评论(8编辑  收藏  举报