Skip to main content
iVentureTeam

pavs_one2many

The values list on a product attribute. Deleting one asks the server whether anything is using it, and refuses outright if the answer is yes, before any confirmation is offered.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 27, 2026Updated September 2, 20265 min read
Technical namepavs_one2many
Field typesone2many
Viewsform, with an embedded list
Moduleproduct, present wherever products exist
Used in core1 occurrence, the values list on the product attribute form in product
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0
No-code setupNo. It calls a server method specific to attribute values
Alternativesone2many, auto_save_res_partner, payment_term_line_ids, many2many_tags

What the attribute values list does

Product attribute values are referenced everywhere once they are in use: on variants, on order lines, in pricing rules, in historical documents. Deleting one that is still referenced is not a decision a user should be allowed to make casually, and in many cases not at all.

This widget guards that. When a value is deleted, it first asks the server whether the value is used on any product. If the server returns a reason, the widget shows it in an Invalid Operation dialog and stops. No confirmation, no deletion.

If the value is genuinely unused, the ordinary delete confirmation appears, and confirming unlinks the value on the server and then saves the attribute. A row that was only just added and never saved skips the whole check and is simply removed.

What this means for your team

Attribute values are the sort of configuration that looks disposable and is not. Deleting a color that appears on three years of sale orders is a data integrity problem that surfaces slowly, in reports and reprints, long after anyone remembers making the change.

Putting the check in front of the confirmation rather than behind it is the right order. A user who is told why they cannot delete something learns the constraint; a user who confirms and then gets a server error learns only that the system is unreliable.

The immediate unlink is the trade. Confirming does not queue a change for the form's save, it writes straight away and then saves the attribute, so there is nothing to discard afterwards. On configuration data that is defensible, and it is worth telling whoever maintains your catalog.

Supported options in Odoo 19

The widget declares no supported options, and neither does the x2many descriptor it spreads: the options dictionary is forwarded to the embedded list as CRUD settings. What governs its behavior is a server method, described in the note. Read from product_attribute_value_list.js, Odoo 19.0.

OptionTypeWhat it does
check_is_used_on_productsserver methodCalled before any deletion. A returned message means the value is in use and the deletion is refused with that message.(since Odoo 18.0)
editablestring (list attribute)Set on the embedded list. Governs inline editing of the values.
deleteboolean (list attribute)Set on the embedded list. Turning it off removes the delete action entirely, and with it the guard.(default: true)

The guard is a server call. The widget asks a method on the attribute value model whether the value is used, and treats any returned message as a refusal. That means the rule lives in Python, where it can be extended by another module without touching the widget.

Working examples

The core usage

<field name="value_ids" widget="pavs_one2many"
       nolabel="1">
  <list editable="bottom"> ... </list>
</field>

No options. Everything the widget adds happens on the delete action.

What the guard calls

# on the attribute value model
check_is_used_on_products(value_id)
# a returned message means: refuse

The message is shown as-is, so the wording comes from Python rather than from the widget.

The ordinary child list

<field name="value_ids">
  <list editable="bottom"> ... </list>
</field>

Deletes without asking the server first, leaving the failure to surface as a constraint error on save.

Ask the server before asking the user

The delete handler runs three checks in order, and the order is the design.

First it calls the server. If a message comes back, the value is in use, and the widget shows that message in a dialog titled Invalid Operation and returns. Nothing else happens, so the user is never offered a confirmation for something that cannot succeed.

Second it checks whether the row is new. An unsaved row has nothing on the server, so it defers to the standard behavior and the row is simply dropped.

Third, for an existing and unused value, it shows the ordinary delete confirmation. Confirming unlinks the record on the server, calls the standard delete handling, and then saves the attribute so the list and the record agree.

The Odoo 19 change is in how that confirmation is awaited. Odoo 18 opened the dialog and continued immediately, so the calling code did not wait for the user; Odoo 19 wraps it in a promise that resolves on confirm or cancel. Anything that awaited the delete now actually waits, which matters when several rows are removed in sequence.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Only the confirmation button's styling changes on the development branch.
Odoo 19.0VerifiedVerified against the shipped source. The delete confirmation is properly awaited.
Odoo 18.0Partial / changedSame guard, but the confirmation was not awaited, so callers continued before the user answered.
Odoo 17.0Not availableWidget does not exist.
Odoo 16.0Not availableWidget does not exist.

Upgrade note. No view change is needed. The widget arrived in Odoo 18, and Odoo 19 changed only how the confirmation is awaited, which affects sequencing rather than the visible flow. On Odoo 17 and earlier the values list had no guard of this kind.

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.

One cosmetic change. The delete confirmation's button gains a danger style on the development branch. The server check, the refusal dialog, the immediate unlink and the awaiting behavior are all unchanged.

Common problems and fixes

SymptomCause and fix
A value cannot be deletedThe server reported it as used on products, and the widget refuses before offering a confirmation. Read the message; remove the value from the products using it first.
Deleting removed the value immediatelyIntended. Confirming unlinks server side and then saves the attribute rather than queueing the change. Expected. There is nothing to discard afterwards.
A new row deletes without any checkAn unsaved row has nothing on the server to check. Expected.
Deleting several rows behaves oddly on Odoo 18The confirmation was not awaited on that version, so sequences of deletions overlapped. Upgrade to Odoo 19, where the confirmation blocks properly.
The refusal message is unclearThe text comes from the server method, not the widget. Adjust the message in Python if you maintain that module.
Options are ignoredThe widget declares none and the descriptor forwards the dictionary to the embedded list. Use the list attributes instead.

Attribute values list vs the alternatives

WidgetBest forKey difference
pavs_one2manyDeleting configuration records that other data may depend onAsks the server whether the value is in use and refuses with the server's own message before confirming
one2manyChild rows with no deletion consequencesDeletes without a server check, leaving failures to surface on save
auto_save_res_partnerChild lists needing a saved parentGuards the add action rather than the delete
payment_term_line_idsLists whose new rows must survive a click elsewhereAnother small guard on the same family of widget
many2many_tagsLinking values rather than owning themUnlinking rather than deleting, so the question does not arise

Use a plain child list wherever deletion has no consequences beyond the record. Where it does, the pattern here is worth copying: ask the server first, refuse with the server's own message, and only then confirm. Putting the check in Python also means another module can tighten it without touching the interface.

Frequently asked questions

Why can I not delete an attribute value?+
Because the server reported it as used on products. The widget asks before offering a confirmation and shows the server's message in an Invalid Operation dialog instead.
Why is the deletion immediate rather than pending?+
Confirming unlinks the record server side and then saves the attribute, so the list and the stored record agree straight away. There is nothing left to discard.
What about a row I just added?+
It skips the check entirely. An unsaved row has nothing on the server to be used by anything.
What changed in Odoo 19?+
The delete confirmation is now awaited properly. On Odoo 18 the calling code continued before the user answered, which showed up when deleting several rows in sequence.
Can I change the refusal message?+
Only in Python. The text comes from the server method the widget calls, which is also where another module can tighten the rule.

Product data that survives its own history

Attributes, variants and the documents that reference them are easy to change and hard to unpick. We structure Odoo product data so today's cleanup does not break last year's orders, on versions 16 through 19.

Book a free consultation

How this page was produced

The three-stage delete handler, the server call and its treatment of the returned message, the immediate unlink and the save afterwards were read from product_attribute_value_list.js on the Odoo 19.0 branch, with the usage taken from product/views/product_attribute_views.xml. The Odoo 18 difference in awaiting the confirmation was established by comparing the same file on that branch, and the Odoo 20 note comes from the public development branch. Spotted an error? Tell us and we will correct the page.