odoo 之 结构化应用数据

odoo 之 结构化应用数据

一.模型深入了解

​ 模型是 Odoo 框架的核心,它们描述应用的数据结构,是应用服务和数据库存储之间的桥梁

​ 模型类型:普通(regular)、临时(transient)和抽象(abstract)类型。

模型属性

### 常用属性
_name    #  是我们创建的 Odoo 模型的内部标识符,在创建新模型时为必填。
_description #是对用户友好的模块记录标题,在用户界面中查看模型时显示。可选但推荐添加。
_order   # 设置浏览模型记录时或列表视图的默认排序。其值为 SQL 语句中 order by 使用的字符串,所以可以传入符合 SQL 语法的任意值,它有智能模式并支持可翻译及many-to-one字段名。


如:
    class Book(models.Model):
    _name = 'library.book'
    _description = 'Book'
    _order = 'name, date_published desc'
    
    
### 高级属性
_rec_name   # 在从关联字段(如many-to-one关联)中引用时作为记录描述。默认使用模型中常用的 name字段,但可以指定任意其它字段。

_table   # 是模型对应的数据表名。默认表名由 ORM 通过替换模块名中的点为下划线来自动定义,但是可通过该属性指定表名。

_log_access=False   #  用于设置不自动创建审计追踪字段:create_uid, create_date, write_uid和write_date。

_auto=False  #  用于设置不自动创建模型对应的数据表。如有需要,可通过重载init()方法来创建数据库对象:数据表或视图。

模型和 Python 类

​ Odoo的模型保存在中央注册表(central registry)中,可通过 env 环境对象(老 API 中称为 pool)获取

它是一个数据库保存所有可用模型类引用的字典,其中的词条可通过模型名引用 。

​ 具体来说,模型方法中的代码可使用self.env[‘library.book’]来获取表示 library.book模型的模型类

# 模型名非常重要,因为它是访问该注册表的关键。模型名的规则是以点号连接的小写单词 . 如library.book或library.book.category

# 由于历史原因,有些内核模型没有遵循这一规则,如res.users。
# 1. 模型名必须全局唯一,因此第一个单词应使用模块关联的主应用对应.以图书应用来说,模型的前缀名使用library. 其它内核模块如 project,crm和sale

# 2. 另一方面 Python 类仅为所声明文件本地内容,名称仅需在代码文件中唯一即可。因为类名不会与其它模块中的类产生冲突,也就不需为其添加主应用相关的前缀

# 3. 类的命名遵守驼峰原则CamelCase,与pep8规范一致

临时(Transient)模型和抽象模型

# 临时模型继承models.TransientModel类 
	1. 作用: 用于向导式的用户交互 , 数据会临时性的存储
	2. 清理: 定期运行清空job来清楚这些表中的老数据.
        
        
# 抽象模型继承models.AbstractModel类
	1. 不带有数据存储, 抽象模型可用于功能集,配合odoo继承功能的其它模型使用
    2. 比如:mail.thread是Discuss应用种的一个抽象模型,用于为其它模型添加消息和follower功能

检查已有模型

​ python类创建的模型和字段,在用户界面中有自己的元标签

​ Settings > Technical > Database Structure > Models,这里有数据库中的所有模型。点击列表中的模型会打开详情表单

# 上图中在右上角 In Apps字段中可以看到library.book模型的定义来自library_app和library_member两个模块。


# 下方区域中还有几个包含附加信息的标签
    Fields可快速查看模型字段
    Access Rights是授予不同权限组的访问控制规则
    Views显示模型所带的视图列表
    
    
# 开发者可通过 View Metadata选项查看模型的外部标识符。  
	如: library.book模型的外部标识符为model_library_book
	在定义安全访问控制列表经常在 CSV 文件中使用到这些XML ID。

二.模型字段深入了解

基本字段类型

#####
Char(string)  # 是一个单行文本,唯一位置参数是string字段标签。

Text(string)  # 是一个多行文本,唯一位置参数是string字段标签。

Selection(selection, string)  # 是一个下拉选择列表。选项位置参数是一个[(‘value’, ‘Title’),]元组列表。元组第一个元素是存储在数据库中的值,第二个元素是展示在用户界面中的描述。该列表可由其它模块使用selection_add关键字参数扩展。

Html(string)  # 存储为文本字段,但有针对用户界面 HTML 内容展示的特殊处理。出于安全考虑,该字段会被清洗,但清洗行为可被重载。

Integer(string)  # 仅需字段标题字符串参数。

Float(string, digits)  # 带有第二个可选参数digits,该字段是一个指定字段精度的(x,y)元组,x 是数字总长,y 是小数位。

Monetary(string, currency_field)  # 与浮点字段类似,但带有货币的特殊处理。第二个参数currency_field用于存储所使用货币,默认应传入currency_id字段。

