odoo框架初识,简单小应用

odoo12版本学习

一·odoo简介

​ odoo是快速开发ERP系统的框架,适合商用. 内置crud,丰富的组件:看板,日历,图表.

​ odoo采用mvc架构模式. m即model,数据层, v及view,视图层(展示层),c即controller,逻辑层

odoo结构:使用开发者模式快速入门 Odoo 12

数据层:

​ 持久化层,负责存储.odoo借用PostgreSQL来实现. 不支持MySQL数据(可借用的第三方集成MySQL)

​ 文件附件,图片一类的二进制存储在filestore目录下

逻辑层

​ 负责与数据层交互 . odoo核心代码种,提供了ORM引擎,ORM提供插件模块与数据交互的API

展示层

​ 展示数据与用户交互, 自带web客户端.

​ 包含CMS框架,支持灵活创建网页

二·odoo环境配置

​ 本实验采用ubuntu系统

步骤一

​ 安装PostgreSQL数据库

sudo apt update
sudo apt install postgresql -y  # 安装PostgreSQL
sudo su -c "createuser -s $USER" postgres # 创建数据库超级用户

步骤二

​ 安装python3环境,以及其他依赖

sudo apt update
sudo apt upgrade

# 安装Git
sudo apt install git -y 
# 安装python3
sudo apt install python3-dev python3-pip -y # Python 3 for dev
# 安装依赖
sudo apt install build-essential libxslt-dev libzip-dev libldap2-dev libsasl2-dev libssl-dev -y

# 安装Node.js和包管理器
sudo apt install npm 
sudo ln -s /usr/bin/nodejs /usr/bin/node # 通过node运行Node.js
sudo npm install -g less less-plugin-clean-css # 安装less

步骤三

​ 安装odoo源码

1.在 Home 目录创建工作目录
	# 创建工作目录
	mkdir ~/odoo-dev 
	# 进入工作目录
	cd ~/odoo-dev 
	
2.克隆odoo12版本源码
	git clone https://github.com/odoo/odoo.git -b 12.0 --depth=1 # 获取 Odoo 源码
	
3.安装odoo所需的依赖
	pip3 install -r ~/odoo-dev/odoo/requirements.txt
	
4.安装odoo其他依赖包
	pip3 install num2words phonenumbers psycopg2-binary watchdog xlwt

步骤四

​ 启动odoo服务

~/odoo-dev/odoo/odoo-bin

# 默认监听端口8069 ,访问 http://localhost:8069

步骤五

​ 管理odoo数据库

# 由于采用的是psql数据库,需要手动创建

1.创建PostgreSQL数据库
	createdb MyDB
2.创建odoo数据库
	 createdb --template=MyDB MyDB2
3.删除数据库 # dropdb是不可撤销的,永久性删除
	dropdb MyDB2  
# 访问odoo客户端 页面会出现以下内容
Database Name:数据库的标识名称,在同一台服务器上可以有多个数据库

Email:管理员的登录用户名,可以不是 email 地址

Password:管理员登录的密码

Language:数据库的默认语言

Country:数据库中公司数据所使用的国家,这个是可选项,与发票和财务等带有本地化特征的应用中会用到

Demo data:勾选此选项会在数据库中创建演示数据,通常在开发和测试环境中可以勾选


## 服务端添加了master password , 要求输入密码,目的是阻止未经授权的管理员操作

odoo其他配置

### 修改监听的端口 , 即可运行多个odoo实例	
	 ~/odoo-dev/odoo/odoo-bin --http-port=8070
	 ~/odoo-dev/odoo/odoo-bin --http-port=8071 


### 数据库选项
	Odoo 开发时,经常会使用多个数据库,有时还会用到不同版本。在同一端口上停止、启动不同服务实例,或在不同数据库间切换,会导致网页客户端会话异常。因为浏览器会存储会话的 Cookie。
	# 它接收一个正则表达式来过滤可用数据库名,要精确匹配一个名称,表达式需要以^开头并以$结束
 	~/odoo-dev/odoo/odoo-bin --db-filter=^testdb$
    
