Relations between models

Looking again at our module design, we have these relations:

  • Each task has a stage – that's a many to one relation, also known as a foreign key. The inverse relation is a one to many, meaning that each stage can have many tasks.
  • Each task can have many tags – that's a many to many relation. The inverse relation, of course, is also a many to many, since each tag can also have many tasks.

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:

  • The inverse of the Many2one relation is a One2many field on stages: each stage can have many tasks. We should add this field to the Stage class.
  • The inverse of the 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.

Many to one relations

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.

Many to many relations

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.

Tip

PostgreSQL table names have a limit of 63 characters, and this can be a problem if the automatically generated relation table name exceeds that limit. That is a case where we should manually set the relational table name using the relation attribute.

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')

One to many inverse relations

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.

Hierarchical relations

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')

Referencing fields using dynamic relations

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')
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset