Chapter 3 Inheritance – Extending Existing Applications


One of Odoo’s most powerful features is the ability to add features without directly modifying the underlying objects.
Odoo最强大的一个特性便是能够不用直接修改底层对象就可以添加功能。

This is achieved through inheritance mechanisms, functioning as modification layers on top of existing objects. These modifications can happen at all levels: models, views, and business logic. Instead of directly modifying an existing module, we create a new module to add the intended modifications.

使用继承机制,在已存在对象的上层运行修改,就可以实现这个目的。这些修改在所有级别都都会发生:模型,视图,以及业务逻辑。与直接修改已存在模块相反,我们应该创建一个新的模块,以便添加所期望的修改。

Here, you will learn how to write your own extension modules, empowering you to leverage existing core or community applications. As a relevant example, you will learn how to add Odoo’s social and messaging features to your own modules.

这里,你可以学到如何编写自己的扩展模块,使你能够改变已经存在的内核或者社区应用。举个例子来说,你可以学会如何将Odoo的社交和消息功能添加到自己的模块。

Adding sharing capability to the To-Do app 对To-Do应用添加共享

Our To-Do application now allows users to privately manage their own to-do
tasks. Won’t it be great to take the app to another level by adding collaboration and social networking features to it? We will be able to share tasks and discuss them with other people.

我们的To-Do应用现状可以让用户独立地管理自己的to-do任务。通过添加协作和社交网络功能就可以将应用提升到另外一个层次岂不是更好?这样,我们能够和其它人分享和讨论任务。

We will do this with a new module to extend the previously created To-Do app to add these new features. Here is what we expect to achieve by the end of this chapter:

我们将通过创建一个新的模块来扩张值钱创建的To-Do应用已添加这些新功能。下图是我们希望在本章结束时可以实现的目标:

img:omit

Road map for the user sharing features 用户分享的路线图

Here is our work plan for the feature extensions to be implemented:

这里是我们将要实现的功能扩展的工作计划:

  • Add fields to the Task model, such as the user who owns the task
  • Modify the business logic to operate only on the current user’s tasks, instead of all tasks the user is able to see
  • Add the necessary fields to the views
  • Add social networking features: the message wall and the followers

  • 添加字段到Task模型,比如任务的创建人。

  • 仅对当前用户修改业务逻辑已操作,而不是修改用户可以看到的所有用户。
  • 对视图添加必须要的视图。
  • 添加社交网络功能:消息墙和粉丝。

We will start creating the basic skeleton for the module alongside the todo_app module. Following the installation example in Chapter 1, Getting Started with Odoo Development we are hosting our modules at ~/odoo-dev/custom-addons/:

我们从在todo_app模块旁边为模块创建基本的框架开始。下面是一个第一章——Odoo开发入门钟的的安装示例,我们将这个模块放到~/odoo-dev/custom-addons/

  1. $ mkdir ~/odoo-dev/custom-addons/todo_user
  2. $ touch ~/odoo-dev/custom-addons/todo_user/__init__.py

Now create the __openerp__.py, containing this code:

现在,创建一个包含以下代码的文件__openerp__.py

  1. { 'name': 'Multiuser To-Do',
  2. 'description': 'Extend the To-Do app to multiuser.',
  3. 'author': 'Daniel Reis',
  4. 'depends': ['todo_app'], }

We haven’t done that, but including the summary and category keys can be important when publishing modules to the Odoo online app store.

我们的工作还没有完成,而且对summary和category键的包括在发布模块到Odoo在线应用商店时特别重要。

Next, we can install it. It should be enough to update the Modules List from the Settings menu, find the new module in the Local Modules list and click on its Install button. For more detailed instructions on discovering and installing a module you can refer back to Chapter 1, Getting Started with Odoo Development.

接下来,我们可以安装这个模块。现在是时候从设置菜单更新模块列表了,找到本地模块中的新模块,然后点击它的安装按钮。关于发现和安装一个模块更多详细说明你可以回溯到第一章,Odoo开发入门

Now, let’s start adding the new features to it.

现在,我们开始对它添加新的功能。

Extending the to-do task model 扩展to-do task模型

New models are defined through Python classes. Extending them is also done through Python classes, but using an Odoo specific mechanism.

新模块是通过Python类来定义的。所以扩展模块也可以通过编写Python类来实现,而不是只使用Odoo的专用机制。

To extend an existing model we use a Python class with a _inherit attribute. This identifies the model to be extended. The new class inherits all the features of the parent Odoo model, and we only need to declare the modifications that we wish to introduce.

我们使用一个拥有_inherit属性的Python类来扩展已有模型。这个属性表明模型可以被扩展。新类继承了父级Odoo模型全部的特性,我们只需要声明希望应用的修改。

In fact, Odoo models exist outside our particular module, in a central registry. This registry can also be referred to as the pool, and can be accessed from model methods using self.env[<model name>]. For example, to reference the res.partner model we would write self.env['res.partner'].