### 管理服务器日志消息
	 –log-level=debug参数
     参数如下:
        debug_sql:查看服务中产生的 SQL 查询
        debug_rpc:服务器接收到的请求详情
        debug_rpc_answer:服务器发送的响应详情

安装第三方插件

# Odoo应用商店可以下载一系列模块安装到系统中, 为 Odoo 添加模块,仅需将其拷贝到官方插件的 addons 文件夹中即可


例子:
	# 1.拷贝library模块
	cd ~/odoo-dev
	git clone https://github.com/alanhou/odoo12-development.git library
	

	# 2.配置插件(add-ons)路径
	"""
	Odoo 服务有一个addons_path参数可设置查找插件的路径,默认指向Odoo 服务所运行处的/addons文件夹。我们可以指定多个插件目录,这样就可以把自定义模块放到另一个目录下,无需与官方插件混到一起。
	"""
	
	cd ~/odoo-dev/odoo
	./odoo-bin -d 12-library --addons-path="../library,./addons"

激活开发者模式

# 方式一  在地址栏添加参数
	未添加: http://127.0.0.1:8069/web#
	添加: http://127.0.0.1:8069/web?debug=1#
# 方式二 
	找到设置(Settings),最底下有激活开发者模式选项

三·odoo模块结构

# 原博客地址:
https://alanhou.org/odoo12-first-application/

####  在odoo开发中,一个应用就相当于是一个模块

分析模块结构

# odoo 的结构

- modelname
	- controllers 
		- __init__.py
		- main.py
    - i18n  #翻译文件
    	- zh-cn.po # po翻译文件
	- data  # 数据文件
		- book_demo.xml
		- library.book.csv
	- models  # 模型
		- __init__.py
		- library_book.py
	- reports  # 报表
		- __init__.py
		- library_book_report.py
		- library_book_report.xml
	- security  # 安全 权限
		- ir.model.access.csv
		- library_security.xml
	- static   # 静态资源
		- src
			- js
			- css
	- tests    # 测试
		- __init__.py
		- test_book.py
	- views    # 展示视图
		- book_list_template.xml
	- __init__.py 
	- __manifest__.py # 模块信息描述文件

四·创建应用

添加顶级菜单

​ 菜单项是使用 XML 文件中添加的视图组件,通过创建views/library_menu.xml来定义菜单项:

<?xml version="1.0"?>
<odoo>
    <!-- Library App Menu -->
    <menuitem id="menu_library" name="Library" />
</odoo>

​ 需要在__manifest__.py中使用 data 属性来添加安装或更新时需要加载的模块列表

'data': [
        'views/library_menu.xml',
],

添加权限组

​ Odoo 中使用安全组来实现,权限授予组,组中分配用户。Odoo 应用通常有两个组:针对普通用户的用户组,包含额外应用配置权限的管理员组。

​ 权限安全相关的文件通常放在模块下/security子目录中,创建security/library_security.xml 文件来进行权限定义

<?xml version="1.0" ?>
<odoo>
    <record id="module_library_category" model="ir.module.category">
        <field name="name">Library</field>
    </record>
</odoo>

添加两个安全组,首先添加用户组

<?xml version="1.0" ?>
<odoo>
    <record id="module_library_category" model="ir.module.category">
        <field name="name">Library</field>
    </record>

    <!-- 加入安全组 Library User Group -->
    <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>
</odoo>
### 字段说明
    name:   组名
    
    category_id:关联应用,这是一个关联字段,因此使用了 ref 属性来通过 XML ID 连接已创建的分类
    
    implied_ids:这是一个one-to-many关联字段,包含一系列组来对组内用户生效。这里使用了一个特殊语法,

创建管理员组,授予用户组的所有权限以及为应用管理员保留的其它权限

<?xml version="1.0" ?>
<odoo>
    <record id="module_library_category" model="ir.module.category">
        <field name="name">Library</field>
    </record>

    <!-- 添加用户组 Library User Group -->
    <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>
    
    <!--创建管理员组 Library Manager Group -->
    <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>

