💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
> 警告 > 本教程需要已经安装odoo [TOC] #### 启动/停止Odoo服务器 Odoo采用C/S架构,客户端通过Web浏览器访问服务端,遵从RPC协议。业务逻辑和扩展通常在服务端执行,而只有添加客户端支持的新特征才会在客户端添加代码(例如,交互过程中新数据的映射表示)。启动服务器,只需要在shell中调用命令odoo-bin,或者完整的路径名调用: ~~~ odoo-bin ~~~ 通过`Ctrl-c`或杀死相应的系统进程来停止Odoo服务。 #### 构建一个Odoo模块 服务端扩展和客户端扩展都被封装为模块,这些模块可选择性的被安装,安装完成后通过数据库来加载。模块即可以是全新的业务逻辑,也可以是更改和扩展已有的业务逻辑。比如创建一个中国会计模块,将中国的会计准则添加到Odoo的通用会计中,也可以创建一个全新的实时可视化管理车队的模块。Odoo中的所有功能都是包含在模块中。 ##### 模块的组成 Odoo模块包含多个部分: **业务对象**   Python类,这些类会被Odoo框架自动持久化,持久化的方式决定于类的定义。 **数据文件**   包括视图、菜单、动作、工作流、权限、演示数据等,以XML或CSV文件定义。 **Web控制器**   处理Web浏览器的请求 **静态页面数据**   网站或界面使用的图片、CSS或JavaScript文件 ##### 模块结构 每个模块都是模块目录中的一个子目录。可以通过`--addons-path`选项指定模块目录的路径。 > 提示 > 大多数命令行选项可以通过配置文件进行设置 Odoo模块由清单文件进行声明。查看清单文件文档了解详细信息。模块是一个包含`__init__.py`文件的的Python包,`__init__.py`文件包含了模块需要的导入的各Python文件。 例如,如果模块中包含`mymodule.py`文件,`__init__.py`应该这样写: ~~~ from . import mymodule ~~~ Odoo提供了脚手架机制来快速创建新模块,`odoo-bin`子命令`scaffold`用来创建一个空模块 ~~~ $ odoo-bin scaffold <模块名> <模块放置路径> ~~~ 该命令为模块创建一个子目录,并自动为模块创建一些标准文件。这些文件大多只包含被注释的代码和XML元素。后面将解释这些文件的含义。 > **练习创建模块** > 使用上面的命令行创建一个空模块*Open Academy*,并将其安装在Odoo中。 > > 1. 调用命令`odoo-bin scaffold openacademy addons` > 2. 修改模块中的相关文件 > 3. 不要修改其它文件 `openacademy/__manifest__.py` ~~~ # -*- coding: utf-8 -*- { 'name': "Open Academy", 'summary': """Manage trainings""", 'description': """ Open Academy module for managing trainings: - training courses - training sessions - attendees registration """, 'author': "My Company", 'website': "http://www.yourcompany.com", # Categories can be used to filter modules in modules listing # Check https://github.com/odoo/odoo/blob/master/odoo/addons/base/module/module_data.xml # for the full list 'category': 'Test', 'version': '0.1', # any module necessary for this one to work correctly 'depends': ['base'], # always loaded 'data': [ # 'security/ir.model.access.csv', 'templates.xml', ], # only loaded in demonstration mode 'demo': [ 'demo.xml', ], } ~~~ `openacademy/__init__.py` ~~~ # -*- coding: utf-8 -*- from . import controllers from . import models ~~~ `openacademy/controllers.py` ~~~ # -*- coding: utf-8 -*- from odoo import http # class Openacademy(http.Controller): # @http.route('/openacademy/openacademy/', auth='public') # def index(self, **kw): # return "Hello, world" # @http.route('/openacademy/openacademy/objects/', auth='public') # def list(self, **kw): # return http.request.render('openacademy.listing', { # 'root': '/openacademy/openacademy', # 'objects': http.request.env['openacademy.openacademy'].search([]), # }) # @http.route('/openacademy/openacademy/objects/<model("openacademy.openacademy"):obj>/', auth='public') # def object(self, obj, **kw): # return http.request.render('openacademy.object', { # 'object': obj # }) ~~~ `openacademy/demo.xml` ~~~ <odoo> <data> <!-- --> <!-- <record id="object0" model="openacademy.openacademy"> --> <!-- <field name="name">Object 0</field> --> <!-- </record> --> <!-- --> <!-- <record id="object1" model="openacademy.openacademy"> --> <!-- <field name="name">Object 1</field> --> <!-- </record> --> <!-- --> <!-- <record id="object2" model="openacademy.openacademy"> --> <!-- <field name="name">Object 2</field> --> <!-- </record> --> <!-- --> <!-- <record id="object3" model="openacademy.openacademy"> --> <!-- <field name="name">Object 3</field> --> <!-- </record> --> <!-- --> <!-- <record id="object4" model="openacademy.openacademy"> --> <!-- <field name="name">Object 4</field> --> <!-- </record> --> <!-- --> </data> </odoo> ~~~ `openacademy/models.py` ~~~ # -*- coding: utf-8 -*- from odoo import models, fields, api # class openacademy(models.Model): # _name = 'openacademy.openacademy' # name = fields.Char() ~~~ `openacademy/security/ir.model.access.csv` ~~~ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink access_openacademy_openacademy,openacademy.openacademy,model_openacademy_openacademy,,1,0,0,0 ~~~ `openacademy/templates.xml` ~~~ <odoo> <data> <!-- <template id="listing"> --> <!-- <ul> --> <!-- <li t-foreach="objects" t-as="object"> --> <!-- <a t-attf-href="{{ root }}/objects/{{ object.id }}"> --> <!-- <t t-esc="object.display_name"/> --> <!-- </a> --> <!-- </li> --> <!-- </ul> --> <!-- </template> --> <!-- <template id="object"> --> <!-- <h1><t t-esc="object.display_name"/></h1> --> <!-- <dl> --> <!-- <t t-foreach="object._fields" t-as="field"> --> <!-- <dt><t t-esc="field"/></dt> --> <!-- <dd><t t-esc="object[field]"/></dd> --> <!-- </t> --> <!-- </dl> --> <!-- </template> --> </data> </odoo> ~~~ #### 对象关系映射 Odoo的关键组件是ORM层。这个层避免了大量手写SQL,并提供扩展性和安全性。业务对象被声明为`Model`类的扩展类,并自动将它们集成到持久层中。可以通过定义时设置属性来配置模型。最重要的属性是`_name`,必填属性,它定义了模块在Odoo系统中的名称。一个最简单的模型定义: ~~~ from odoo import models class MinimalModel(models.Model): _name = 'test.model' ~~~ #### 模型字段 字段定义了模型中需要存储的内容和存储的位置。字段通过类的属性来定义: ~~~ from odoo import models, fields class LessMinimalModel(models.Model): _name = 'test.model2' name = fields.Char() ~~~ **通用属性** 和模型一样,字段也可以配置,字段通过属性参数的方式来配置: ~~~ name = field.Char(required=True) ~~~ 一些属性可以用于所有字段,以下是最常见的属性: `string(unicode,default: field's name)`   用户界面中的字段标签(用户可见) `required(bool,default:False)`   如果为True,这个字段不能为空,它必须有一个默认值或者在创建记录时总是给定一个值。 `help (unicode, default: '')`   长格式,在用户界面上显示的提示。 `index (bool, default: False)`   请求Odoo在列上创建数据库索引。 **简单字段** 有两大类字段:简单字段和关联字段,简单字段的值是存储在模型表中的原子值,而关联字段是指向模型(相同模型或不同模型)的记录行。 简单字段的例子如:Boolean、Date、Char 关联字段的例子如:Many2One、One2Many、Many2Many **保留字段** Odoo在所有模型中都创建几个固定字段,这些字段由系统管理,用户程序不应写入。但是可以在用户程序中读取: `id(Id)`   模型中记录的唯一标识符 `create_date(Datetime)`   记录的创建日期 `create_uid(Many2one)`   创建记录的用户 `write_date(Datetime)`   记录的最后修改时间 `write_uid(Many2one)`   上次修改记录的用户 **特殊字段** 默认情况下,Odoo的name在所有模型上还需要一个字段,用于显示和搜索。用于这些目的的字段可以通过设置字段`_rec_name`来实现。 > 练习定义模型,在openacademy模块中定义新的数据模型*课程*,每门课程包含两个字段,标题和描述,其中标题是必填字段。编辑文件`openacademy/models/models.py`以包含`Course`类。 `openacademy/models.py` ~~~ from odoo import models, fields, api class Course(models.Model): _name = 'openacademy.course' name = fields.Char(string="Title", required=True) description = fields.Text() ~~~ #### 数据文件 Odoo是一个高度数据驱动的系统,虽然行为是通过Python代码制定的,但一些模块的值是在加载时通过数据文件设置的。 > 提示: > 一些模块的作用仅仅是为了将数据添加到Odoo中 模块数据通过带有`<record>`元素的XML数据文件来声明。每个`<record>`元素创建或更新数据库中的一个记录行。 ~~~ <odoo> <data> <record model="{model name}" id="{record identifier}"> <field name="{a field name}">{a value}</field> </record> </data> </odoo> ~~~ * `model`是在记录行中记录的Odoo模型名称 * `id`是外部标识符,它允许引用记录(而不必知道其在数据库中的标识符) * `<field>`,每个`<field>`对应数据行中的一个字段,name属性是字段名(例如*description*),而`body`就是字段的值。 数据文件通过在manifest文件声明而被载入,他们既可以在`data`列表声明(始终载入)也可以在`demo`列表声明(仅在演示模式下载入) > 练习定义演示数据,添加演示数据以填充*Course*模型的数据,编辑文件`openacademy/demo/demo.xml`来添加演示数据 `openacademy/demo/demo.xml` ~~~ <odoo> <data> <record model="openacademy.course" id="course0"> <field name="name">Course 0</field> <field name="description">Course 0's description Can have multiple lines </field> </record> <record model="openacademy.course" id="course1"> <field name="name">Course 1</field> <!-- no description for this one --> </record> <record model="openacademy.course" id="course2"> <field name="name">Course 2</field> <field name="description">Course 2's description</field> </record> </data> </odoo> ~~~ #### 操作和菜单 操作和菜单是数据库中的常规数据,通常以数据文件声明。操作可以通过三种方式触发: 1.点击菜单项(链接到特定操作) 2.点击视图中的按钮(如果这些按钮已经被连接到操作) 3.作为对象的上下文操作 因为菜单的声明相对复杂,所以有个一个`<menuitem>`的快捷方式来声明`ir.ui.menu`菜单对象,并将其更容易的连接到对应的操作。 ~~~ <record model="ir.actions.act_window" id="action_list_ideas"> <field name="name">Ideas</field> <field name="res_model">idea.idea</field> <field name="view_mode">tree,form</field> </record> <menuitem id="menu_ideas" parent="menu_root" name="Ideas" sequence="10" action="action_list_ideas"/> ~~~ > 危险 > 操作必须在XML文件中对应的菜单之前声明.数据文件是按顺序执行的,操作的`id`必须在对应的菜单建立之前就存在于数据库中。 > > 练习定义新菜单项,在开放学院菜单项下定义新菜单项来访问课程。用户应该能够: > > * 显示所有课程的列表 > * 建立或编辑课程 > 1.建立`openacademy/views/openacademy.xml`以创建操作和能够触发操作的菜单项。 > 2.添加这个文件到`openacademy/__manifest__.py`下的`data`列表。 `openacademy/__manifest__.py` ~~~ 'data': [ # 'security/ir.model.access.csv', 'templates.xml', 'views/openacademy.xml', ], # only loaded in demonstration mode 'demo': [ ~~~ `openacademy/views/openacademy.xml` ~~~ <?xml version="1.0" encoding="UTF-8"?> <odoo> <data> <!-- window action --> <!-- The following tag is an action definition for a "window action", that is an action opening a view or a set of views --> <record model="ir.actions.act_window" id="course_list_action"> <field name="name">Courses</field> <field name="res_model">openacademy.course</field> <field name="view_type">form</field> <field name="view_mode">tree,form</field> <field name="help" type="html"> <p class="oe_view_nocontent_create">Create the first course </p> </field> </record> <!-- top level menu: no parent --> <menuitem id="main_openacademy_menu" name="Open Academy"/> <!-- A first level in the left side menu is needed before using action= attribute --> <menuitem id="openacademy_menu" name="Open Academy" parent="main_openacademy_menu"/> <!-- the following menuitem should appear *after* its parent openacademy_menu and *after* its action course_list_action --> <menuitem id="courses_menu" name="Courses" parent="openacademy_menu" action="course_list_action"/> <!-- Full id location: action="openacademy.course_list_action" It is not required when it is the same module --> </data> </odoo> ~~~ 作者:luohuayong 链接:http://www.jianshu.com/p/06e38310a465 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。