实际上,Odoo模块存在于特殊模块之外,在一个注册中心里面。这个注册中心可以被引用为pool,也可以使用self.env[<model name>]从模块的方法来访问。例如,要引用res.partner模型,我们可以这样写self.env['res.partner']

To modify an Odoo model we get a reference to its registry class and then perform in place changes on it. This means that these modifications will also be available everywhere else where the model is used.

如果修改一个引用自模型注册中心的Odoo模型,那么可以模型执行原地修改。这就意味着,这些修改也可以运用在其它的使用到模型的地方。

In the module loading sequence, during a server start, modifiations will only be visible to the modules loaded afterward. So, the loading sequence is important and we should make sure that the module dependencies are correctly set.

按照模块的载入顺序,当服务器启动时,修改仅在模块被载入后可以看到。所以,载入顺序很很重要,我们应该保证模块依赖被正确地设置。

Adding fields to a model 对模型添加字段

We will extend the todo.task model to add a couple of fields to it: the user responsible for the task, and a deadline date.

我们扩展todo.task模型以添加几个字段:任务的负责人以及截止日期。

Create a new todo_task.py file declaring a class extending the original model:

创建一个新的todo_task.py 文件以声明扩展原始模型的类:

  1. # -*- coding: utf-8 -*-
  2. from openerp import models, fields, api
  3. class TodoTask(models.Model):
  4. _inherit = 'todo.task'
  5. user_id = fields.Many2one('res.users', 'Responsible')
  6. date_deadline = fields.Date('Deadline')

The class name is local to this Python file, and in general is irrelevant for other modules. The _inherit class attribute is the key here: it tells Odoo that this class is inheriting from the todo.task model. Notice the _name attribute absent. It is not needed because it is already inherited from the parent model.

类名称在这个Python文件是个本地变量,通常与其它模块没有关联。类属性_inherit的重点是:它告诉Odoo这个类是从todo.task模型继承而来的。注意_name属性的缺失。我们并不需要_name属性,因为这个属性已经从父模型继承了。

The next two lines are regular field declarations. The user_id represents a user from the Users model, res.users. It’s a Many2one field, the equivalent to a foreign key in database jargon. The date_deadline is a simple date field. In Chapter 5, Models Structuring the Application Data we will be explaining in more detail the types of fields available in Odoo.

接下来的两行识常规字段的声明。user_id表示用户来自用户模型res.usersres.users是一个多对一字段,在数据库术语中它等同于外键。date_deadline 是一个简单的日期字段。在第五章——模型——结构化应用数据,我们会详细解释Odoo中可用的字段类型。

We still need to add to the __init__.py file the import statement to include it in the module:

我们需要在模块中对__init__.py文件添加import语句以导入模块。

  1. from . import todo_task

To have the new fields added to the model’s supporting database table we need to perform a module upgrade. If everything goes as expected, you should see the new fields when inspecting the todo.task model, in the Technical menu, Database Structure | Models option.

为了将新字段添加到模型支持的数据库表中,我们需要执行模块升级。如果没有问题话,你可以在检查Technical菜单的Database Structure| Models选项的todo.task模型时看到新字段。

Modifying existing fields 修改已存在的字段

As you can see, adding new fields to an existing model is quite straightforward. Since Odoo 8, modifying attributes on already existing fields is also possible. It’s done by adding a field with the same name, and setting values only for the attributes to be changed.

如你所见,对现有模型添加新字段相当简单直接。从Odoo 8开始,修改已存在的字段的属性也是可行的。通过添加一个同名字段,然后只对需要改变的属性设置值。

For example, to add a help tooltip to the name field, we could add this line to the todo_task.py described above:

例如,对name字段添加一个工具提示,我们对上面提到的todo_task.py添加下面这一行内容:

  1. name = fields.Char(help="What needs to be done?")

If we upgrade the module, go to a to-do task form, and pause the mouse pointer over the Description field, the above tooltip text will be displayed.

如果需要我们升级模块,那么可以找到to-do task表单,然后把鼠标指针悬停在Description字段,上面的工具提示内容就会显示出来。

Modifying model’s methods 修改模型的方法

Inheritance also works at the business logic level. Adding new methods is simple: just declare their functions inside the inheriting class.

继承也可以运用在业务逻辑层面。添加新方法很简单:在继承类的内部声明类的函数就好了。

To extend existing logic, the corresponding method can be overridden by declaring a method with the exact same name, and the new method will replace the previous one. But it can extend the code of the inherited class, by using Python’s super() keyword to call the parent method.

要扩展现存的逻辑,你可以通过声明一个完全相同的名字来重写对应的方法,这样新方法就会替换之前的那个方法。不过,使用Python的super()关键字去调用父级方法就可以扩展类的代码。

It’s best to avoid changing the method’s function signature (that is, keep the same arguments) to be sure that the existing calls on it will keep working properly. In case you need to add additional parameters, make them optional (with a default value) keyword arguments.

最好避免改变方法函数的签名(也就是,使用相同的参数)以保证已经调用这个函数能够正常使用。这种情况,你需要添加额外的参数,对函数应用可选的(携带默认值)关键字参数。