​ 同样需要在声明文件中添加该 XML 文件:

'data': [
    'security/library_security.xml',
    'views/library_menu.xml',
],

添加自动化测试

# 1.测试应放在tests/子目录中,在tests/__init__.py
	from . import test_book
# 2.在tests/test_book.py文件中添加实际的测试代码:
from odoo.tests.common import TransactionCase
class TestBook(TransactionCase):
    def setUp(self, *args, **kwargs):
        result = super().setUp(*args, **kwargs)
        self.Book = self.env['library.book']
        self.book_ode = self.Book.create({
            'name': 'Odoo Development Essentials',
            'isbn': '879-1-78439-279-6'})
        return result
    def test_create(self):
        "Test Books are active by default"
        self.assertEqual(self.book_ode.active, True)

模型层

创建数据模型
1. 在模块主__init__.py文件添加
    from . import models
2. 在models/__init__.py文件种引入模型
	from . import library_book
# 创建模型 models/library_book.py

from odoo import fields, models
class Book(models.Model):
    _name = 'library.book'  # 在视图中能够引用到
    _description = 'Book'   # 模型描述
    
    name = fields.Char('Title', required=True)
    isbn = fields.Char('ISBN')
    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')
#### odoo数据库基本字段类型

Binary:二进制类型,用于保存图片、视频、文件、附件等,在视图层显示为一个文件上传按钮。【Odoo底层对该类型字段的容量作了限制,最多能容纳20M内容】

Char:字符型,size属性定义字符串长度。

Boolean:布尔型

Float:浮点型,如 rate = fields.float('Relative Change rate',digits=(12,6)), digits定义数字总长和小数部分的位数。

Integer:整型

Date:短日期,年月日,在view层以日历选择框显示。

Datetime:时间戳。

Text:文本型,多用于多行文本框,可以用widget属性为它添加样式。

Html:与text类似,用于多行文本编辑,不过自带编辑器样式,并且会把内容以html解析。

Selection:下拉列表,枚举类型。


#### 关联字段类型
one2one: 一对一关系。 在V5.0以后的版本中不建议使用,而是用many2one替代。
    格式:
        fields.one2one(关联对象Name, 字段显示名, ... )
    
many2one: 多对一关系  
    格式:
        fields.many2one(关联对象Name, 字段显示名, ... )
    参数:
        comodel_name(string) -- 目标模型名称,除非是关联字段否则该参数必选
        domain -- 可选,用于在客户端筛选数据的domain表达式
        context -- 可选,用于在客户端处理时使用
        ondelete -- 当所引用的数据被删除时采取的操作,取值:'set null', 'restrict', 'cascade'
        auto_join -- 在搜索该字段时是否自动生成JOIN条件,默认False
        delegate -- 设置为True时可以通过当前model访问目标model的字段,与_inherits功能相同
        
        
one2many: 一对多关系 
    格式:
        fields.one2many(关联对象Name, 关联字段, 字段显示名, ... )
    参数:
        comodel_name -- 目标模型名称,
        inverse_name -- 在comodel_name 中对应的Many2one字段
        domain -- 可选,用于在客户端筛选数据的domain表达式
        context -- 可选,用于在客户端处理时使用
        auto_join -- 在搜索该字段时是否自动生成JOIN条件,默认False
        limit(integer) -- 可选,在读取时限制数量
        
many2many: 多对多关系
    格式: (生成第三表: ...._ref表)
  'category_id'=fields.many2many('res.partner.category','res_partner_category_rel','partner_id','category_id','Categories')

# 表示以多对多关系关联到对象res.partner.category,关联表为'res_partner_category_rel',关联字段为 'partner_id'和'category_id'。
# 当定义上述字段时,OpenERP会自动创建关联表为 'res_partner_category_rel',它含有关联字'partner_id'和'category_id'

    参数:
        comodel_name -- 目标模型名称,除非是关联字段否则该参数必选
        relation -- 关联的model在数据库存储的表名,默认采用comodel_name获取数据
        column1 -- 与relation表记录相关联的列名
        column2 --与relation表记录相关联的列名
        domain -- 用于在客户端筛选数据的domain表达式
        context -- 用于在客户端处理时使用
        limit(integer) --在读取时限制数量

        

