因为本章有包含不少基础知识,我的不会所有转化为本身的语言。直接机器翻译了(用斜体标注,机器翻译反而一字不落,我会过滤掉冗余的内容),虽然机翻,但会保证意思不会偏。html
本章主要章节以下:python
模型具备定义其行为的结构属性。它们的前缀是
下划线。模型最重要的属性是_name,由于它定义了内部全局标识符。在内部,Odoo使用_name属性来建立一个数据库表。例如,若是您提供library.book,则Odoo ORM将在数据库中建立library_book表。这就是为何在Odoo中,_name属性必须是惟一的。git
定义模型文件models/library_book.pysql
_description = 'Library Book'
_order = 'date_release desc, name'
_rec_name = 'short_name' short_name = fields.Char('Short Title', required=True)
<field name="short_name"/>
完整的library_book.py文件以下:数据库
from odoo import models, fields class LibraryBook(models.Model): _name = 'library.book' _description = 'Library Book' _order = 'date_release desc, name' _rec_name = 'short_name' name = fields.Char('Title', required=True) short_name = fields.Char('Short Title', required=True) date_release = fields.Date('Release Date') author_ids = fields.Many2many('res.partner', string='Authors')
完整的form视图内容以下:编程
<form> <group> <group> <field name="name"/> tags"/> <field name="author_ids" widget="many2many_ </group> <group> <field name="short_name"/> <field name="date_release"/> </group> </group> </form>
经过UI更新模块
api
或者经过命令行升级安全
python3 odoo-bin -u my_library
第一步是为模型的定义添加一个更加用户友好的标题。这不是强制性的,但能够由某些附加组件使用。例如,在建立新记录时,邮件附加模块中的跟踪功能将其用于通知文本。有关更多详细信息,请参阅第23章,在Odoo中管理电子邮件。若是您不使用模型的描述,在这种状况下,Odoo将在日志中显示警告。
默认状况下,Odoo使用内部id值(自动生成的主键)对记录进行排序。可是,这能够更改,这样咱们就可使用咱们选择的字段,方法是提供一个带有字符串的_order属性,该字符串包含以逗号分隔的字段名列表。
字段名后面能够跟desc关键字,以便按降序排序。数据结构
小贴士
只有存储在数据库中的field才能进行排序,对于computed的字段是不支持排序的。app
_order是缩减版的SQL ORDER BY,他不支持NULLS FIRST等。
模型记录从其余记录引用时使用表示。例如,值为1的用户标识字段表示管理员用户。在窗体视图中显示时,Odoo将显示用户名,而不是数据库ID。简而言之,_rec_name是Odoo GUI用来表示该记录的记录的显示名称。默认状况下,使用名称字段。实际上,这是_rec_name属性的默认值,这就是为何在咱们的模型中有一个name字段比较方便的缘由。在咱们的例子中图书馆.bookmodel有一个name字段,所以,默认状况下,Odoo将使用它做为显示名。咱们想在步骤3中更改此行为;咱们使用了short_name做为_rec_name。以后,library.book模型的显示名从form视图的名称name改成short_name,Odoo GUI将使用short_name的值来表示记录。
警告
若模型中没有name字段也没有配置_rec_name,那么将展现记录的模型名称及记录ID(library.book, 1)
咱们新增了short_name的字段,实际上是在数据库表中新增了一列,同时咱们须要在视图中展现相应的字段。
自odoo8以后,计算字段magic_display字段被默认添加到模型中。他的值是经过nage_get()的模型方法生成的。
name_get()方法默认是经过_rec_name属性去生成展现的名称的。若是你想本身实现展现的名称,能够重写name_get()函数。函数必须返回包含由记录ID和Unicode字符串组成的元组的列表。
举例:
def name_get(self): result = [] for record in self: rec_name = "%s (%s)" % (record.name, record.date_ release) result.append((record.id, rec_name)) return result
仍是my_library模块为例,models/library_book.py定义了基本的模型。
from odoo import models, fields class LibraryBook(models.Model): # ... short_name = fields.Char('Short Title') notes = fields.Text('Internal Notes') state = fields.Selection( [('draft', 'Not Available'), ('available', 'Available'), ('lost', 'Lost')], 'State') description = fields.Html('Description') cover = fields.Binary('Book Cover') out_of_print = fields.Boolean('Out of Print?') date_release = fields.Date('Release Date') date_updated = fields.Datetime('Last Updated') pages = fields.Integer('Number of Pages') reader_rating = fields.Float( 'Reader Average Rating', digits=(14, 4), # Optional precision decimals, )
<form> <group> <group> <field name="name"/> tags"/> <field name="author_ids" widget="many2many_ <field name="state"/> <field name="pages"/> <field name="notes"/> </group> <group> <field name="short_name"/> <field name="date_release"/> <field name="date_updated"/> avatar"/> <field name="cover" widget="image" class="oe_ <field name="reader_rating"/> </group> </group> <group> <field name="description"/> </group> </form>
下面的代码定义了字段经常使用的属性,能够先有个印象
short_name = fields.Char('Short Title',translate=True, index=True) state = fields.Selection( [('draft', 'Not Available'), ('available', 'Available'), ('lost', 'Lost')], 'State', default="draft") description = fields.Html('Description', sanitize=True, strip_ style=False) pages = fields.Integer('Number of Pages', groups='base.group_user', states={'lost': [('readonly', True)]}, help='Total book page count', company_dependent=False)
重要提醒
Selection类型是可使用数字做为key的,可是0在odoo中是做为未设置当前字段存在的。所以,若是key中使用了0,那么可能存在乎想不到的坑。
对于关系字段,咱们须要事先肯定关系的目标模型(或协同模型)。然而,有时,咱们可能须要把这个决定留给用户,首先选择咱们想要的模型,而后选择咱们想要连接到的记录。这能够经过使用参考字段来实现。
from odoo import models, fields, api class LibraryBook(models.Model): # ... @api.model def _referencable_models(self): models = self.env['ir.model'].search([ ('field_id.name', '=', 'message_ids')]) return [(x.model, x.name) for x in models]
ref_doc_id = fields.Reference( selection='_referencable_models', string='Reference Document')
引用字段与m2o字段类似,他们都容许用户本身选择关联的模型。
目标模型经过selection属性进行选择,selection必须是包含两个元素元组的列表,第一个元素是内部标识,第二个标识是展现的内容。
例如:
[('res.users', 'User'), ('res.partner', 'Partner')]
可是,咱们可使用最多见的模型,而不是提供固定的列表。为了简单起见,咱们使用了全部具备消息传递功能的模型。使用可引用的模型方法,咱们动态地提供了一个模型列表。
咱们的方法是经过提供一个函数来浏览全部能够被引用的模型记录,从而动态地构建一个将提供给selection属性的列表。虽然这两种形式都是容许的,可是咱们在引号中声明了函数名,而不是直接引用不带引号的函数。这是更灵活的,它容许引用的函数只在稍后的代码中定义,例如,这在使用直接引用时是不可能的。
函数运行在模型层面,所以须要用@api.model装饰器。
虽然这个特性看起来不错,但它会带来很大的执行开销。显示大量记录的引用字段(例如,在列表视图中)会形成沉重的数据库负载,由于每一个值都必须在单独的查询中查找。与常规关系字段不一样,它也没法利用数据库引用完整性。
odoo能够实现对归属于其余模块的模型功能进行扩展,而不去动原有的代码。能够添加字段、方法、以及修改以存在的字段、方法。
odoo提供了三种方式的继承
class ResPartner(models.Model): _inherit = 'res.partner' _order = 'name' authored_book_ids = fields.Many2many( 'library.book', string='Authored Books') count_books = fields.Integer( 'Number of Authored Books', compute='_compute_count_books' )
# ... from odoo import api # if not already imported # class ResPartner(models.Model): # ... @api.depends('authored_book_ids') def _compute_count_books(self): for r in self: r.count_books = len(r.authored_book_ids)
咱们经过_inherit属性实现对于已有模块的继承。新增的字段将直接体如今原有模型上。
已有字段也能够进行增量修改。能够对原模型中的函数进行重写或修改(可经过super调用原有模型的函数)。
原型继承,对现有模块完整的复制。
原型继承会用到_name及_inherit的类属性。
from odoo import models, fields, api class LibraryBookCopy(models.Model): _name = "library.book.copy" _inherit = "library.book" _description = "Library Book's Copy"
在使用_name及_inherit的类属性的时候,odoo将使用_name做为类名复制_inherit的模型。
新的模型将体如今数据库中,有单独的数据库表。以上为例,library_book_copy表。
原型继承是copy父类完整的内容,包括字段、属性及方法。若是将要调整这些内容,可直接定义便可。例如, library.book已经有了name_get函数,可是不符合咱们的要求。咱们能够在library.book.copy模型中直接新增一个name_get函数。
警告
若是_name使用了父类的名称,那么原型继承是不生效的,而是普通的继承。
虽然官方提供了原型继承,可是应用场景不多。反而是经过委托继承,能够在不复制整个数据结构的状况下实现咱们想要的功能。
委托继承使用类属性_inherits,注意多了一个s。在某些场景下,相较于修改现有模型,建立一个新的模型并与老的模型进行关联反而是更好的选择。
委托继承与面向对象编程的理念更为贴近。它还支持多态继承,便可以同时从多个模型继承。
好比,咱们有一个图书馆。会有不少的读书人来图书馆读书,这些人在咱们这有一些基本的信息(姓名、电话等),其中又有一些人是图书馆会员。会员与普通用户都有姓名、电话等基础信息,可是又多了办理会员的日期、会员卡号等特有信息。
class LibraryMember(models.Model): _name = 'library.member' _inherits = {'res.partner': 'partner_id'} partner_id = fields.Many2one( 'res.partner', ondelete='cascade')
# class LibraryMember(models.Model): # ... date_start = fields.Date('Member Since') date_end = fields.Date('Termination Date') member_number = fields.Char() date_of_birth = fields.Date('Date of birth')
咱们经过_inherits实现对res.partner对象的委托继承,这是一个key-value的字典。key是继承模型的类名,value是当前模型关联到继承模型的字段。
当咱们对新模型建立记录时,会如今res.partner、library.member中分别建立一条记录,并经过partner_id进行关联。
委托继承只是对字段的继承,并不包含函数。
关于委托继承,有个简写方式。即在m2o中添加delegate=True属性,去掉_inherits的类属性。上面的例子能够写成
class LibraryMember(models.Model): _name = 'library.member' partner_id = fields.Many2one('res.partner', ondelete='cascade', delegate=True) date_start = fields.Date('Member Since')
一个典型的委托继承是用户模型,res.users,继承自res.partner。也就是説咱们在res.users中看到的一些字段实际上是partner中的。
传统继承和原型继承都是能够为模型添加新的特性,可是效率偏低。
有时咱们有一个特性,向同时添加到好几个模型中。抽象模型是能够实现咱们想要的特性,而后被其余几个模型继承。
举个例子,咱们将实现一个简单的归档特性。它将活动字段添加到模型中(若是它还不存在),并提供一个存档方法来切换活动标志。这是由于活动是一个魔法场。若是默认状况下存在于模型中,则active=False的记录将从查询中过滤掉。
归档特性通常会有本身的模块,至少是本身的python文件。此处为了简单,就放在library_book.py文件中。
class BaseArchive(models.AbstractModel): _name = 'base.archive' active = fields.Boolean(default=True) def do_archive(self): for record in self: record.active = not record.active
class LibraryBook(models.Model): _name = 'library.book' _inherit = ['base.archive'] # ...
抽象模型基于models.AbstractModel建立。他与普通的models.Model功能基本相似,只是他并不在数据库中建立相应的数据表。他的存在就是为了让其余模型继承用的。
当一个模型定义了_inherit属性,那么他将继承该收藏模型全部的字段、属性及方法。
注意,此处_inherit的值是列表。
其实,_inhiret有两种形式。列表表明继承自多个模型,单独的字符串是继承自一个模型。
最值得一提的抽象模型是mail.thread,它定义在mail(Discuss)模块中。它为模型添加了讨论的特性。咱们能够在模型form视图下方看到消息。 还有一个模型,models.TransientModel。它跟model.Model相似,只是它存储的数据是暂时的,odoo会有定时任务清理掉。抛掉这个不一样,瞬态模型跟常规模型同样。 瞬态模型在用户交互比较复杂的场景下比较有帮助,好比wizards(向导)。将在第八章详细介绍。