Skip to main content
iVentureTeam

sol_o2m

The order lines table on every Odoo quotation. Most of what it adds to the generic sectioned list is one feature: making combo products behave like a single line that happens to have parts.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 27, 2026Updated September 1, 20267 min read
Technical namesol_o2m
Field typesone2many
Viewsform, with an embedded list
Modulesale, the Sales app
Used in core1 occurrence, the order lines tab of the sale order form in sale
VersionsOdoo 20.0, Odoo 19.0
No-code setupNo. It is tied to the sale order line model and its combo fields
Alternativesproduct_label_section_and_note_field_o2m, so_template_line_o2m, sol_text, one2many

What the sale order lines does

A quotation's lines are the most heavily worked table in Odoo. They carry products, sections, notes, quantities, discounts and taxes, and since Odoo 19 they also carry combos: a single sellable thing made of several products the customer chooses between.

A combo is awkward to represent in a flat list. It is one line from the customer's point of view and several rows in the database, and the rows underneath the combo are not independently editable, sortable or deletable. This widget is what makes that work.

It builds on the shared document line list that invoices and purchase orders also use, so sections, notes, drag ordering and product labels all behave the same way. What it adds is combo awareness: the styling that makes a combo read as a group header, the locking of its items, the group-wise reordering and the totals shown on the combo row.

What this means for your team

Combos exist to sell a bundle without pretending it is one product. The customer picks a size, a color or a plan; the order still shows what they picked, and the pricing still adds up per item. That only works if the interface treats the group as a unit, which is what this widget provides.

The behavior to brief a sales team on is the locking. Inside a combo, only the description, the taxes and the delivered quantity are editable. Everything else, including quantity and price on the items, is controlled by the combo and its configurator. Users who try to edit an item price directly will find the cell inert, and the answer is to change the combo, not the row.

The second thing worth knowing is that combo totals shown on the header row are computed in the browser by summing the item rows. They are a display convenience, not a stored value, so they follow whatever is currently in the list rather than what the server last calculated.

Supported options in Odoo 19

The widget declares no options of its own. It spreads the product label section list descriptor, which itself spreads the shared section list, so everything below is inherited two levels up. Read from sale_order_line_field.js and the shared section and product label fields, Odoo 19.0.

OptionTypeWhat it does
aggregated_fieldsstring (attribute)Inherited from the shared section list. Comma-separated columns to sum. On a combo row those sums are computed in the browser from the item rows.
subsectionsbooleanInherited. Enables a second level of section nesting in the line list.(default: false)
hide_pricesbooleanInherited, and read in the base extractor without being declared. Hides price columns.(default: false)
hide_compositionbooleanInherited and undeclared in the same way. Hides the composition display of combo and kit lines.(default: false)

Combo behavior is hardcoded. The widget identifies a combo by the product type and an item by the presence of a combo item link, both read literally, and the list of fields that stay editable on an item is written into the source. None of it is configurable, and none of it works on a model that does not carry those fields.

Working examples

The core usage, trimmed

<field name="order_line" widget="sol_o2m"
       mode="list,kanban">
  <list editable="bottom">
    <field name="sequence" widget="handle"/>
    <field name="product_template_id" widget="sol_product_many2one"/>
    <field name="name" widget="sol_text"/>
  </list>
</field>

The widget expects its companion cell widgets: the product picker and the label text field are part of the same design.

Aggregated columns on combo rows

<field name="order_line" widget="sol_o2m"
       aggregated_fields="price_subtotal"/>

The attribute is inherited from the shared section list. Fields named here are summed across a combo's items and shown on the combo row.

The two product columns

<field name="product_template_id" optional="show"/>
<field name="product_id" optional="hide"/>

Enabling the variant column automatically removes the template column, so users never see both at once.

How a combo is found without a relation

The combo grouping is worked out by walking the list rather than by reading a relation. From a combo row the widget walks forward, collecting rows while they carry a combo item link pointing back at that combo, and stops at the first row that does not. From an item row it walks backward until it finds the combo it belongs to. Both directions handle unsaved rows by matching on the temporary identifier as well as the database one.

That walk is what powers everything else. Reordering a combo swaps the whole collected group against whatever is above or below it, and the neighbour is itself collected as a group if it turns out to be part of one, so a combo never lands in the middle of another. Dragging is disabled on those rows precisely because the drag would move a single row out of its group.

The locking is a straightforward override: a cell on an item row is read-only unless its column is the description, the taxes or the delivered quantity. Everything else, including price and quantity, is owned by the combo.