#### 引用类型
related字段
	格式:
        字段=fields.类型(related="某个字段.类字段",store=true/false)
    # related字段可以简记为“带出字段”,由当前模型的某个关联类型字段的某个字段带出值。

reference字段
	reference是比related更高级的引用字段,可以指定该字段引用那些模型范围内的模型的哪些字段的值,范围更广。
  

#### Odoo保留字段
name(Char) -- _rec_name的默认值,在需要用来展示的时候使用
active(Boolean) -- 设置记录的全局可见性,当值为False时通过search和list是获取不到的
sequence(Integer) -- 可修改的排序,可以在列表视图里通过拖拽进行排序
state(Selection) -- 对象的生命周期阶段,通过fileds的states属性使用
parent_id(Many2one) -- 用来对树形结构的记录排序,并激活domain表达式的child_of运算符
parent_left,parent_right -- 与 _parent_store结合使用,提供更好的树形结构数据读取

#### 自动化属性
	id  Identifier field 是模型中每条记录的唯一数字标识符
    _log_access 是否创建日期字段,默认创建(default:True)
    create_date 记录创建日期 Type:Datetime
    create_uid 第一创建人 Type:res.users
    write_date 最后一次修改日期 Type:Datetime
    write_uid 最后一次修改人 Type:res.users
    _last_update 最后一次修改日期 它不存储在数据库,用于做并发检测
    display_name 对外显示的名称
	
    # 不想在model自动添加这些属性 ,在类种添加:
    	_log_access=False

#### Compute字段   
    ompute字段不是一种字段类型,而是指某个字段的值是计算出来的。
    一个字段的值,可以通过一个函数来动态计算出来。定义格式如下:
    
字段名=fields.类型(compute="函数名",store=True/false) #store定义了该动态改变的字段值是否保存到数据库表中

@api.depends(依赖的字段值)#depend的字段值一旦发生变化,就会触发该函数,从而更新compute字段值。
def 函数(self):
    self.字段=计算字段值
# 原博客https://www.cnblogs.com/ygj0930/p/10826099.html

设置访问权限

添加访问权限控制

​ 权限通过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

​ 注解:

# 1. 应注意该文件第一行后不要留有空格,否则会导致报错

# 2. csv列解读
	id         是记录的外部标识符(也称为XML ID),需在模块中唯一
    
	name       是描述性标题,仅在保证唯一时提供有用信息
    
	model_id   是赋权模型的外部标识符,模型有ORM自动生成的XML ID,对于library.book,标识符为model_library_book
    
	group_id   指明授权的安全组,我们给前文创建的安全组授权:library_group_user和library_group_manager
    
	perm_…     字段标记read读, write写, create创建, 或unlink删除权限,我们授予普通用户读权限、管理员所有权限
    
# 3. __manifest__.py的 data 属性中添加对新文件的引用    
    'data': [
        'security/library_security.xml',
        'security/ir.model.access.csv',
        'views/library_menu.xml',
    ],
行级权限规则

​ 添加记录规则,需编辑security/library_security.xml文件

记录规则在ir.rule中定义,和往常一样我们选择一个唯一名称。还应获取操作的模型及使用权限限制的域过滤器。域过滤器使用 Odoo 中常用的元组列表,添加域表达式.
<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>

视图层

视图类别:

​ tree视图,form表单视图,search搜索视图 等

添加菜单项

​ 添加相应菜单项。编辑views/library_menu.xml文件,在 XML 元素中定义菜单项以及执行的操作:

    <!-- Action to open the Book list -->
    <act_window id="action_library_book"
        name="Library Books"
        res_model="library.book"
        view_mode="tree,form"
    />
    <!-- Menu item to open the Book list -->
    <menuitem id="menu_library_book"
        name="Books"
        parent="menu_library"
        action="action_library_book"
    />

