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).
| Command | Code | What it does |
| CREATE | 0 | Creates a new record and links it |
| UPDATE | 1 | Updates values of an already linked record |
| DELETE | 2 | Removes the link and deletes the record from the database |
| UNLINK | 3 | Removes the link only; the record stays in the database |
| LINK | 4 | Links an existing record |
| CLEAR | 5 | Removes all links (does not delete the records) |
| SET | 6 | Replaces 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.