Looking again at our module design, we have these relations:
Let's add the corresponding relation fields to the to-do tasks in our todo_ui/todo_model.py
file:
class TodoTask(models.Model): _inherit = 'todo.task' stage_id = fields.Many2one('todo.task.stage', 'Stage') tag_ids = fields.Many2many('todo.task.tag', string='Tags')
The preceding code shows the basic syntax for these fields, setting the related model and the field's title string. The convention for relational field names is to append _id
or _ids
to the field names, for to one and to many relations, respectively.
As an exercise, you may try to also add on the related models, the corresponding inverse relations:
Many2one
relation is a One2many
field on stages: each stage can have many tasks. We should add this field to the Stage
class.Many2many
relation is also a Many2many
field on tags: each tag can also be used on many tasks.Let's have a closer look at relational field definitions.
Many2one
accepts two positional arguments: the related model (corresponding to the comodel
keyword argument) and the title string
. It creates a field in the database table with a foreign key to the related table.
Some additional named arguments are also available to use with this type of field:
ondelete
defines what happens when the related record is deleted. Its default is set
null
, meaning it is set to an empty value if the related record is deleted. Other possible values are restrict
, raising an error preventing the deletion, and cascade
also deleting this record.context
and domain
are meaningful for the web client views. They can be set on the model to be used by default on any view where the field is used. They will be better explained in the Chapter 6, Views - Designing the User Interface.auto_join=True
allows the ORM to use SQL joins when doing searches using this relation. By default this is False
to be able to enforce security rules. If joins are used, the security rules will be bypassed, and the user could have access to related records the security rules wouldn't allow, but the SQL queries will be more efficient and run faster.The Many2many
minimal form accepts one argument for the related model, and it is recommended to also provide the string
argument with the field title.
At the database level, this does not add any column to the existing tables. Instead, it automatically creates a new relation table with only two ID fields with the foreign keys to the related tables. The relation table name and the field names are automatically generated. The relation table name is the two table names joined with an underscore with _rel
appended to it.
These defaults can be manually overridden. One way to do it is to use the longer form for the field definition:
# TodoTask class: Task <-> Tag relation (long form): tag_ids = fields.Many2many( 'todo.task.tag', # related model 'todo_task_tag_rel', # relation table name 'task_id', # field for "this" record 'tag_id', # field for "other" record string='Tasks')
Note that the additional arguments are optional. We could just set the name for the relation table and let the field names use the automatic defaults.
If you prefer, you may use the long form using keyword arguments instead:
# TodoTask class: Task <-> Tag relation (long form): tag_ids = fields.Many2many( comodel_name='todo.task.tag', # related model relation='todo_task_tag_rel', # relation table name column1='task_id', # field for "this" record column2='tag_id', # field for "other" record string='Tasks')
Like many to one fields, many to many fields also support the domain
and context
keyword attributes.
On some rare occasions we may have to use these long forms to override the automatic defaults, in particular, when the related models have long names or when we need a second many to many relation between the same models.
The inverse of the Many2many
relation is also a Many2many
field. If we also add a Many2many
field to the tags, Odoo infers that this many to many relation is the inverse of the one in the task model.
The inverse relation between tasks and tags can be implemented like this:
# class Tag(models.Model): # _name = 'todo.task.tag' # Tag class relation to Tasks: task_ids = fields.Many2many( 'todo.task', # related model string='Tasks')
The inverse of a Many2one
can be added to the other end of the relation. This has no impact on the actual database structure, but allows us easily browse from the "one" side the "many" side records. A typical use case is the relation between a document header and its lines.
On our example, with a One2many
inverse relation on stages, we could easily list all the tasks in that stage. To add this inverse relation to stages, add the code shown here:
# class Stage(models.Model): # _name = 'todo.task.stage' # Stage class relation with Tasks: tasks = fields.One2many( 'todo.task', # related model 'stage_id', # field for "this" on related model 'Tasks in this stage')
The One2many
accepts three positional arguments: the related model, the field name in that model referring this record, and the title string. The two first positional arguments correspond to the comodel_name
and inverse_name
keyword arguments.
The additional keyword parameters available are the same as for many to one: context
, domain
, ondelete
(here acting on the "many" side of the relation), and auto_join
.
Parent-child relations can be represented using a Many2one
relation to the same model, to let each record reference its parent. And the inverse One2many
makes it easy for a parent to keep track of its children.
Odoo also provides improved support for these hierarchic data structures: faster browsing through tree siblings, and simpler search with the additional child_of
operator in domain expressions.
To enable these features we need to set the _parent_store
flag attribute and add the helper fields: parent_left
and parent_right
. Mind that this additional operation comes at storage and execution time penalties, so it's best used when you expect to read more frequently than write, such as a the case of a category tree.
Revisiting the tags model defined in the todo_ui/todo_model.py
file, we should now edit it to look like this:
class Tags(models.Model): _name = 'todo.task.tag' _parent_store = True # _parent_name = 'parent_id' name = fields.Char('Name') parent_id = fields.Many2one( 'todo.task.tag', 'Parent Tag', ondelete='restrict') parent_left = fields.Integer('Parent Left', index=True) parent_right = fields.Integer('Parent Right', index=True)
Here, we have a basic model, with a parent_id
field to reference the parent record, and the additional _parent_store
attribute to add hierarchic search support. When doing this, the parent_left
and parent_right
fields also have to be added.
The field referring to the parent is expected to be named parent_id
. But any other field name can be used by declaring it with the _parent_name
attribute.
Also, it is often convenient to add a field with the direct children of the record:
child_ids = fields.One2many( 'todo.task.tag', 'parent_id', 'Child Tags')
So far, the relation fields we've seen can only reference one model. The Reference
field type does not have this limitation and supports dynamic relations: the same field is able to refer to more than one model.
We can use it to add a To-do Task field, Refers to, that can either refer to a User
or a Partner
:
# class TodoTask(models.Model): refers_to = fields.Reference( [('res.user', 'User'), ('res.partner', 'Partner')], 'Refers to')
You can see that the field definition is similar to a Selection
field, but here the selection list holds the models that can be used. On the user interface, the user will first pick a model from the list, and then pick a record from that model.
This can be taken to another level of flexibility: a Referencable Models configuration table exists to configure the models that can be used in Reference
fields. It is available in the Settings | Technical | Database Structure menu. When creating such a field we can set it to use any model registered there, with the help of the referencable_models()
function in the openerp.addons.res.res_request
module. In Odoo version 8, it is still using the old-style API, so we need to wrap it to use with the new API:
from openerp.addons.base.res import res_request def referencable_models(self): return res_request.referencable_models( self, self.env.cr, self.env.uid, context=self.env.context)
Using the preceding code, the revisited version of the Refers to
field would look like this:
# class TodoTask(models.Model): refers_to = fields.Reference( referencable_models, 'Refers to')