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