Date(string)和Datetime(string)  # 字段只需一个字符串文本位置参数。

Boolean(string)  # 的值为True 或False,可传入一个字符串文本位置参数。

Binary(string)  # 存储文件类二进制文件,只需一个字符串文本位置参数。它可由Python使用 base64编码字符串进行处理。
##### 文本字符串 Char Text Html的特有属性
	size  #  (Char)设置最大允许尺寸。无特殊原因建议不要使用,例如可用于带有最大允许长度的社保账号。
	
    translate  #  使用得字段内容可翻译,带有针对不同语言的不同值。

    trim默认值为 True,  #  启动在网络客户端中自动去除周围的空格。可通过设置trim=false来取消。

常用字段属性

tring  #  是字段的默认标签,在用户界面中使用。除Selection和关联字段外,它都是第一个位置参数,所以大多数情况下它用作关键字参数。如未传入,将由字段名自动生成。

default   #   设置字段默认值。可以是具体值(如 active字段中的default=True),或是可调用引用,有名函数或匿名函数均可。

help  #  提供 UI 中鼠标悬停字段向用户显示的提示文本。

readonly=True  #  
会使用户界面中的字段默认不可编辑。在 API 层面并没有强制,模型方法中的代码仍然可以向其写入。仅针对用户界面设置。

required=True  #  使得用户界面中字段默认必填。这通过在数据库层面为列添加NOT NULL 约束来实现。

index=True  #  为字段添加数据库索引,让搜索更快速,但同时也会部分降低写操作速度。

copy=False  #  让字段在使用 ORM copy()方法复制字段时忽略该字段。除 to-many 关联字段外,其它字段值默认会被复制。

groups  #  可限制字段仅对一些组可访问并可见。值为逗号分隔的安全组XML ID列表,如groups=’base.group_user,base.group_system’。

states  #  传入依赖 state字段值的 UI 属性的字典映射值。可用属性有readonly, required和invisible,例如states={‘done’:[(‘readonly’,True)]}。


##注意
	states 字段等价于视图中的 attrs 属性。同时注意视图也支持 states 属性,但用途不同,传入逗号分隔的状态列表来控制元素什么时候可见。
    
    
    
# 例子
name = fields.Char(
        'Title',
        default=None,
        index=True,
        help='Book cover title',
        readonly=False,
        required=True,
        translate=False,
    )
# default属性 是固定值, 或者引用函数来计算默认值. 对于简单的运算,使用lambda即可. 如下:

last_borrow_date = fields.Datetime(
        'Last Borrowed On',
        default=lambda self: fields.Datetime.now(),
    )

# 默认值也可以是一个函数引用,或者待定义函数名字符串
    last_borrow_date = fields.Datetime(
        'Last Borrowed On',
        default='_default_last_borrow_date',
    )

    def _default_last_borrow_date(self):
        return fields.Datetime.now()
    
    
### 当模块数据结构在不同版本种,下列两个属性非常重要
	deprecated=True  # 在字段被使用时记录一条 warning 日志
    oldname=’field’  # 是在新版本中重命名字段时使用,可在升级模块时将老字段中的数据自动拷贝到新字段中

特殊字段名

​ 出于特殊目的作为 ORM 保留字,也是默认字段

# 只要模型中没有设置  _log_access=False , 都会在新模型中自动创建

create_uid为创建记录的用户
create_date是记录创建的日期和时间
write_uid是最后写入记录的用户
write_date是最后修改记录的日期和时间
### 一些内置 API 功能默认需要一些指定字段名。避免在不必要的场合使用这些字段名会让开发更轻松。其中有些字段名被保留并且不能在其它地方使用

name #  (通常为 Char)默认作为记录的显示名称。通过是一个 Char,但也可以是 Text 或Many2one字段类型。用作显示名的字段可修改为_rec_name模型属性。

active   #  (Boolean型)允许我们关闭记录。带有active=False的记录会自动从查询中排除掉。可在当前上下文中添加{‘active_test’: False} 来关闭这一自动过滤。可用作记录存档或假删除(soft delete)。

state    #   (Selection类型) 表示记录生命周期的基本状态。它允许使用states字段属性来根据记录状态以具备不同的 UI 行为。动态修改视图:字段可在特定记录状态下变为readonly, required或invisible。

parent_id和parent_path Integer和Char型)  #  对于父子层级关系具有特殊意义。本文后续会进行讨论。

三.模型关系深入了解

​ 具体的用例就是层级关联,即一个模型中的记录与同模型中的其它记录关联。

​ Odoo 框架还支持弹性关系,即一个字段可指向其它表中的字段,这称为引用字段。

Many-to-one关联

​ 多对一

# 格式:
publisher_id = fields.Many2one(
        'res.partner', string='Publisher')