The totals on a combo row are computed by summing the item rows for each aggregated column and formatting the result with the column's own formatter. The widget also has to opt those cells into being formatted at all, because the combo row is otherwise treated as a section header, and section headers do not normally print values.

Deleting has a small but important extra step: before removing a combo row, the widget clears the stored selection of combo items on that record. Without it, a subsequently re-added combo would open its configurator with the previous selection.

Version compatibility

VersionStatusNotes
Odoo 20.0Partial / changedNot released. The widget remains; its companion label cell widget is renamed and its internals rework combo handling.
Odoo 19.0VerifiedFirst version, shipped with combo products. Verified against the shipped source.
Odoo 18.0Not availableWidget does not exist. Order lines used the generic product label section list.
Odoo 17.0Not availableWidget does not exist.
Odoo 16.0Not availableWidget does not exist.

Upgrade note. The widget is new in Odoo 19 together with combo products. On Odoo 18 the order lines used the generic product label section list, so an upgrade gains combo handling with the standard views and nothing needs migrating. Custom sale order forms that replaced the lines field should switch to this widget to get combo support.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The development branch is unstable and this may still change; we re-verify the page after release.

The widget survives, its companion cell does not. The order line label column moves from sol_text to a new widget in its own file, and the old name is no longer registered. A custom order line view still naming the old cell widget will silently fall back to the plain text field.

Internals move. The file imports the shared section record helper and the command helpers from their new locations, and adds a field specification helper, which suggests the combo handling is being reworked around explicit field loading rather than walking the loaded list.

Common problems and fixes

SymptomCause and fix
Combo item cells will not accept editsIntended. Only the description, taxes and delivered quantity stay editable on an item row. Change the combo itself through its configurator.
A combo line cannot be draggedIntended. Dragging is disabled on combo rows and their items so a row cannot leave its group. Use the group reordering controls, which move the whole combo.
Combo totals look wrongThey are summed in the browser from the item rows currently in the list, not read from a stored field. Save the order and reload if the item rows are out of step with the server.
Re-adding a combo reopens with old choicesThe stored item selection was not cleared, which the widget normally does before deleting a combo row. Check for a customization overriding the delete handling.
Both product columns are visibleThe variant column is not enabled, so the template column is not hidden. Expected. Enabling the variant column hides the template one automatically.
Nothing combo-related works on my modelThe widget reads the product type and combo item link by literal name. Use it only on sale order lines, or align your model's field names.

Sale order lines vs the alternatives

WidgetBest forKey difference
sol_o2mSale order lines, including combo products and their locked item rowsAdds combo grouping, locking, group reordering and browser-side totals to the shared document line list
product_label_section_and_note_field_o2mInvoice and bill linesSections and product labels without any combo handling
so_template_line_o2mQuotation template linesOptional sections and quantity bookkeeping instead of combos
sol_textThe description cell inside these linesA cell widget rather than the list itself
one2manyAn ordinary child listNo sections, no product labels and no combos

For invoices and purchase orders, the shared product label section list is the right widget; it has the sections and product labels without the combo machinery. Use this one only on sale order lines, where the combo fields exist. If you need combo-like grouping on your own model, the walk-the-list approach is the part to copy, not the widget.

Frequently asked questions

Why can I not edit a combo item's price?+
Because the widget locks every cell on a combo item row except the description, the taxes and the delivered quantity. Price and quantity belong to the combo and its configurator.
How does it know which rows belong to a combo?+
By walking the list. From a combo row it collects the following rows while they link back to it, and from an item row it walks backward to find its combo. Unsaved rows are matched on their temporary identifier.
Why can I not drag a combo line?+
Dragging is disabled on combo rows and their items, because a drag would move one row out of its group. Reordering moves the whole combo at once.
Are combo totals stored?+
No. They are summed in the browser from the item rows currently loaded, for the columns named in the aggregated fields attribute.
Which versions have it?+
Odoo 19 onwards, shipped with combo products. On the Odoo 20 development branch the widget survives but its companion description cell widget is renamed.

Quotes that price a bundle without hiding what is in it

Combos, optional sections and configurable products change how your quotes read and how your margins report. We set up Odoo Sales around how you actually sell, on versions 16 through 19.

Book a free consultation

How this page was produced

The combo detection walk, the locked columns, the group reordering, the browser-side aggregation and the selection clearing on delete were read from sale_order_line_field.js on the Odoo 19.0 branch, with the inherited behavior read from the product label section list and the shared section list in the account module. The usage comes from sale/views/sale_order_views.xml. Version coverage comes from the absence of the file on 16.0 and 17.0, a comparison with 18.0, and a full comparison against the public development branch including its view changes. Spotted an error? Tell us and we will correct the page.