odoo遵循了MVC模式model、controllers、view
1、在odoo目录下同与‘addons’同级目录创建一个例如“custom_addons”模块用于存放自己开发的模块;
2、在‘odoo.conf’文件中配置文件路径,便于加载:
3、创建项目目录和声明文件:
python3 odoo-bin scaffold 创建的模块名 模块放置的目录
# 例如
python3 odoo-bin scaffold library_app custom_addons
如下图创建完会生成如下图的项目目录:
4、对_manifest_.py文件进行相关配置:
主要配置红色标记的便于在‘应用中’搜索到;
‘manifest.py’文件中的字段说明:
name : 插件模块标题字符串;
description : 功能描述长文件,通常为RST格式;此处可由模块目录下的README.rst或者README.md代替。
author : 作者姓名,此处为一个字符串,如有多个作者可以用逗号分隔;
depends :该模块所依赖的插件模块列表,在创建的时候回自动添加内核base模块,注意所有依赖的模块都需要在此处声明,否则会因为找
不到相应模块而报错;
summary :显示为模块副标题的字符串;
version :版本号;
license :默认为LGPL-3;
website :关于模块信息的URL;
category :模块功能性分类的字符串;
data : 声明视图和权限文件,注意一般权限文件需要放在视图前面,避免引起关于ID找不到的现象;
# ###########################################注意##########################################
application :布尔型标记,代表模块是在应用列表中以APP展现;
installable :默认为True,但可以通过设置为False来禁用模块;
auto_install :若设置为True,在其依赖已安装时会自动安装,主要用于同一个实例两个模块安装后功能的链接。
5、添加图标:
图标可在该网址选择:https://www.iconfont.cn/home/index
①、在项目目录下创建如下目录“static/description”将对象的图标放置在description目录下;
6、去应用中查看模块:
①、激活开发者模式,url中就会有debug=#:
②、点击“刷新本地模块列表”在搜索框搜模块名就会看到自定义的模块:
7、点击安装即可安装应用,不过当前应用中没有任何功能;
安装了为什么没有显示????请继续向下看
8、创建新的应用:
一个应用应包含如下部分元素:
图标:用于在应用列表中的展示;
顶级菜单项:其下位置所有的应用菜单项;
应用安全组:通过权限访问仅对指定用户开发;
①、添加应用顶级菜单项:
A、在views目录下创建“library_menu.xml”文件来定义菜单项:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="menu_library" name="Library"/>
</odoo>
代码注释:
以上代码是odoo的数据文件,用于odoo数据库的记录,其中的元素是向“ir.ui.menu”模型写入记录,id作为唯一标识符;
B、在 manifest.py的data属性来添加创建的“library_menu.xml”文件;
'data': [
'views/library_menu.xml',
],
注意:此时升级模块还不会有太大的效果,因为顶级菜单项中未包含可操作性的子菜单。
②、添加权限组:
A、权限的目的:
权限组主要用于用户在使用应用的相应权限,权限主要分为:普通用户权限组,额外配置的权限管理组;
B、接下来添加两个安全组,在security目录下创建library_security.xml文件中定义:
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- 在ir.module.category创建图书应用的分类-->
<record id="module_library_category" model="ir.module.category">
<field name="name">Library</field>
</record>
<!--添加安全组 如下添加了用户组-->
<record id="library_group_user" model="res.groups">
<field name="name">User</field>
<field name="category_id" ref="module_library_category"/>
<field name="implied_ids" eval="[(4,ref('base.group_user'))]"/>
</record>
<!-- 创建管理组-->
<record id="library_group_manager" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="module_library_category"/>
<field name="implied_ids" eval="[(4,ref('library_group_user'))]"/>
<field name="users" eval="[
(4,ref('base.user_root')),
(4,ref('base.user_admin'))
]"/>
</record>
</odoo>
以上代码中字段说明:
以上代码将在res.groups模型中创建:
name: 组名;
category_id:关联应用,使用ref属性来通过XML ID链接已创建的分类;例如:链接创建图书对应的分类;
implied_id:这是一个one-2-many关联字段,主要是一系列组来对组内用户生效,后面会详细介绍;
例如用户组:我们使用内部用户组base.greoup_user;
例如管理组:关联了图书用户组,以继承其权限;
users:让管理组合内部超级管理员自动称为应用管理员;
C、在 manifest.py的data属性来添加创建的“library_security.xml”文件;
'data': [
# 'security/ir.model.access.csv',
'security/library_security.xml',
'views/library_menu.xml',
],
D、启动服务,在参数设置–>用户&公司–>群组 中可以查看:
9、模型层:
①、在models文件下创建模型文件“library_book.py”:
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class Book(models.Model):
_name = 'library.book'
_description = 'Book'
name = fields.Char('书名',required=True)
isbn = fields.Char('ISDN')
active = fields.Boolean('Active?',default=True)
date_published = fields.Date() # 出版时间
image = fields.Binary('Cover') # 存储图书封面的二进制字段
publisher_id = fields.Many2one('res.partner',string='Publisher') # 出版公司多对一关联
author_ids = fields.Many2many('res.partner',string="Authors") # 作者多对多关联
代码释义:
_name:定义了Odoo全局对该模型引用的标识符; 必填项
_description:模型说明;非必填项
②、在models目录下的“init.py”文件下导入library_book文件:
from . import library_book
③、当定义完以上字段,(需开启开发者模式)可在“参数设置–>技术–>模型中检查相关字段”:
在列表中搜索library.book,然后点击查看该模型,即可看到下图:
字段释义:
# 除上图中红色标记的自定义字段外,启动服务的时候,Odoo会自己创建出如下字段:
id :是模型中每条记录的唯一数字标识符
create_date和create_uid:分别为记录创建时间和创建者
display_name:为所使用的记录提供文本显示,如其它记录引用它,它就会被计算并默认使用 name 字段中的文本
write_date和write_uid:分别表示最后修改时间和修改者
__last_update:是一个助手字段 ,它不存储在数据库,用于做并发检测
10、设置访问权限:
当启动服务的时候,也许你在日志里面已经看到如下WARNING,根据警告已经很明显的告诉我们该引用没有设置权限:
The model library.book has no access rules, consider adding one. E.g. access_library_book,access_library_book,model_library_book,base.group_user,1,0,0,0
①、可在“参数设置–>技术–>访问权限”里面对相关权限进行查看:
A、权限通过‘security/ir.model.access.csv’文件来添加,添加该文件并加入如下内容:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_book_user,BookUser,model_library_book,library_group_user,1,0,0,0
access_book_manager,BookManager,model_library_book,library_group_manager,1,1,1,1
注意事项:
文件第一行后不要留空格,否则会导致报错;
代码释义:
id :是记录的外部标识符,在模块中唯一;
name :描述性标题,在保证唯一时提供有用信息;
model_id :是赋权模型的外部标识符,对于该模型,标识符为“model_library_book”;
group_id :指名授权的安全组,在前面的“library_security.xml”文件中创建了library_group_user和library_group_manager;
# 以下字段 1代表拥有该权限,0则相反
perm_read :读,在此处我们授予普通用户读权限,管理员所有权限;
perm_write :写;
perm_create :创建;
perm_unlike :删除权限;
B、为了让权限生效,还需要在_manifest_.py的data属性中添加对新文件的引入:
'data': [
'security/library_security.xml',
'security/ir.model.access.csv',
'views/library_menu.xml',
],
此处需要注意:由于csv文件里面引用了“library_security.xml”文件的相关部分,因此需要放在其下面;
②、行级权限:
假设需求:
默认active标记为False的记录不可见,但用户在需要时可以使用过滤器来访问,假设不希望普通用户访问无效图书,因此需要通过记录规则来实现。
A、在security/library_security.xml文件中添加如下代码:
<data noupdate="1">
<record id="book_user_rule" model="ir.rule">
<field name="name">Library Book User Access</field>
<field name="model_id" ref="model_library_book"/>
<field name="domain_force">
[('active','=',True)]
</field>
<field name="groups" eval="[(4,ref('library_group_user'))]"/>
</record>
</data>
代码释义:
noupdate=“1” :表示这些记录在模型安装时会被创建,但在模型更新时不会重写,
目的是允许规则在后面自定义时在升级模型时自定义内容丢失。但是建议在开发过程置为0;
11、视图层(view):
①、添加菜单项:
<act_window id="action_library_book"
name="Library Books"
res_model="library.book"
view_mode="tree,form"/>
<menuitem id="menu_library_book"
name="Books"
parent="menu_library"
action="action_library_book"/>
代码释义:
<act_window>元素定义客户端窗口操作,它按顺序通过启用列表和表单视图打开library.book 模型
<menuitem>定义一个调用前面定义的action_library_book操作的顶级菜单项
启动服务,升级模块:
此时在界面可以看到菜单界面有了我们所安装的Library应用;
②、创建视图表单;
所有的视图都存在数据库ir.ui.view模型中。
A、添加view/book_view.xml文件来定义表单视图:
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_form_book" model="ir.ui.view">
<field name="name">Book Form</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<form string="Book">
<group>
<field name="name"/>
<field name="author_ids" widget="many2many_tags"/>
<field name="publisher_id"/>
<field name="date_published"/>
<field name="isbn"/>
<field name="active"/>
<field name="image" widget="image"/>
</group>
</form>
</field>
</record>
</odoo>
代码释义:
ir.ui.view中记录三个字段值:
name:主要用于提供信息;
model:模型名称;
arch:包含视图的定义;
<form>:包含了要在表单中显示的字段;
B、在data中进行声明:
刷新页面:
③、业务文件表单视图:
在odoo表单中包含两个元素:
<header>:定义操作按钮;
<sheet>:定义数据字段
如下代码:
<form string="Book">
<header>
<!-- 此处添加按钮 -->
</header>
<sheet>
<group>
<field name="name" />
...
</group>
</sheet>
</form>
④、添加操作按钮:
定义一个操作按钮,用于检测ISDN的有效性,逻辑代码放在Book模型中,方法命名为button_check_isdn(),暂时未创建,但是我们先可以在表单中添加相应按钮:
<header>
<button name="button_check_isdn" type="object"
string="Check ISBN"/>
</header>
代码释义:
按钮的基本属性有
string: 定义按钮显示文本;
type:执行的操作类型;
name:操作的标识符;
class:应用CSS样式的可选属性,与HTML相同;
⑤、使用组来组织表单:
<group>标签可用于组织表单内容,在<group>元素内加<group>会在外层组中创建一个两列布局,
推荐在group元素中添加name属性,易于其他模块对其进行继承。
A、修改代码如下:
<sheet>
<group name="group_top">
<group name="group_left">
<field name="name"/>
<field name="author_ids" widget="many2many_tags"/>
<field name="publisher_id"/>
<field name="date_published"/>
</group>
<group name="group_right">
<field name="isbn"/>
<field name="active"/>
<field name="image" widget="image"/>
</group>
</group>
</sheet>
B、启动刷新界面:
⑥、增加列表视图和搜索视图:
A、在book_view.xml文件中添加 tree 视图:
<record id="view_tree_book" model="ir.ui.view">
<field name="name">Book List</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="author_ids" widget="many2many_tags"/>
<field name="publisher_id"/>
<field name="date_published"/>
</tree>
</field>
</record>
刷新界面,显示如下图:
B、定义搜索视图,观察上图会发现右上角有一个搜索框,搜索的字段和可用过滤器由视图定义,在book_view.xml文件中添加:
<!--搜索试图-->
<record id="view_search_book" model="ir.ui.view">
<field name="name">Book FIlters</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<search>
<field name="publisher_id"/>
<filter name="filter_active"
string="Active"
domain="[('active','=',True)]"/>
<filter name="filter_inactive"
string="Inactive"
domain="[('active','=',False)]"/>
</search>
</field>
</record>
代码释义:
<field>元素定义在搜索框中输入搜索的字段,这里添加了publisher_id 自动提示出版商字段。
<filter>元素添加预定义过滤条件,用户进行点击来切换。odoo12中要求<filter>要包含name=""属性,唯一标识每个过滤器,如果不写验证会失败。
刷新界面,显示效果如下:
12、业务逻辑(model)
业务逻辑层主要编写应用的业务规则,如验证和自动计算,下来我们添加isbn验证按钮的业务逻辑;
①、添加业务逻辑
上面我们添加了一个ISBN有效验证的按钮,现在我们来实现,ISBN包含13位数字,最后一位由前面12位计算所得。
@api.multi
def _check_isbn(self):
self.ensure_one()
isbn = self.isbn.replace('-','')
digits = [int(x) for x in isbn if x.isdigit()]
if len(digits) == 13:
ponderations = [1,3]*6
terms = [a * b for a,b in zip(digits[:12],ponderations)]
remain = sum(terms) % 10
check = 10 - remain if remain != 0 else 0
return digits[-1] == check
然后继续添加按钮的代码:
@api.multi
def button_check_isbn(self):
for book in self:
if not book.isbn:
raise Warning('Please privide an ISBN for %s'%book.name)
if book.isbn and not book._check_isbn():
raise Warning('%s is an invalid ISBN'% book.isbn)
return True
此处需要注意导入Warning包:
from odoo.exceptions import Warning
代码释义:
使用了@api.multi装饰器,此处的self便是一个记录集,然后遍历每一条记录,遍历所有已选图书,如果ISBN有值,
则检查有效性,若无值,则向用户抛出警告。
13、网页和控制器(controllers)
Odoo提供了一个web开发框架,用于开发与后台应用深度继承的功能;
接下来我们创建一个显示有效图书列表的简单网页;
A、在controllers目录下创建main.py文件
from odoo import http
class Books(http.Controller):
@http.route('/library/books',auth='user')
def list(self,**kwargs):
Book = http.request.env['library.book']
books = Book.search([])
return http.request.render(
'library_app.book_list_template',{'books':books}
)
注意:在同main.py文件下的“init.py”文件中导入main文件
代码释义:
这里使用了odoo.http,是提供网页相关功能的核心组件;
http.Controller是需要继承的类控制器;
@http.route装饰器,它声明了与类方法关联的URL地址,默认访问URL地址要求客户登录,推荐明确指出访问的授权模式,这里添加了auth='user'参数,
如果允许公开访问,还可以添加auth='public'参数,但是在图书搜索前应使用sudo()进行提权;
http.request.env获取环境,试用其从目录中获取有效图书记录集;
http.request.render()通过字典来向模板传值;
B、QWeb模板是一个视图类型,应放在/views目录下,创建book_list_tenplate.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="book_list_template" name="Book List">
<div id="wrap" class="container">
<h1>Books</h1>
<t t-foreach="books" t-as="book">
<div class="row">
<span t-field="book.name" />,
<span t-field="book.date_published" />,
<span t-field="book.publisher_id" />
</div>
</t>
</div>
</template>
</odoo>
注意:需要在data里面进行声明;
代码释义:
<template>元素用于声明QWeb模板,实质就是存储模块的base模型;
t-foreach用于遍历变量books的每一项,通过控制器http.request.render()调用来获取;
t-field用于渲染记录字段的内容;
14、实现效果