# 参数:
    ondelete #  定义关联记录删除时执行的操作:
    set null  # (默认值): 关联字段删除时会置为空值
    restricted #  :抛出错误阻止删除
    cascade:#   在关联记录删除时同时删除当前记录
    context  # 是一个数据字典,可在浏览关联时为网页客户端传递信息,比如设置默认值
    domain  # 是一个域表达式:使用一个元组列表过滤记录来作为关联记录选项
    auto_join=True  #  允许ORM在使用关联进行搜索时使用SQL连接。使用时会跳过访问安全规则,用户可以访问安全规则不允许其访问的关联记录,但这样 SQL 的查询会更有效率且更快。
	delegate=True   # 创建一个关联记录的代理继承。使用时必须设置required=True和ondelete=’cascade’。

One-to-many反向关联

​ one-to-many关联是many-to-one的反向关联

​ 在图书模型中,publisher_id和parnter是 many-to-one. 同样说明 在partner模型中是有一个one-to-many的反向模型

# 在partner 模型中添加 library_app/models/res_partner.py下代码:
from odoo import fields, models

class Partner(models.Model):
    _inherit = 'res.partner'
    published_book_ids = fields.One2many(
        'library.book', # related model
        'publisher_id', # fields for "this" on related model
        string='Published Books')
    
# One2many字段接收三个位置参数说明
	关联模型 (comodel_name关键字参数) # 关联的模型
	引用该记录的模型字段 (inverse_name关键字参数) # 引用关联模型的字段
	字段标签 (string关键字参数) # 提示
    
# 其它可用的关键字参数与many-to-one字段相同:context, domain和ondelete 

Many-to-many关联

​ 书和作者之间是many-to-many关联:一本书可以有多个作者,一个作者可以有多本书

# 图书
class Book(models.Model):
    _name = 'library.book'
...
    author_ids = fields.Many2many(
        'res.partner', string='Authors')
        
# 作者端
class Partner(models.Model):
    _inherit = 'res.partner'
    book_ids = fields.Many2many(
        'library.book', string='Authored Books')
    
    
# 格式:
author_ids = fields.Many2many(
    'res.partner', # 关联模型(尾款)
    'library_book_res_partner_rel', # 要使用的关联表名
    'a_id', # 本记录关联表字段
    'p_id', # 关联记录关联表字段
    'Authors') # string标签文本

# many2many 默认自动生成第三张表
	# 1.模型名称过程,psql默认是63个字符,需要手动指定关联名
    # 2.手动创建时,避免冲突

# 参数 和one-to-many类似,还可以使用context, domain和auto_join这些关键字参数

# 在创建抽象模型时,many-to-many中不要使用column1和column2属性。在 ORM 设计中对抽象模型有一个限制,如果指定关联表列名,就无法再被正常继承。    

层级关联

​ 父子树状关联使用同一模型中many-to-one关联表示,来将每条记录引用其父级。反向的one-to-many关联对应记录的子级。Odoo 通过域表达式附加的child_of和parent_of操作符改良了对这些层级数据结构的支持。只要这些模型有parent_id字段(或_parent_name有效模型定义)就可以使用这些操作符。

​ 通过设置_parent_store=True和添加parent_path帮助字段可加快层级树的查询速度。该字段存储用于加速查询速度的层级树结构信息。

from odoo import api, fields, models

class BookCategory(models.Model):
    _name = 'library.book.category'
    _description = 'Book Category'
    _parent_store = True

    name = fields.Char(translate=True, required=True)
    # Hierarchy fields
    parent_id = fields.Many2one(
        'library.book.category',
        'Parent Category',
        ondelete='restrict')
    parent_path = fields.Char(index=True)

    # Optional but good to have:
    child_ids = fields.One2many(
        'library.book.category',
        'parent_id',
        'Subcategories')
    
    
### 这里定义了一个基本模型,包含引用父级记录的parent_id字段。为启用层级索引来加快树级搜索,添加了一个_parent_store=True 模型属性。使用该属性必须要添加且必须要索引parent_path字段。引用父级的字段名应为parent_id,但如果声明了可选的_parent_name模型属性,则可以使用任意其它字段名。    

引用字段的弹性关联

​ 普通关联字段指定固定的引用co-model模型,但是Reference字段类型不受这一点限制,支持弹性关联

​ 因此相同字段不用限制只指向相同的目标模型。

# 图书分类模型来添加引用重点图书或者作者,因此该字段可引用图书或 partner:

class BookCategory(models.Model):
...   
    highlighted_id = fields.Reference(
        [('library.book', 'Book'), ('res.partner', 'Author')],
        'Category Highlight'
    )

###该字段定义与 selection 字段相似,但这里选择项为该字段中可以使用的模型。在用户界面中,用户会先选择列表中的模型,然后选择模型中的指定记录。