The original Clear All Done action is not appropriate for our task-sharing module anymore, since it clears all tasks regardless of their user. We need to modify it so that it clears only the current user tasks.

原始的Clear All Done动作对于task-sharing模块来说已经不在适用,因为这个动作无视模块的用户就去清除所有的任务。我们需要修改它,让它只能够修改当前用户的任务。

For this, we will override the original method with a new version that first finds the list of completed tasks for the current user, and then inactivates them:

为此,我们用一个新方法来重写原始的方法,该新方法首先找到当前用户的已完成任务列表,然后激活这些列表:

  1. @api.multi
  2. def do_clear_done(self):
  3. domain = [('is_done', '=', True), '|', ('user_id', '=', self.env.uid), ('user_id', '=', False)]
  4. done_recs = self.search(domain)
  5. done_recs.write({'active': False})
  6. return True

We first list the done records to act upon using the search method with a filter expression. The filter expression follows an Odoo speci c syntax referred to as a domain.

我们首先列出 done 记录,以作用于上面的使用过滤器表达式的search方法。过滤器表达式依照被引用为域的Odoo专用语法。

The filter domain used is defined the first instruction: it is a list of conditions, where each condition is a tuple.
These conditions are implicitly joined with an AND operator (‘&’ in domain syntax). To add an OR operation a pipe (‘|’) is used in place of a tuple, and it will affect the next two conditions. We will go into more details about domains in Chapter 6, Views - Designing the User Interface.

过滤器的域用来定义第一条指令:这是一个条件列表,其中每个条件都是一个元组。这些条件都隐式地使用了AND运算符(’&’)连接。为了添加OR运算符,管道(‘|’)就被放倒了元组中使用,它会影响到接下来的两个条件。我们会在第六章-视图——设计用户界面中了解关于域的更多细节。

The domain used here filters all done tasks ('is_done', '=', True) that either have the current user as responsible ('user_id', '=', self.env.uid) or don’t have a current user set ('user_id', '=', False).

这里用域来过滤所有的已完成任务('is_done', '=', True),其中这些已完成任务可以是当前用户作为负责人('user_id', '=', self.env.uid),也可以是非当前用户。

What we just did was to completely overwrite the parent method, replacing it with a new implementation.

我们刚刚完全地重写了父方法,即,使用新的实现替换了它。

But this is not what we usually want to do. Instead we should extend the existing logic to add some additional operations to it. Otherwise we could break already existing features. Existing logic is inserted in an overriding method using Python’s super() command, to call the parent’s version of the method.

但是这并不是我们通常想要做的。相反我们应该扩展已存在的逻辑以添加一些额外的操作。否则,我们会破坏已存在的功能。使用Python的super()命令可以把现存的逻辑插入到方法中,以调用父版本的方法。

Let’s see an example of this: we could write a better version of do_toggle_done() that only performs its action on the Tasks assigned to our user:

然我们来看一看这个例子:我们可以编写一个更好版本的do_toggle_done(),这个方法只对分配到用户任务执行动作:

  1. @api.one
  2. def do_toggle_done(self):
  3. if self.user_id != self.env.user:
  4. raise Exception('Only the responsible can do this!')
  5. else:
  6. return super(TodoTask, self).do_toggle_done()

These are the basic techniques for overriding and extending business logic defined in model classes. Next we will see how to extend the user interface views.

这些都是重写或者扩展定义在模型类中业务逻辑的基本技术。接下来,我们可以看到如何扩展用户界面视图。

Extending views 扩展视图

Forms, lists, and search views are defined using the arch XML structures. To extend views we need a way to modify this XML. This means locating XML elements and then introducing modifications on those points.

表单、列表、和搜索视图都是用XML的arch结构定义的。要扩展视图我们需要一种能够修改这个XML文件的方法。这意味定位XML元素,然后对这些点运用修改。

Inherited views allow just that. An inherited view looks like this:

继承的视图就可以实现这个目的。继承的视图的样子大概如此:

  1. <record id="view_form_todo_task_inherited" model="ir.ui.view">
  2. <field name="name">Todo Task form – User extension</field>
  3. <field name="model">todo.task</field>
  4. <field name="inherit_id" ref="todo_app.view_form_todo_task"/>
  5. <field name="arch" type="xml">
  6. <!-- ...match and extend elements here! ... -->
  7. </field>
  8. </record>

