odoo开发(二)2)创建第一个应用模块(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

开发模块(module)

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

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

  1. 创建用户目录

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

my_addons/employee/
|-- __init__.py
|-- __manifest__.py
|-- models/
|   |-- __init__.py
|-- views/
  1. 填写描述文件__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 -*-
以支持中文编码
  1. 创建员工对象

我们在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

1class类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)

  1. 编写视图

我们先来看看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视图
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)分组

在这里插入图片描述

  1. 编写动作和菜单

我们写好了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

  1. 引用并安装模块

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

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

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

在这里插入图片描述
再搜索employee,点击安装

在这里插入图片描述
在这里插入图片描述
撒花!!!等等!看不到?为什么呢!

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

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

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

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

新建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

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值