Skip to main content
iVentureTeam

many2many_alt_pos

The list of competing quotations on a purchase order. It marks the one you are looking at, refuses to reopen it, and opens the others in the same window so the breadcrumb keeps growing.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 27, 2026Updated September 2, 20266 min read
Technical namemany2many_alt_pos
Field typesmany2many
Viewsform, with an embedded list
Modulepurchase_requisition, the blanket order and alternatives app
Used in core1 occurrence, the alternatives tab of the purchase order form in purchase_requisition
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0, Odoo 16.0
No-code setupNo. It is tied to the alternatives relation on purchase orders
Alternativesmany2many, one2many, x2many_buttons, many2many_tags

What the alternative orders list does

Asking several suppliers for the same thing produces several quotations that only make sense next to each other. Odoo links them as alternatives, and every one of them shows the same list, including itself.

That self-inclusion is what this widget handles. Its renderer knows which row is the record you are currently on and marks it, so the list reads as a comparison rather than as a set of unrelated documents. Clicking that row is a no-op, because reopening the page you are on would only clear your place.

Clicking any other row opens it properly: the widget asks the server for that record's form action and navigates there in the same window, so the breadcrumb keeps the chain of quotations you have been comparing.

What this means for your team

Comparing quotations is a decision process, not a lookup, and it happens by moving back and forth between documents. Preserving the breadcrumb is what makes that bearable, because the buyer can walk back through the ones they have already seen rather than starting from the list each time.

The self-marking matters for the same reason. Without it, a list of five quotations that all look identical gives no clue which one you are reading, and the usual result is opening the one you are already on and losing your place.

The structural note for anyone planning an upgrade is that this feature moves house in the next version: alternatives are being separated from blanket orders into their own module. The widget name survives, but any dependency declared on the old module needs revisiting.

Supported options in Odoo 19

The widget declares no options of its own, and neither does the x2many descriptor it spreads: the options dictionary is forwarded to the embedded list as CRUD settings. The controls that matter are the field's context and the embedded list's own attributes, listed below and verified against the Odoo 19.0 source.

OptionTypeWhat it does
quotation_onlycontext keySet on the field's context in the core view. Restricts which purchase orders can be linked as alternatives.
no_createbooleanForwarded to the embedded list as a CRUD setting. Prevents creating new orders from the alternatives list.(default: false)
editablestring (list attribute)Set on the embedded list. Governs inline editing of the linked rows.

It forces many-to-many semantics. The widget overrides the check that decides whether the relation is owned or linked, always answering that it is linked. That is deliberate: an alternative is another document in its own right, not a child row, so adding or removing one should link and unlink rather than create and delete.

Working examples

The core usage, trimmed

<field name="alternative_po_ids"
       widget="many2many_alt_pos"
       readonly="not id"
       context="{'quotation_only': True}">
  <list> ... </list>
</field>

The read-only guard keeps alternatives from being linked before the order exists, and the context restricts the selection to quotations.

Restricting how alternatives are added

<field name="alternative_po_ids"
       widget="many2many_alt_pos"
       options="{'no_create': True}"/>

Forwarded to the embedded list as a CRUD setting, so alternatives can only be linked from existing orders.

What clicking does

# for any row that is not the current record
get_formview_action(record)  # asked of the server
doAction(action)             # same window, breadcrumb extended

Asking the server means a custom form view for purchase orders is respected rather than bypassed.

Three overrides, and why one of them forces many-to-many

Three overrides, each one line of intent.

The renderer gains a test comparing each row's database id with the id of the form's root record. That is what lets its row template mark the current document, and it is the only thing the renderer subclass exists for.

The field component overrides the check for whether the relation is a many-to-many, always returning true. Without that, the framework would treat the field according to its declared type and could offer to create and delete rows, which for alternatives would mean creating purchase orders from inside a list.

The open handler is the substantial one. It first checks that the clicked record is not the one already open, and returns immediately if it is. Otherwise it calls the server for that record's form view action, passing the field's context, and hands the result to the action service. The source comment states both intentions: avoid reopening the current record, and open in the same window with the breadcrumb extended.

Asking the server for the action rather than constructing one is the detail worth copying. It means a database with a customized purchase order form gets that form, and any window action configuration such as view modes or context defaults is honored.

