Looking again at our module design, we have these relationships:
The following Entity Relationship Diagram can help visualizing the relationships we are about to create on the model. The lines ending with a triangle represent a many sides of the relationships:
Let's add the corresponding relationship fields to the to-do tasks in our 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 of 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 relationships, respectively.
As an exercise, you may try to also add the corresponding inverse relationships to the related models:
Many2one
relationship is a One2many
field on Stages, since each Stage can have many tasks. We should add this field to the Stage class.Many2many
relationship is also a Many2many
field on Tags, since each Tag can also be used on many Tasks.Let's have a closer look at relational field definitions.
The Many2one
relationship 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 that an empty value is set when the related record is deleted. Other possible values are restrict
, raising an error preventing the deletion, and cascade
also deleting this record.context
is a dictionary of data, meaningful for the web client views, to carry information when navigating through the relationship. For example, to set default vales. It will be better explained in the Chapter 6, Views - Designing the User Interface.domain
is a domain expression, a list of tuples, used filter the records available for the relation field. auto_join=True
allows the ORM to use SQL joins when doing searches using this relationship. If used, the access 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 signature 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, it does not add any columns to the existing tables. Instead, it automatically creates a new relationship table that has only two ID fields with the foreign keys for the related tables. The relationship table name and the field names are automatically generated. The relationship table name is the two table names joined with an underscore with _rel
appended to it.
On some occasions we may need to override these automatic defaults.
One such case is when the related models have long names, and the name for the automatically generated relationship table is too long, exceeding the 63 characters PostgreSQL limit. In these cases we need to manually choose a name for the relationship table, to conform to the table name size limit.
Another case is when we need a second many-to-many relationship between the same models. In these cases we need to manually provide a name for the relationship table, so that it doesn't collide with the table name already being used for the first relationship.
There are two alternatives to manually override these values: either using positional arguments or keyword arguments.
Using positional arguments for the field definition we have:
# Task <-> Tag relation (positional args): 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='Tags')
We can instead use keyword arguments, which some people prefer for readability:
# Task <-> Tag relation (keyword args): 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='Tags')
Just like many-to-one fields, many-to-many fields also support the domain
and context
keyword attributes.
The inverse of the Many2many
relationship is also a Many2many
field. If we also add a Many2many
field to the Tags
model, Odoo infers that this many-to-many relationship is the inverse of the one in the Task
model.
The inverse relationship between Tasks and Tags can be implemented like this:
class Tag(models.Model): _name = 'todo.task.tag' # Tag class relationship to Tasks: task_ids = fields.Many2many( 'todo.task', # related model string='Tasks')
An inverse of a Many2one
can be added to the other end of the relationship. This has no impact on the actual database structure, but allows us easily browse from the one side of the many related records. A typical use case is the relationship between a document header and its lines.
In our example, a One2many
inverse relationship on Stages allows us to easily list all the Tasks in that Stage. The code to add this inverse relationship to Stages is:
class Stage(models.Model): _name = 'todo.task.stage' # Stage class relationship 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 first two positional arguments correspond to the comodel_name
and inverse_name
keyword arguments.
The additional keyword parameters available are the same as for Many2one
: context
, domain
, ondelete
(here acting on the many side of the relationship), and auto_join
.
Parent-child tree relationships are represented using a Many2one
relationship with the same model, so that each record references its parent. And the inverse One2many
makes it easy for a parent to keep track of its children.
Odoo provides improved support for these hierarchic data structures, for faster browsing through tree siblings, and for easier search using the additional child_of
operator in domain expressions.
To enable these features we need to set the _parent_store
flag attribute and add to the model 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_model.py
file, we should now edit it to look like this:
class Tags(models.Model): _name = 'todo.task.tag' _description = 'To-do 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 must also be added.
The field referring to the parent is expected to be named parent_id
, but any other field name can be used as long as we declare that in 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')
Regular relational fields reference one fixed comodel. The Reference field type does not have this limitation and supports dynamic relationships, so that the same field is able to refer to more than one model.
For example, we can use it to add a Refers to
field to To-do Tasks, 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')
As you can see, 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 av available list, and then pick a record from that model.
This can be taken to another level of flexibility: a Referenceable 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 referenceable_models()
function in the odoo.addons.res.res_request
module.
Using the Referenceable Models configuration, an improved version of the Refers to
field would look like this:
from odoo.addons.base.res.res_request import referenceable_models # class TodoTask(models.Model): refers_to = fields.Reference( referenceable_models, 'Refers to')
Note that in Odoo 9.0 this function used a slightly different spelling, and was still using the old API. So in version 9.0, before using the code shown before, we have to add some code at the top of our Python file to wrap it so that it uses the new API:
from openerp.addons.base.res import res_request def referenceable_models(self): return res_request.referencable_models( self, self.env.cr, self.env.uid, context=self.env.context)