Enable Dark Mode!
how-to-write-one2many-and-many2many-values-with-command-in-odoo-20.jpg
By: Vishnu P

How to Write One2many and Many2many Values with Command in Odoo 20

Technical Odoo 20 Fields and widgets

If you have been working with Odoo for a while, you will have written something similar to (6, 0, [1, 2, 3]) to populate a Many2many field. This is functional but can be difficult to read and easy to make mistakes with. Luckily, Odoo provides us with a Command class in odoo.fields. This makes the various relational operations very readable.

Within this blog, we will learn how to correctly create One2many and Many2many values using Command within Odoo 20. We will also look at how you can use them in XML data files and JavaScript. Finally, we will look at some common pitfalls to avoid.

Why Do We Need Commands?

There are two types of relation fields that cannot be set directly; these are One2many and Many2many. When using create() or write() functions, you need to provide Odoo with a list of commands to help it understand how to manage those related records (create, update, delete, or link).

The Command Enum

This is an enum type located in odoo.fields. It contains several members, each representing an action and each having a number representing its value from when they were in tuple format.

Import it from odoo.fields import Command. This type is found in odoo/orm/commands.py. Each helper returns the same tuple you would have written by hand. Command.link(5) returns (Command.LINK, 5, 0), which is equivalent to (4, 5, 0).

CommandCodeWhat it does
CREATE0Creates a new record and links it
UPDATE1Updates values of an already linked record
DELETE2Removes the link and deletes the record from the database
UNLINK3Removes the link only; the record stays in the database
LINK4Links an existing record
CLEAR5Removes all links (does not delete the records)
SET6Replaces all existing links with the given list of ids

Helper Methods

You don't have to type the numbers from the enum directly; just call one of the helper methods to get the right command.

from odoo.fields import

Command.create(vals)        # create a new record

Command.update(id, vals)    # update a linked record

Command.delete(id)          # unlink and delete the record

Command.unlink(id)          # unlink only

Command.link(id)            # link an existing record

Command.clear()             # unlink all records

Command.set(ids)            # replace all links with ids

Command.create(vals)

Use when you want to create child records along with the parent. For example, sales orders and lines.

order = self.env['sale.order'].create({
   'partner_id': partner.id,
   'order_line': [
       Command.create({'product_id': product_a.id, 'product_uom_qty': 2}),
       Command.create({'product_id': product_b.id, 'product_uom_qty': 1}),
   ],
})

Example:

The base module will create a model using Command.create in the default value of ir.model. All models created from the UI will have an x_name field. This can be found in odoo/addons/base/models/ir_model.py:

return [Command.create({'name': 'x_name', 'field_description': 'Name', 'ttype': 'char', 'copied': True})]

Command.update(id, vals) and Command.delete(id)

Use these to modify or delete a particular line from an existing record.

order.write({
   'order_line': [
       Command.update(line_1.id, {'product_uom_qty': 5}),
       Command.delete(line_2.id),
   ],
})

Command.link(id) and Command.unlink(id)

These are generally used with Many2many fields where you have existing related records.

# Add one tag, keep the existing ones
partner.write({'category_id': [Command.link(tag.id)]})
# Remove one tag, but do not delete it
partner.write({'category_id': [Command.unlink(tag.id)]})

Command.set(ids)

This replaces the entire value. Whatever you pass into the command will be in the field.

partner.write({'category_id': [Command.set([tag_1.id, tag_2.id])]})

Command.clear()

This removes all links. The records themselves are kept.

partner.write({'category_id': [Command.clear()]})

Combining Commands

Multiple commands can be combined in a single list. They will be evaluated in sequence.

project.write({
   'task_ids': [
       Command.clear(),
       Command.create({'name': 'Kick-off meeting'}),
       Command.link(existing_task.id),
   ],
})

DELETE vs UNLINK: An Important Difference

There are two similar commands:

  • Command.unlink(id): will only remove the link.
  • Command.delete(id): will remove the link and delete the record.

For a One2many field, the result of unlink depends on how the inverse Many2one is defined. Depending on that field (for example, ondelete='cascade'), the line may be deleted; otherwise, the inverse field is set to False, and the record is kept.

Using Commands in XML Data Files

The Command class is available in the eval context of the XML data file.

<record id="demo_partner" model="res.partner">
   <field name="name">Demo Partner</field>
   <field name="category_id"
          eval="[Command.set([ref('base.res_partner_category_12'),
                              ref('base.res_partner_category_14')])]"/>