The inherit_id field identifies the view to be extended, by referring to its external identifier using the special ref attribute. External identifiers will be discussed in more detail in Chapter 4, Data Serialization and Module Data.
使用特殊ref属性来引用自身的外部标识符,视图的标识符字段`inherit_id就能够被扩展。外部标识符会在第四章——数据序列化和模块数据中详细谈论。

The natural way to locate elements in XML is to use XPath expressions. For example, taking the form view defined in the previous chapter, the XPath expression to locate the <field name="is_done"> element is: //field[@name]='is_done'. This expression finds a field element with a name attribute equal to is_done. You can find more information on XPath at: https://docs.python.org/2/library/xml. etree.elementtree.html#xpath-support.

定位在XML中元素的原生方法是使用XPath表示。例如,拿前面一章中定义的表单视图来说,其用来定位<field name="is_done">元素的XPath表达式为 //field[@name]='is_done'。 该表达式查找一个拥有name属性等于is_donefield元素。你可以在这里找到更多关于XPath的信息:https://docs.python.org/2/library/xml. etree.elementtree.html#xpath-support.

Having name attributes on elements is important because it makes it a lot easier to select them for extension points. Once the extension point is located, it can be modified or have XML elements added near it.

元素拥有name属性非常重要,因为这可以让扩展点在选择这些元素时更容易操作。只要扩展点被定位,元素就可以被修改,或者是在元素周围添加XML元素。

As a practical example, to add the date_deadline field before the is_done field, we would write in the arch:

例如,在is_done字段之前添加date_deadline字段,我们在arch中写入内容:

  1. <xpath expr="//field[@name]='is_done'" position="before">
  2. <field name="date_deadline" />
  3. </xpath>

Fortunately Odoo provides shortcut notation for this, so most of the time we can avoid the XPath syntax entirely. Instead of the xpath element above we can use the element type we want to locate and its distinctive attributes. The above could also be written as:

幸运地是Odoo对此提供了快捷标记,所以很多时候我们能够避免编写完整的XPath语法。与在上面使用的xpath元素相反,我们使用希望被定位的元素类型,以及该元素的不同属性。上面的内容也可以被写成:

  1. <field name="is_done" position="before">
  2. <field name="date_deadline" />
  3. </field>

Adding new fields next to existing fields is done often, so the <field> tag is frequently used as the locator. But any other tag can be used: <sheet>, <group>, <div>, and so on. The name attribute is usually the best choice for matching elements, but sometimes, we may need to use string (the displayed label text) or the CSS class element.

在现有字段旁边添加新字段经常执行的操作,所以<field>标签常常被用做定位符。但是其它标签也可以拿来用:<sheet>, <group>, <div>,等等。name属性通常是用来匹配元素的最佳选择,但是有时候,我们也需要使用string(显示在页面的标签文本)或者CSSclass元素。

The position attribute used with the locator element is optional, and can have the following values:

position元素和定位符的联合使用是可选的,并可以应用下面的值:

  • after: This is added to the parent element, after the matched node.
  • before: This is added to the parent element, before the matched node.
  • inside (the default value): This is appended to the content of the matched node.
  • replace: This replaces the matched node. If used with empty content, it deletes an element.
  • attributes: This modifies the XML attributes of the matched element (there are more details described following this list).

  • after: 添加到匹配节点之后到父元素。

  • before: 添加到匹配节点之前到父元素。
  • inside(该定位符为默认值):被追加到匹配节点的内容。
  • replace: 提花匹配的节点。如果使用了空白内容,它会将元素删除。
  • attributes: 修改匹配元素的XML属性(更多详情在下面的列表有描述)

The attribute position allows us to modify the matched element’s attributes. This is done using <attribute name="attr-name"> elements with the new attribute values.

attribute 位置允许我们修改匹配元素的属性。使用拥有新属性值的<attribute name="attr-name">元素可以实现这种操作。

In the Task form, we have the Active field, but having it visible is not that useful. Maybe, we can hide it from the user. This can be done setting its invisible attribute:

在Task表单中,我们设置了“Active”字段之,但是让这字段不可见不是太好用。或许,我们让它对用户进行隐藏。你可以通过对字段设置invisible属性来实现这个目的:

  1. <field name="active" position="attributes">
  2. <attribute name="invisible">1<attribute/>
  3. </field>

Setting the invisible attribute to hide an element is a good alternative to using the replace locator to remove nodes. Removing should be avoided, since it can break extension models that may depend on the deleted node.

设置不可见属性来隐藏一个元素是除了使用替换定位符来移除节点之外的另一个好选择。你应该避免执行移除操作,因为这样做会破坏扩展模型所依赖的可能被删除掉的节点。

Finally, we can put all of this together, add the new fields, and get the following complete inheritance view to extend the to-do tasks form:

最后,我们可以将这些内容放到一起,添加新字段,然后以下面完整的继承视图来扩展to-do tasks表单:

  1. <record id="view_form_todo_task_inherited" model="ir.ui.view">
  2. <field name="name">Todo Task form – User extension</field>
  3. <field name="model">todo.task</field>
  4. <field name="inherit_id" ref="todo_app.view_form_todo_task"/>
  5. <field name="arch" type="xml">
  6. <field name="name" position="after">
  7. <field name="user_id" />
  8. </field>
  9. <field name="is_done" position="before">
  10. <field name="date_deadline" />
  11. </field>
  12. <field name="name" position="attributes">
  13. <attribute name="string">I have to...<attribute/>
  14. </field>
  15. </field>
  16. </record>

This should be added to a todo_view.xml file in our module, inside the <openerp> and <data> tags, as shown in the previous chapter.

就像前一章所示,这些内容应该被添加到模块中的todo_view.xml文件,<openerp><data>标签的内部。

Note

Inherited views can also be inherited, but since this creates more intricate dependencies, it should be avoided.

注释

继承到视图还可以被继承,但是这样做会产生更加错综复杂的依赖关系,所以你应该避免这样做。

Also, we should not forget to add the data attribute to the __openerp__.py descriptor file:

而且,我们不应该忘记将数据属性添加到描述符文件__openerp__.py

  1. 'data': ['todo_view.xml'],

Extending tree and search views 扩展树形视图和搜索视图

Tree and search view extensions are also defined using the arch XML structure, and they can be extended in the same way as form views. We will follow example of a extending the list and search views.

树形视图和搜索视图扩展也可以使用arch XML结构来定义,

For the list view, we want to add the user field to it:

对于列表视图,我们希望对它添加user字段:

  1. <record id="view_tree_todo_task_inherited" model="ir.ui.view">
  2. <field name="name">Todo Task tree – User extension</field>
  3. <field name="model">todo.task</field>
  4. <field name="inherit_id" ref="todo_app.view_tree_todo_task"/>
  5. <field name="arch" type="xml">
  6. <field name="name" position="after">
  7. <field name="user_id" />
  8. </field>
  9. </field
  10. </record>

For the search view, we will add search by user, and predefined filters for the user’s own tasks and tasks not assigned to anyone:

对于搜索视图,我们让用户来添加搜索,并给用户的所拥有的任务以及还没有分配给其它用户的用户预定义过滤器:

  1. <record id="view_filter_todo_task_inherited" model="ir.ui.view">
  2. <field name="name">Todo Task tree – User extension</field>
  3. <field name="model">todo.task</field>
  4. <field name="inherit_id" ref="todo_app.view_filter_todo_task"/>
  5. <field name="arch" type="xml">
  6. <field name="name" position="after">
  7. <field name="user_id" />
  8. <filter name="filter_my_tasks" string="My Tasks"
  9. domain="[('user_id','in',[uid,False])]" />
  10. <filter name="filter_not_assigned" string="Not Assigned"
  11. domain="[('user_id','=',False)]" />
  12. </field>
  13. </field>
  14. </record>

Don’t worry too much about the views-specific syntax. We’ll cover that in more detail in Chapter 6, Views - Designing the User Interface.

不要太过担心视图专有语法。我们将在第六章-视图-设计用户界面中详细说明。

More on using inheritance to extend models 使用继承扩展模型的更多话题

We have seen the basic in place extension of models, which is also the most frequent use of inheritance. But inheritance using the _inherit attribute has more powerful capabilities, such as mixin classes.

我们已经看过了基本的原地模型扩展,这也是最常使用的继承方式。不过使用了_inherit属性的继承更加具有强大的兼容性,比如mixin类。

We also have available the delegation inheritance method, using the _inherits attribute. It allows for a model to contain other models in a transparent way for the observer, while behind the scenes each model is handling its own data.

我们也可以使用_inherits来委托继承方法。这样能够让一个模型对于观察者来说透明地包含了其他模型,而后台中每个模型都可以处理自己的数据。

Let’s explore these possibilities in more detail.

我们来详细地浏览这些可能性。

Copying features using prototype inheritance 使用原型继承来复制功能

The method we used before to extend a model used just the _inherit attribute. We defined a class inheriting the todo.task model, and added some features to it. The class _name was not explicitly set; implicitly it was todo.task also.

我们之前使用的方法只是使用了 _inherit属性来扩展一个模型。我们定义了一个继承todo.task模型的类,并对类添加了一个功能。类的_name并没有明确设置,todo.task也是同样。

But using the _name attribute allows us to create mixin classes, by setting it to the model we want to extend. Here is an example:

通过将_name属性设置为我们希望扩展的模型,它就允许我们创建mixin类。下面是例子:

  1. from openerp import models
  2. class TodoTask(models.Model):
  3. _name = 'todo.task'
  4. _inherit = 'mail.thread'

This extends the todo.task model by copying to it the features of the mail.thread model. The mail.thread model implements the Odoo messages and followers features, and is reusable, so that it’s easy to add those features to any model.

本次对todo.task模型的扩展是通过将这个模型复制mail.thread模型的功能而实现的。mail.thread模型实现Odoo消息和关注人功能,而且是可以被重复使用的,因此,我们可以很轻松地将这些功能添加任何一个模型。

Copying means that the inherited methods and fields will also be available in the inheriting model. For fields this means that they will be also created and stored in the target model’s database tables. The data records of the original (inherited) and the new (inheriting) models are kept unrelated. Only the definitions are shared.

复制意味着继承的方法和字段可以在继承模型中使用。对于字段来说,这就意味着它们可以被创建并存春刀目标模型的数据库表中。陨石的数据集(被继承的)和新(在继承的)模型是存在关联的。只有定义被共享。

These mixins are mostly used with abstract models, such as the mail.thread used in the example. Abstract models are just like regular models except that no database representation is created for them. They act like templates, describing fields and logic to be reused in regular models. The fields they define will only be created on those regular models inheriting from them. In a moment we will discuss in detail how to use this to add mail.thread and its social networking features to our module. In practice when using mixins we rarely inherit from regular models, because this causes duplication of the same data structures.

这些mixin大多数时间是和抽象模型一起使用的,比如例子中的mail.thread。除了没有为抽象模型创建数据库表现之外,抽象模型就和普通的模型一样。它们的行为像是模板,描述了在普通模型可以重复使用的字段和逻辑。抽象模型定义的字段只会在继承自己的普通模型上创建。过一会儿,我们会讨论详细如何使用抽象来对自定义的模块添加mail.thread和社交网络功能。实际上,当使用mixin时,我们很少从普通模型继承,因为这样做会造成相同数据结构的重复。

Odoo provides the delegation inheritance mechanism, which avoids data structure duplication, so it is usually preferred when inheriting from regular models. Let’s look at it in more detail.

Odoo提供了委托继承机制,它可以避免数据结构的重复,所以从普通模型执行继承时通常偏向于选择此种机制。我们来看一看委托继承的更多详情。

Embedding models using delegation inheritance 使用委托继承来嵌入模型

Delegation inheritance is the less frequently used model extension method, but it can provide very convenient solutions. It is used through the _inherits attribute (note the additional -s) with a dictionary mapping inherited models with fields linking to them.

委托继承是很少用到的的模型扩展方式,但是它可以提供非常便捷的解决办法。委托继承通过使用_inherits

A good example of this is the standard Users model, res.users, that has a Partner model embedded in it:

这种情况的一个好例子就是标准的拥有内嵌Partner模型的User模型res.users:

  1. from openerp import models, fields
  2. class User(models.Model):
  3. _name = 'res.users'
  4. _inherits = {'res.partner': 'partner_id'}
  5. partner_id = fields.Many2one('res.partner')

With delegation inheritance the model res.users embeds the inherited model res.partner, so that when a new User is created, a partner is also created and a reference to it is kept in the partner_id field of the User. It is similar to the polymorphism concept in object oriented programming.

模型res.users使用委托继承,嵌入了继承模型res.partner,所以,当User创建时,合作伙伴也被创建,对合作伙伴的引用也被保存在Userpartner_id字段中。这类似于面向对象编程中的多态概念。

All fields of the inherited model, Partner, are available as if they were User fields, through the delegation mechanism. For example, the partner name and address fields are exposed as User fields, but in fact they are being stored in the linked Partner model, and no data structure duplication occurs.

在使用委托机制的情况下,如果继承模型——Partner出现在User字段中,那么Partner模型的所有字段都可以被使用的。例如,合作伙伴的名字和地址字段暴露在了User的字段,但是,实际情况是这些字段存储在链接的Partner模型中,而且也不会有数据结构重复的事情发生。

The advantage of this, compared to prototype inheritance, is that there is no need
to repeat data structures in many tables, such as addresses. Any new model that needs to include an address can delegate that to an embedded Partner model. And if modifications are introduced in the partner address fields or validations, these are immediately available to all the models embedding it!

继承的优势在于,相较于原型继承,委托继承不需要在很多表中重复数据结构,比如地址。任何一个需要使用地址的新模型可以把自己委托到一个内嵌的Partner模型中。如果在partner的地址字段上应用修改或者是验证,这些操作在所有正内嵌Partner模型的模型中都是立即可用的。

Note

Note that with delegation inheritance, fields are inherited, but methods are not.

注释

注意,使用委托继承时,只有字段被继承,方法却不能够被继承。

Using inheritance to add social network features 运用继承添加社交网络功能

The social network module (technical name mail) provides the message board found at the bottom of many forms, also called Open Chatter, the followers are featured along with the logic regarding messages and notifications. This is something we will often want to add to our models, so let’s learn how to do it.

社交网络模块(技术名为mail)在很多表单的底部提供了消息面板,所以这个模块也称作Open Chatter,关注人关心的是消息和通知。这也是我们经常对自己模型多做的事情,那么我们来学习一下它是如何实现的。

The social network messaging features are provided by the mail.thread model of the mail module. To add it to a custom model we need to:

社交网络消息的功能是由mail模块中的mail.thread模型提供的。要把mail.thread添加到自定义模型我们需要:

  • Have the module depend on mail.
  • Have the class inherit from mail.thread.
  • Have the Followers and Thread widgets added to the form view.
  • Optionally, set up record rules for followers.

  • 模型依赖于mail。

  • 类从mail.thread继承。
  • 对表单视图添加了关注人和Thread部件。
  • 可选择的是,你可以对关注人设置记录规则。

Let’s follow this checklist:

我们来检查一下这张清单:

Regarding #1, since our extension module depends on todo_app, which in turn depends on mail, the dependency on mail is already implicit, so no action is needed.

关于第一条内容,因为我们的扩展模块依赖于todo_app,按照依赖顺序是mail,对mail的依赖也是隐式包含的,所以也不需要动作。

Regarding #2, the inheritance on mail.thread is done using the _inherit attribute we used before. But our to-do task extension class is already using the _inherit attribute. Fortunately it can also accept a list of models to inherit from, so we can use that to make it also include the inheritance on mail.thread:

关于第二条内容,对mail.thread的继承是通过我们之前使用过的_inherit属性来实现的。而且我们的to-do task扩展类已经使用了_inherit属性。幸运的是,它也可以接受需要继承的一个模型列表,所以使用这个方法,可以让我们对mail.thread继承包括在内。

  1. _name = 'todo.task'
  2. _inherit = ['todo.task', 'mail.thread']

The mail.thread model is an abstract model. Abstract models are just like regular models except that they don’t have a database representation; no actual tables are created for them. Abstract models are not meant to be used directly. Instead they are expected to be used in mixin classes, as we just did. We can think of them as templates with ready-to-use features. To create an abstract class we just need it to use models.AbstractModel instead of models.Model.

mail.thread模型是一个抽象模型。抽象模型除了不拥有数据库表现(没有创建实际的表)之外它们就和普通模型一样。抽象模型并不可以直接使用。相反它们被期望用在mixin类中,就我们刚才所作的那样。我们把它们认为是拥有开箱即用特性的模板。创建一个抽象类,我们只要使用models.AbstractModel而不是models.Model就好了。

For #3, we want to add the social network widgets at the bottom of the form. We can reuse the inherited view we already created, view_form_todo_task_inherited, and add this into its arch data:

对于第三条内容,我们想要在表单的底部添加社交网络部件。我们可以重复使用我们之前已经创建的继承了的视图,view_form_todo_task_inherited,然后对这个视图添加arch数据:

  1. <sheet position="after">
  2. <div class="oe_chatter">
  3. <field name="message_follower_ids" widget="mail_followers" />
  4. <field name="message_ids" widget="mail_thread" />
  5. </div>
  6. </sheet>

The two fields we add here haven’t been explicitly declared by us, but they are provided by the mail.thread model.

我们添加到视图中两个字段并没明确声明,因为它们是由mail.thread提供的。

The final step is to set up record rules for followers. This is only needed if our model has record rules implemented that limit other users’ access to its records. In this case, we need to make sure that the followers for each record have at least read access to it.

最后一步为关注者设置记录规则。仅在我们实现了限制其他用户访问自定义模型的记录时才需要用到。在这种情况下,我们需要保证关注者对于每一条记录至少都能够访问记录。

We do have record rules on the to-do task model so we need to address this, and that’s what we will do in the next section.

我们拥有了to-do task模型的记录规则,所以我们需要记录规则定位,这就是我们在下一节要做的事情。

Modifying data 修改数据

Unlike views, regular data records don’t have an XML arch structure and can’t be extended using XPath expressions. But they can still be modified to replace values in their fields.

和视图不同,普通的数据记录并不包含XML的arch结构,而且也不能够使用XPath表达式来括扩展。但是它们仍旧可以被修改以替换它们的值。

The <record id="x" model="y"> element is actually performing an insert or update operation on the model: if x does not exist, it is created; otherwise, it is updated/written over.

<record id="x" model="y"> 元素实际上对模型执行的是一个插入或者更新的操作:如果x不存在,那么就新建;否则,就更新或者重写。

Since records in other modules can be accessed using a <model>.<identifier> identifier, it’s perfectly legal for our module to overwrite something that was written before by another module.

因为其他模块中的记录能够使用标识符<model>.<identifier>来访问,

Note

Note that the dot is reserved to separate the module name from the object identifier, so they shouldn’t be used in identifiers. Instead use the underscore.

注释

注意点号被保留的,以便从对象标识符中分隔模块名称,所以它们不应该用在标识符中。相反你应该使用的是下划线。

As an example, let’s change the menu option created by the todo_app module to into My To Do. For that we could add the following to the todo_user/todo_view.xml file:

如例所示,我们把todo_app模块创建的菜单选项更改为My To Do。为此,我们添加以下内容到文件todo_user/todo_view.xml:

  1. <!-- Modify menu item -->
  2. <record id="todo_app.menu_todo_task" model="ir.ui.menu">
  3. <field name="name">My To-Do</field>
  4. </record>
  5. <!-- Action to open To-Do Task list -->
  6. <record model="ir.actions.act_window"
  7. id="todo_app.action_todo_task">
  8. <field name="context">
  9. {'search_default_filter_my_tasks': True}
  10. </field>
  11. </record>

Extending the record rules 扩展记录规则

The To-Do application included a record rule to ensure that each task would only be visible to the user that created it. But now, with the addition of the social features, we need the task followers to also have access to them. The social network module does not handle this by itself.

To-Do应用包含了记录规则以确保每个任务都只对任务的创建人可见。不过,现在,随着社交功能的添加,我们需要任务的关注人也能够访问到它们。社交网络模块自身并没有处理这种情况。

Also, now tasks can have users assigned to them, so it makes more sense to have the access rules to work on the responsible user instead of the user who created the task.

现在,任务也可以用户分配给它们了,所以把访问规则用在负责人上而不是任务的创建人上更有意义。

The plan would be the same as we did for the menu item: overwrite the todo_app.todo_task_user_rule to modify the domain_force field to a new value.

计划和我们对菜单选项所做的一样:重写todo_app.todo_task_user_rule以修改domain_force 字段为一个新的值。

Unfortunately this won’t work this time. Remember the <data no_update="1"> we used in the security rules XML file: it prevents later write operations on it.

不幸的是,这一次的操作并不会生效。记住,我们在安全规则XML文件中使用的<data no_update="1">:它能够阻止

Since updates on that record are being prevented, we need a workaround. That will be to delete that record and add a replacement for it in our module.

因为,对该条记录的更新被阻止了,所以我们需要作出改变。这会删除记录,然后替换自定义模块只能够的记录。

To keep things organized, we will create a security/todo_access_rules.xml file and add the following content to it:

为了内容清晰有序,我们创建一个文件security/todo_access_rules.xml,并把以下内容添加到文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <openerp>
  3. <data noupdate="1">
  4. <delete model="ir.rule" search="[('id', '=', ref('todo_app.todo_task_user_rule'))]" />
  5. <record id="todo_task_per_user_rule" model="ir.rule">
  6. <field name="name">ToDo Tasks only for owner</field>
  7. <field name="model_id" ref="model_todo_task"/>
  8. <field name="groups" eval="[(4, ref('base.group_user'))]"/>
  9. <field name="domain_force">
  10. ['|',('user_id','in', [user.id,False]), ('message_follower_ids','in',[user.partner_id.id])]
  11. </field>
  12. </record>
  13. </data>
  14. </openerp>

This finds and deletes the todo_task_user_rule record rule from the todo_app module, and then creates a new todo_task_per_user_rule record rule. The domain filter we will now use makes a task visible to the responsible user user_id, to everyone if the responsible user is not set (equals False), and to all followers.
The rule will run in a context where user is available and represents the current session user. The followers are partners, not User objects, so instead of user.id, we need to use user.partner_id.id.

这会从todo_app模块中发现并删除todo_task_user_rule记录规则,然后创建一条新的规则todo_task_per_user_rule。我们现在要使用的域过滤器将产生一个负责人的可见的任务,这条规则会在当前用户以及可用用户的上下文中运行。关注者是合作伙伴而不是User对象,所以我们需要使用user.partner_id.id,而不是user.id

Tips

Working on data files with <data noupdate="1"> is tricky because any later edit won’t be updated on Odoo. To avoid that, temporarily use <data noupdate="0"> during development, and change it back only when you’re done with the module.

提示

对数据文件使用<data noupdate="1">是一种技巧,因为在Odoo上的任何编辑都不再被更新。为了避免此种情况,在开发时临时使用<data noupdate="0">,仅在完成模块时才改回来。

As usual, we must not forget to add the new file to the __openerp__.py descriptor file in the data attribute:

和往常一样,我们一定不能忘记把新文件添加到__openerp__.py描述文件中数据属性中:

  1. 'data': ['todo_view.xml', 'security/todo_access_rules.xml'],

Notice that on module upgrade, the <delete> element will produce an ugly warning message, because the record to delete does not exist anymore. It is not an error and the upgrade will be successful, so we don’t need to worry about it.

注意在模型升级时,<delete>元素会产生一个很难看的警告信息,因为需要删除的记录根本就不存在了。这并不是一个错误,而且升级也会成功的,所以我们不需要担心这件事。

Summary

You should now be able to create new modules to extend existing modules. We saw how to extend the To-Do module created in the previous chapter.

现在,你应该能够创建新模块来括扩展现存的模块。我们在前一章也学习了对需要扩展的To-Do模块的创建。

New features were added onto the several layers that make up an application.
We extended the Odoo model to add new fields, and extended the methods with its business logic. Next, we modi ed the views to make the new elds available on them. Finally, you learned how to extend a model by inheriting from other models, and we used that to add the social network features to our To-Do app.

新特性被添加到了装饰器一个应用的多个层面。我们扩展Odoo模块以添加新字段,并使用它的业务逻辑来扩展方法。接下来,我们修改视图,以便可以在视图上使用新的字段。最后,你学会如何通过继承其它模型来扩展一个模型,同时我们使用这个方法对To-Do应用添加社交网络功能。

With these three chapters, we had an overview of the common activities involved in Odoo development, from Odoo installation and setup to module creation and extension. The next chapters will focus on specific areas, most of which we visited briefly in these overviews. In the following chapter, we will address data serialization and the usage of XML and CSV les in more detail.

通过这三章,从Odoo的安装和设置,到模块创建和扩展,我们对常见的Odoo开发活动就有了一个大概了解。接下的章节我们关注于特殊的领域,