Version compatibility

VersionStatusNotes
Odoo 20.0Partial / changedNot released. Same widget name and behavior, but the file and its view move to a new purchase alternatives module.
Odoo 19.0VerifiedVerified against the shipped source.
Odoo 18.0VerifiedSame three overrides and the same navigation behavior.
Odoo 17.0VerifiedSame behavior with the older component conventions.
Odoo 16.0VerifiedSame behavior with the older component conventions.

Upgrade note. The widget name and behavior are stable from Odoo 16 through Odoo 19, so views carry over. What changes in the next version is where it lives: the alternatives feature moves out of the blanket order module into a module of its own, so module dependencies and any inherited views declared against the old module need updating.

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 moves module. On the development branch the file and its view are in a new purchase alternatives module rather than the blanket order module, while the registered widget name is unchanged.

What that means in practice. View XML naming the widget keeps working, provided the new module is installed. Custom modules that depend on the blanket order module purely to get this feature should depend on the new one instead, and inherited views targeting the old module's view identifiers will need to be repointed.

Common problems and fixes

SymptomCause and fix
Clicking a row does nothingIt is the record you are already on, which the widget deliberately refuses to reopen. Expected. The current row is marked; click a different one.
The current row is not markedThe row template that uses the current-record test was overridden. Keep the widget's own row template, or reimplement the marking in your override.
Opening an alternative loses the breadcrumbSomething is opening the record in a dialog rather than through the widget's handler. Check for a customization intercepting the row click.
The list offers to create purchase ordersThe CRUD settings forwarded to the embedded list allow creation. Pass no_create in the options, as a comparison list rarely wants creation.
The field is read-onlyThe core view makes it read-only until the order has been saved. Save the purchase order first; alternatives are links and need a record to link from.
Missing widget error after upgradingThe widget moves to a new module in the next version. Install the new purchase alternatives module and update any dependency on the old one.

Alternative orders list vs the alternatives

WidgetBest forKey difference
many2many_alt_posComparing competing quotations from inside any one of themMarks the current record, refuses to reopen it, and navigates to the others in the same window
many2manyAn ordinary linked listNo current-record marking and no controlled navigation
one2manyOwned child rowsCreates and deletes records rather than linking existing ones
x2many_buttonsA very compact relationButtons rather than a comparable list
many2many_tagsA short set of linksTags, with no columns to compare across

A plain many-to-many widget gives the list without the current-record marking or the controlled navigation, which on a comparison screen is most of the value. If what you need is a comparison table rather than a list of links, a dedicated report is a better fit than any relational widget.

Frequently asked questions

Why does clicking one row do nothing?+
Because it is the record you are already viewing. The widget compares each row's id with the form's root record and returns immediately for a match, so you do not lose your place.
Why does it force many-to-many behavior?+
An alternative quotation is a document in its own right, not a child row. Forcing the linked semantics means adding and removing alternatives links and unlinks rather than creating and deleting purchase orders.
Why does opening an alternative keep the breadcrumb?+
The widget asks the server for the record's form action and opens it in the same window, which is stated in the source comment. That also means a customized purchase order form is respected.
Does it support any options?+
None of its own. It spreads the x2many descriptor and forwards the options dictionary to the embedded list as CRUD settings; the field's context is where the selection is restricted.
What changes in Odoo 20?+
The widget keeps its name but moves into a new purchase alternatives module, as that feature is separated from blanket orders. Module dependencies and inherited views need repointing.

Buying decisions made side by side, not in email

Calls for tenders, alternative quotations and supplier scoring all live in Odoo Purchase, and the setup decides whether buyers use it. We implement purchasing processes on Odoo 16 through 19.

Book a free consultation

How this page was produced

The current-record test, the forced many-to-many semantics, the open handler with its source comment and the server-side form action lookup were read from purchase_order_alternatives_widget.js on the Odoo 19.0 branch, with the usage taken from purchase_requisition/views/purchase_views.xml. Version coverage comes from comparing the file across the 16.0, 17.0 and 18.0 branches, and the module move was verified by locating the file and its view in the new module on the public development branch. Spotted an error? Tell us and we will correct the page.