# 技术细节:
	# 1. 引用字段在数据库中以model,id字符串形式存储
	# 2. read()方法供外部应用使用,以格式化的(‘model_name’, id)元组返回,而不是常用的many-to-one字段的(id, ‘display_name’)形式

四.计算字段

​ 计算字段声明和普通字段类似, 有一个额外的compute参数来定义计算函数

例如: 添加出版商的国别 依赖 出版商的 country_id

class Book(models.Model):
...
    publisher_country_id = fields.Many2one(
        'res.country', string='Publisher Country',
        compute='_compute_publisher_country'
    )

    @api.depends('publisher_id.country_id')
    def _compute_publisher_country(self):
        for book in self:
            book.publisher_country_id = book.publisher_id.country_id
            
            
### 注意 
	计算字段必须分配值,如果没有分配值则会报错.

五.模型约束

​ 模型约束的目的是: 通过验证保证数据完整性和正确性.

​ psql支持多可用验证: 避免重复,检查条件是否符合等. 如若要求更复杂的逻辑,还是用python代码实现约束

SQL模型约束

​ 由PostgreSQL直接执行, 由_sql_constraints类属性来定义.

​ 是由元组组成的列表,格式(name,code,error)

# name 是约束标识名
# code 是约束的PostgreSQL语法
# error 是在约束验证未通过时向用户显示的错误消息
_sql_constraints = [
        ('library_book_name_date_uq', # 约束唯一标识符
        'UNIQUE (name, date_published)', # 约束 SQL 语法
        'Book title and publication date must be unique'), # 消息
    
        ('library_book_check_date',
        'CHECK (date_published <= current_date)',
        'Publication date must not be in the future.'),
    ]

Python模型约束

​ 自定义代码来检查条件,@api.constrains装饰器

from odoo.exceptions import ValidationError

class Book(models.Model):
...
    @api.constrains('isbn') # 约束的字段
    def _constrain_isbn_valid(self):
        for book in self:
            if book.isbn and not book._check_isbn():
                # 抛出异常时,数据自动回滚
                raise ValidationError('%s is an invalid ISBN' % book.isbn)

搜索和写入计算字段

​ 刚计算的字段可读取,但不可以搜索和写入. 默认情况下是实时计算,而不是存储在数据库种的.

# 可以通过特殊方法来开启搜索和写入操作
# 计算字段可与compute方法一起设置,实现搜索逻辑的search方法,以及实现写入逻辑的inverse方法

class Book(models.Model):
...
    publisher_country_id = fields.Many2one(
        'res.country', string='Publisher Country',
        compute='_compute_publisher_country',
        # store = False, # 默认不在数据库中存储
        inverse='_inverse_publisher_country',
        search='_search_publisher_country',
    )

​ 计算字段的写入是计算的反向逻辑. 因此处理写入的方法成为inverse

# 计算将book.publisher_id.country_id 的值复制给book.publisher_country_id,反向操作是将写入book.publisher_country_id的值拷贝给book.publisher_id.country_id field字段

def _inverse_publisher_country(self):
        for book in self:
            book.publisher_id.country_id = book.publisher_country_id
            
            
# 注意 
	# 1.由于要修改出版商partner记录数据,因此也会修改相同出版商图书的字段.
    # 2.仅对partner模型有写入权限的当前用户才能成功执行此操作

​ 为计算字段开启搜索操作,需要实现search方法. 要将计算字段的搜索转换为使用常规存储字段的搜索域.

def _search_publisher_country(self, opearator, value):
        return [('publisher_id.country_id', operator, value)]

存储计算字段

​ 字段参数添加 store = True

​ 将计算字段值保存到数据库中,在任意依赖变更时值就会重新计算.可以像普通字段一样被搜索,不需要使用search方法了

关联字段

​ 依赖 related参数

publisher_country_id = fields.Many2one(
        'res.country', string='Publisher Country',
        related='publisher_id.country_id',
    )

六.base模型

​ odoo内置了base插件模块,包含

​ 信息仓库(Information Repository) ir.*模型

​ 资源(Resources) res.*模型

# 信息仓库用于存储 odoo 所需数据, 如:菜单,视图,模型,aciton等
    ir.actions.act_window用于窗口操作
    ir.ui.menu用于菜单项
    ir.ui.view用于视图
    ir.model用于模型
    ir.model.fields用于模型字段
    ir.model.data用于XML ID
# 资源包含基本数据,基本上用于应用
    res.partner用于业务伙伴,如客户、供应商和地址等等
    res.company用于公司数据
    res.currency用于货币
    res.country用于国家
    res.users用于应用用户
    res.groups用于应用安全组
posted @ 2020-05-26 13:38  染指未来  阅读(516)  评论(0编辑  收藏  举报