​ 注释:

1.<act_window> 元素定义客户端窗口操作,按找顺序通过启用列表和表单视图打开library.book模型
2.<menuitem> 定义一个调用 action_library_book操作的定义菜单

# act 是行为动作, menu是菜单.  页面上生成的菜单-->行为动作指向action
创建表单视图

​ 添加views/book_view.xml文件来定义表单视图:

<?xml version="1.0"?>
<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>
业务文件表单视图

​ header 和 sheet将form表单分成 两部分

​ header部分包含:可操作的button按钮, 状态栏

​ sheet部分包含: 数据列

<form string="Book">
    <header>
        <!-- 此处添加按钮 -->
        <button name="button_check_isbn" type="object"
        string="Check ISBN" />
    </header>
    <sheet>
        <group>
            <field name="name" />
            ...
        </group>
    </sheet>
</form>
Odoo 12 开发之创建第一个 Odoo 应用
组来组织表单

<group> 标签,用来组织表单视图的布局

​ 推荐在group 元素中添加 name 属性,更易于其它模块对其进行继承

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

​ 注释

# 1.  ir.ui.view 和record是固定搭配
# 2. name 视图名称 , id 当前视图唯一的xml ID标识符 , model依赖的模型  arch form视图固定搭配
# 3. 视图具有继承视图,即:在原有的视图上添加新的字段,新的展示内容

# 4. __manifest__.py 的 data中声明
	'views/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>
搜索视图

<search> 搜索视图

<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" />
            <!-- domain 过滤条件 -->
            <filter name="filter_active"
                    string="Active"
                    domain="[('active','=',True)]" />
            <filter name="filter_inactive"
                    string="Inactive"
                    domain="[('active','=',False)]" />
        </search>
    </field>
</record>

Odoo 12 开发之创建第一个 Odoo 应用

业务逻辑层

​ 业务逻辑层编写应用的业务规则,如验证和自动计算。

添加业务逻辑
# 模型中有isbn字段, 上文在表单视图中添加了button按钮 name为:button_check_isbn . 现在为其添加上业务校验逻辑

# 在models的 Book 模型中添加 校验逻辑

	#1. 检验 isbn有效行
    def _check_isbn(self):
        self.ensure_one() 
        isbn = self.isbn.replace('-', '') # 为保持兼容性 Alan 自行添加
        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
        
        
     # 2. 按钮点击时触发此方法  button_check_isbn 方法与button的name必须同名
    from odoo import api, fields, models
	from odoo.exceptions import Warning
    def button_check_isbn(self):
        for book in self:
            if not book.isbn:
                raise Warning('Please provide 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
总结:
1.在处理业务逻辑是,添加异常后,odoo自动做事务回滚.
2.业务逻辑一般放在models定义的模型类中 , 本身是由orm去操作的,需要成熟面向对象思想

网页和控制器

​ 在页面上响应数据

# 1. 在library_app/__init__.py导入控制器模块目录加入控制器
	from . import models
    from . import controllers
# 2.创建main.py,在library_app/controllers/__init__.py文件来让目录可被 Python 导入
	from . import main
    
# 3.定义代码
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})

    
### 注释: 
		# 1. http.request.env获取环境,从目录中获取有效图书记录集
    	# 2. http.request.render() 来处理 library_app.index_template Qweb 模板并生成输出 HTML
        # 3. auth 制定访问的授权模式
		# 4. {'books':books} book图书集合传递到Qweb中
QWeb模板是视图类型

​ 放在/views子目录下,创建views/book_list_template.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>

​ 注释:

​ 1.<template>

​ 2. t-foreach用于遍历变量 books的每一项

​ 3. t-field用于渲染记录字段的内容

基于博客:https://alanhou.org/odoo12-first-application/

posted @ 2020-05-14 14:08  染指未来  阅读(1774)  评论(0编辑  收藏  举报