</record>

Child records can also be created directly:

<record id="demo_order" model="sale.order">
   <field name="partner_id" ref="demo_partner"/>
   <field name="order_line" eval="[
       Command.create({
           'product_id': ref('product.product_product_4'),
           'product_uom_qty': 3,
       }),
   ]"/>
</record>

ref() returns the database id of an XML record.

Using Commands in JavaScript

On the client side, we also have the One2many and Many2many commands. These commands are the same 7 commands and are accessible through x2ManyCommands in JavaScript. You can import them from @web/core/orm_plugin in Odoo 20.

import { x2ManyCommands } from "@web/core/orm_plugin";
await orm.write("res.partner", [partnerId], {
   category_id: [x2ManyCommands.set([1, 2, 3])],
});
await orm.write("project.project", [projectId], {
   task_ids: [
       x2ManyCommands.create(false, { name: "New task" }),
       x2ManyCommands.update(taskId, { name: "Renamed task" }),
       x2ManyCommands.unlink(oldTaskId),
   ],
});

They can be used with the following functions: create, update, delete, unlink, link, clear, and set. The return value for each function is the same as the Python helpers. For example, x2ManyCommands.link(id) will return [4, id, false], and x2ManyCommands.set(ids) will return [6, false, ids]. There is a slight difference with Python, as the create function requires a virtual id as the first parameter. If you don’t need one, simply pass in false.

Common Mistakes with Tuples

Before Command was introduced, the commands were all written with 3-element tuples. This syntax is still allowed but can cause lots of errors.

The tuple equivalents are below:

(0, 0, vals)      # CREATE

(1, id, vals)     # UPDATE

(2, id, 0)        # DELETE

(3, id, 0)        # UNLINK

(4, id, 0)        # LINK

(5, 0, 0)         # CLEAR

(6, 0, ids)       # SET

Putting the IDs in the wrong position

The third item in SET should be the IDs. The second part will always be 0.

# Wrong
[(6, [1, 2, 3])]
[(6, [1, 2, 3], 0)]
# Correct
[(6, 0, [1, 2, 3])]
# Best
[Command.set([1, 2, 3])]

Forgetting the outer list

Commands must be used inside a list

# Wrong

partner.write({'category_id': Command.link(tag.id)})

# Correct

partner.write({'category_id': [Command.link(tag.id)]})

Passing a list of IDs to LINK

You can only pass one id to the link command. If you have multiple records to pass, then you should use multiple commands, or Command.set.

# Wrong

[(4, [1, 2, 3])]

# Correct

[Command.link(i) for i in (1, 2, 3)]

Using SET when you only wanted to add

Using SET replaces all existing links. Any record that is not in your list is removed from the field, but it is not deleted. You should use LINK if you are just adding new records.

Confusing DELETE with UNLINK

By using (2, id, 0), the record will be deleted completely. Many developers will expect it to just unlink. Be sure to check what you want.

Writing a plain list of IDs

# Wrong
partner.write({'category_id': [1, 2, 3]})
Use [Command.set([1, 2, 3])].

Expecting commands when reading

Commands are only used for writing. When reading a One2many/Many2many field, you will receive records (or a list of IDs using read()).

The Command class makes working with One2many and Many2many fields much clearer. You no longer have to remember the codes and positions in tuples. You can just use Command.create, Command.link, or Command.set. This makes it clear what your intention was. Using Command in Python models, data files, or JavaScript in the web client will all use the same 7 commands. If you learn these, you will be able to use them everywhere in Odoo 20.

To read more about How to Use Relational Fields in Odoo 20, refer to our blog, How to Use Relational Fields in Odoo 20.


Frequently Asked Questions

What is the difference between Command.unlink() and Command.delete()?

Command.unlink(id) only removes the link between the two records, and the related record stays in the database. Command.delete(id) removes the link and also deletes the record from the database. For a One2many field, unlink can also delete the line if the inverse Many2one is defined with ondelete='cascade'.

When should I use Command.set() instead of Command.link()?

Use Command.set(ids) when you want the field to contain exactly the records you pass, because it replaces all existing links. Use Command.link(id) when you only want to add a record and keep the existing ones.

Can I use Command in XML data files without importing it?

Yes. The Command class is already available in the eval context of XML data files, so you can write eval="[Command.set([ref('module.record_id')])]" without any import. Use ref() to get the database ID of an XML record.

If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



WhatsApp