Chained Swapper

chained_swapper
REPOSITORY
REPOSITORYOCA/server-ux
GIT
GIThttps://github.com/OCA/server-ux.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/server-ux/tree/18.0/chained_swapper
VERSION
VERSION 1.0.0
CATEGORY
CATEGORYTools
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSTecnativa, Odoo Community Association (OCA)
MAINTAINERS
MAINTAINERSTecnativa, Odoo Community Association (OCA)
COMMITTERS
COMMITTERSPedro M. Baeza, Weblate, OCA-git-bot, oca-ci, juancarlosonate-tecnativa
WEBSITE
WEBSITEhttps://github.com/OCA/server-ux
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 19:30:16
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - base
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION
This module allows to swap the value of a field and propagate it in a
chained way to linked records. Example: changing the delivery address in
a confirmed sales order, it should be changed in its delivery orders as
well.

It also allows to apply constraints for not allowing to do that change
according rules, so the business logic is not broken. Example: Don't
allow to change the delivery address if the delivery order is validated.

This module requires some technical knowledge for setting the chained
swap and the constraint, as it's defined through technical names and
Python code.

**WARNING**: Use this module with care, as it can screw up database
consistency if swaps are not properly designed .

Code Analysis

Views touched (6)
XML IDNameModelTypeStatus
chained_swapper_constraint_view_form chained.swapper.constraint.form chained.swapper.constraint form New
chained_swapper_constraint_view_tree chained.swapper.constraint.tree chained.swapper.constraint list New
chained_swapper_sub_field_view_tree chained.swapper.sub.field.tree chained.swapper.sub.field list New
chained_swapper_view_form chained.swapper.form chained.swapper form New
chained_swapper_view_tree chained.swapper.tree chained.swapper list New
chained_swapper_wizard_view_form chained.swapper.wizard.form chained.swapper.wizard form New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (4)

New fields (8)
  • allowed_field_ids_domain Binary
    compute='_compute_allowed_field_ids_domain'
  • constraint_ids One2many → chained.swapper.constraint
    comodel_name='chained.swapper.constraint' inverse_name='chained_swapper_id' string='Constraints'
  • field_id Many2one → ir.model.fields
    comodel_name='ir.model.fields' domain='allowed_field_ids_domain' ondelete='cascade' required=True
  • group_ids Many2many → res.groups
    column1='mass_id' column2='group_id' comodel_name='res.groups' relation='mass_group_rel' string='Groups'
  • model_id Many2one → ir.model
    comodel_name='ir.model' help='Model is used for Selecting Field. This is editable until Contextual Action is not created.' ondelete='cascade' required=True
  • name Char
    index=1 required=True translate=True
  • ref_ir_act_window_id Many2one → ir.actions.act_window
    comodel_name='ir.actions.act_window' help='Action to make this template available on records of the related document model.' readonly=True string='Action'
  • sub_field_ids One2many → chained.swapper.sub.field
    comodel_name='chained.swapper.sub.field' inverse_name='chained_swapper_id' string='Sub-fields'
Public methods (4)
  • add_action(self)
  • unlink(self)
  • unlink_action(self)
  • write(self, vals)

New fields (3)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • expression Text
    default='True' help="Boolean python expression. You can use the keyword 'records' as the records selected to execute the contextual action. Ex.: bool(records.mapped('parent_id'))" required=True string='Constraint expression'
  • name Char
    required=True translate=True
Public methods (0)

No public methods.

New fields (3)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • model_name Char
    related='chained_swapper_id.model_id.model' string='Model Name'
  • sub_field_chain Char
    help="You can specify here a field of related fields as dotted names. Ex.: 'child_ids.lang'." required=True
Public methods (0)

No public methods.

New fields (1)
  • company_id Many2one → res.company
    default=<expr> string='Company' args: 'res.company'
Public methods (9)
  • change_action(self)
  • create(self, vals_list)
    @api.model_create_multi
    As we don't have any field in this model, the key-value pair received in vals dict are only used to change the value in the active models.
  • default_get(self, fields)
    @api.model
  • fields_get(self, allfields=None, attributes=None)
    @api.model
  • get_view(self, view_id=None, view_type='form', **options)
    @api.model
    As we don't have any field in this model, result['fields'] and result['arch'] are modified to add dynamically the corresponding field.
  • get_views(self, views, options=None)
    @api.model
  • onchange(self, values, field_names, fields_spec)
  • post_chained_swap(self, model, field_name, original_values, new_value)
    @api.model
  • read(self, fields, load='_classic_read')
    Without this call, dynamic fields build by get_view() generate a crash and warning, i.e.: read() with unknown field 'myfield'
REPOSITORY
REPOSITORYOCA/server-ux
GIT
GIThttps://github.com/OCA/server-ux.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/server-ux/tree/15.0/chained_swapper
VERSION
VERSION 1.0.4
CATEGORY
CATEGORYTools
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSTecnativa, Odoo Community Association (OCA)
MAINTAINERS
MAINTAINERSTecnativa, Odoo Community Association (OCA)
COMMITTERS
COMMITTERSPedro M. Baeza, Enric Tobella, Víctor Martínez, Weblate, OCA-git-bot, oca-ci, Cesar Andres Sanchez, josep-tecnativa
WEBSITE
WEBSITEhttps://github.com/OCA/server-ux
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:46:39
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - base
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION

Code Analysis

Views touched (6)
XML IDNameModelTypeStatus
chained_swapper_constraint_view_form chained.swapper.constraint.form chained.swapper.constraint form New
chained_swapper_constraint_view_tree chained.swapper.constraint.tree chained.swapper.constraint tree New
chained_swapper_sub_field_view_tree chained.swapper.sub.field.tree chained.swapper.sub.field tree New
chained_swapper_view_form chained.swapper.form chained.swapper form New
chained_swapper_view_tree chained.swapper.tree chained.swapper tree New
chained_swapper_wizard_view_form chained.swapper.wizard.form chained.swapper.wizard form New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (4)

New fields (8)
  • allowed_field_ids Many2many → ir.model.fields
    comodel_name='ir.model.fields' compute='_compute_allowed_field_ids'
  • constraint_ids One2many → chained.swapper.constraint
    comodel_name='chained.swapper.constraint' inverse_name='chained_swapper_id' string='Constraints'
  • field_id Many2one → ir.model.fields
    comodel_name='ir.model.fields' domain="[('id', 'in', allowed_field_ids)]" ondelete='cascade' required=True
  • group_ids Many2many → res.groups
    column1='mass_id' column2='group_id' comodel_name='res.groups' relation='mass_group_rel' string='Groups'
  • model_id Many2one → ir.model
    comodel_name='ir.model' help='Model is used for Selecting Field. This is editable until Contextual Action is not created.' ondelete='cascade' required=True
  • name Char
    index=1 required=True translate=True
  • ref_ir_act_window_id Many2one → ir.actions.act_window
    comodel_name='ir.actions.act_window' help='Action to make this template available on records of the related document model.' readonly=True string='Action'
  • sub_field_ids One2many → chained.swapper.sub.field
    comodel_name='chained.swapper.sub.field' inverse_name='chained_swapper_id' string='Sub-fields'
Public methods (4)
  • add_action(self)
  • unlink(self)
  • unlink_action(self)
  • write(self, vals)

New fields (3)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • expression Text
    default='True' help="Boolean python expression. You can use the keyword 'records' as the records selected to execute the contextual action. Ex.: bool(records.mapped('parent_id'))" required=True string='Constraint expression'
  • name Char
    required=True translate=True
Public methods (0)

No public methods.

New fields (2)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • sub_field_chain Char
    help="You can specify here a field of related fields as dotted names. Ex.: 'child_ids.lang'." required=True
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (6)
  • change_action(self)
  • create(self, vals)
    @api.model
    As we don't have any field in this model, the key-value pair received in vals dict are only used to change the value in the active models.
  • default_get(self, fields)
    @api.model
  • fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False)
    @api.model
    As we don't have any field in this model, result['fields'] and result['arch'] are modified to add dynamically the corresponding field.
  • post_chained_swap(self, model, field_name, original_values, new_value)
    @api.model
  • read(self, fields, load='_classic_read')
    Without this call, dynamic fields build by fields_view_get() generate a crash and warning, i.e.: read() with unknown field 'myfield'
REPOSITORY
REPOSITORYOCA/server-ux
GIT
GIThttps://github.com/OCA/server-ux.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/server-ux/tree/14.0/chained_swapper
VERSION
VERSION 1.0.2
CATEGORY
CATEGORYTools
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSTecnativa, Odoo Community Association (OCA)
MAINTAINERS
MAINTAINERSTecnativa, Odoo Community Association (OCA)
COMMITTERS
COMMITTERSEnric Tobella, OCA Transbot, oca-travis, Weblate, OCA-git-bot, Enrique Martín, fshah, schhatbar
WEBSITE
WEBSITEhttps://github.com/OCA/server-ux
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:41:07
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - base
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION

Code Analysis

Views touched (6)
XML IDNameModelTypeStatus
chained_swapper_constraint_view_form chained.swapper.constraint.form chained.swapper.constraint form New
chained_swapper_constraint_view_tree chained.swapper.constraint.tree chained.swapper.constraint tree New
chained_swapper_sub_field_view_tree chained.swapper.sub.field.tree chained.swapper.sub.field tree New
chained_swapper_view_form chained.swapper.form chained.swapper form New
chained_swapper_view_tree chained.swapper.tree chained.swapper tree New
chained_swapper_wizard_view_form chained.swapper.wizard.form chained.swapper.wizard form New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (4)

New fields (8)
  • allowed_field_ids Many2many → ir.model.fields
    comodel_name='ir.model.fields' compute='_compute_allowed_field_ids'
  • constraint_ids One2many → chained.swapper.constraint
    comodel_name='chained.swapper.constraint' inverse_name='chained_swapper_id' string='Constraints'
  • field_id Many2one → ir.model.fields
    comodel_name='ir.model.fields' domain="[('id', 'in', allowed_field_ids)]" ondelete='cascade' required=True
  • group_ids Many2many → res.groups
    column1='mass_id' column2='group_id' comodel_name='res.groups' relation='mass_group_rel' string='Groups'
  • model_id Many2one → ir.model
    comodel_name='ir.model' help='Model is used for Selecting Field. This is editable until Contextual Action is not created.' ondelete='cascade' required=True
  • name Char
    index=1 required=True translate=True
  • ref_ir_act_window_id Many2one → ir.actions.act_window
    comodel_name='ir.actions.act_window' help='Action to make this template available on records of the related document model.' readonly=True string='Action'
  • sub_field_ids One2many → chained.swapper.sub.field
    comodel_name='chained.swapper.sub.field' inverse_name='chained_swapper_id' string='Sub-fields'
Public methods (4)
  • add_action(self)
  • unlink(self)
  • unlink_action(self)
  • write(self, vals)

New fields (3)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • expression Text
    default='True' help="Boolean python expression. You can use the keyword 'records' as the records selected to execute the contextual action. Ex.: bool(records.mapped('parent_id'))" required=True string='Constraint expression'
  • name Char
    required=True translate=True
Public methods (0)

No public methods.

New fields (2)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • sub_field_chain Char
    help="You can specify here a field of related fields as dotted names. Ex.: 'child_ids.lang'." required=True
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (6)
  • change_action(self)
  • create(self, vals)
    @api.model
    As we don't have any field in this model, the key-value pair received in vals dict are only used to change the value in the active models.
  • default_get(self, fields)
    @api.model
  • fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False)
    @api.model
    As we don't have any field in this model, result['fields'] and result['arch'] are modified to add dynamically the corresponding field.
  • post_chained_swap(self, model, field_name, original_values, new_value)
    @api.model
  • read(self, fields, load='_classic_read')
    Without this call, dynamic fields build by fields_view_get() generate a crash and warning, i.e.: read() with unknown field 'myfield'
REPOSITORY
REPOSITORYOCA/server-ux
GIT
GIThttps://github.com/OCA/server-ux.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/server-ux/tree/13.0/chained_swapper
VERSION
VERSION 1.0.1
CATEGORY
CATEGORYTools
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSTecnativa, Odoo Community Association (OCA)
MAINTAINERS
MAINTAINERSTecnativa, Odoo Community Association (OCA)
COMMITTERS
COMMITTERSPedro M. Baeza, Ernesto Tejeda, oca-travis, Weblate, OCA-git-bot
WEBSITE
WEBSITEhttps://github.com/OCA/server-ux
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:34:17
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - base
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION

Code Analysis

Views touched (6)
XML IDNameModelTypeStatus
chained_swapper_constraint_view_form chained.swapper.constraint.form chained.swapper.constraint form New
chained_swapper_constraint_view_tree chained.swapper.constraint.tree chained.swapper.constraint tree New
chained_swapper_sub_field_view_tree chained.swapper.sub.field.tree chained.swapper.sub.field tree New
chained_swapper_view_form chained.swapper.form chained.swapper form New
chained_swapper_view_tree chained.swapper.tree chained.swapper tree New
chained_swapper_wizard_view_form chained.swapper.wizard.form chained.swapper.wizard form New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (4)

New fields (8)
  • allowed_field_ids Many2many → ir.model.fields
    comodel_name='ir.model.fields' compute='_compute_allowed_field_ids'
  • constraint_ids One2many → chained.swapper.constraint
    comodel_name='chained.swapper.constraint' inverse_name='chained_swapper_id' string='Constraints'
  • field_id Many2one → ir.model.fields
    comodel_name='ir.model.fields' domain="[('id', 'in', allowed_field_ids)]" required=True
  • group_ids Many2many → res.groups
    column1='mass_id' column2='group_id' comodel_name='res.groups' relation='mass_group_rel' string='Groups'
  • model_id Many2one → ir.model
    comodel_name='ir.model' help='Model is used for Selecting Field. This is editable until Contextual Action is not created.' required=True
  • name Char
    index=1 required=True translate=True
  • ref_ir_act_window_id Many2one → ir.actions.act_window
    comodel_name='ir.actions.act_window' help='Action to make this template available on records of the related document model.' readonly=True string='Action'
  • sub_field_ids One2many → chained.swapper.sub.field
    comodel_name='chained.swapper.sub.field' inverse_name='chained_swapper_id' string='Sub-fields'
Public methods (4)
  • add_action(self)
  • unlink(self)
  • unlink_action(self)
  • write(self, vals)

New fields (3)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • expression Text
    default='True' help="Boolean python expression. You can use the keyword 'records' as the records selected to execute the contextual action. Ex.: bool(records.mapped('parent_id'))" required=True string='Constraint expression'
  • name Char
    required=True translate=True
Public methods (0)

No public methods.

New fields (2)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • sub_field_chain Char
    help="You can specify here a field of related fields as dotted names. Ex.: 'child_ids.lang'." required=True
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (6)
  • change_action(self)
  • create(self, vals)
    @api.model
    As we don't have any field in this model, the key-value pair received in vals dict are only used to change the value in the active models.
  • default_get(self, fields)
    @api.model
  • fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False)
    @api.model
    As we don't have any field in this model, result['fields'] and result['arch'] are modified to add dynamically the corresponding field.
  • post_chained_swap(self, model, field_name, original_values, new_value)
    @api.model
  • read(self, fields, load='_classic_read')
    Without this call, dynamic fields build by fields_view_get() generate a crash and warning, i.e.: read() with unknown field 'myfield'
REPOSITORY
REPOSITORYOCA/server-ux
GIT
GIThttps://github.com/OCA/server-ux.git
GIT FOLDER
GIT FOLDERhttps://github.com/OCA/server-ux/tree/12.0/chained_swapper
VERSION
VERSION 1.0.1
CATEGORY
CATEGORYTools
LICENSE
LICENSEAGPL-3
APPLICATION
APPLICATIONNo
AUTO-INSTALLABLE
AUTO-INSTALLABLENo
AUTHORS
AUTHORSTecnativa, Odoo Community Association (OCA)
MAINTAINERS
MAINTAINERSTecnativa, Odoo Community Association (OCA)
COMMITTERS
COMMITTERSOCA Transbot, sergio-teruel, Ernesto Tejeda, oca-travis, OCA-git-bot
WEBSITE
WEBSITEhttps://github.com/OCA/server-ux
LAST TRACKING UPDATE
LAST TRACKING UPDATE2026-07-06 00:29:22
ODOO DEPENDENCIES
ODOO DEPENDENCIES odoo/odoo:
    - base
PYTHON DEPENDENCIES
PYTHON DEPENDENCIES Not have
SYSTEM DEPENDENCIES
SYSTEM DEPENDENCIES Not have
DESCRIPTION
DESCRIPTION

Code Analysis

Views touched (6)
XML IDNameModelTypeStatus
chained_swapper_constraint_view_form chained.swapper.constraint.form chained.swapper.constraint form New
chained_swapper_constraint_view_tree chained.swapper.constraint.tree chained.swapper.constraint tree New
chained_swapper_sub_field_view_tree chained.swapper.sub.field.tree chained.swapper.sub.field tree New
chained_swapper_view_form chained.swapper.form chained.swapper form New
chained_swapper_view_tree chained.swapper.tree chained.swapper tree New
chained_swapper_wizard_view_form chained.swapper.wizard.form chained.swapper.wizard form New
HTTP endpoints (0)

No HTTP endpoints found for this module.

Models touched (4)

New fields (8)
  • allowed_field_ids Many2many → ir.model.fields
    comodel_name='ir.model.fields' compute='_compute_allowed_field_ids'
  • constraint_ids One2many → chained.swapper.constraint
    comodel_name='chained.swapper.constraint' inverse_name='chained_swapper_id' string='Constraints'
  • field_id Many2one → ir.model.fields
    comodel_name='ir.model.fields' domain="[('id', 'in', allowed_field_ids)]" required=True
  • group_ids Many2many → res.groups
    column1='mass_id' column2='group_id' comodel_name='res.groups' relation='mass_group_rel' string='Groups'
  • model_id Many2one → ir.model
    comodel_name='ir.model' help='Model is used for Selecting Field. This is editable until Contextual Action is not created.' required=True
  • name Char
    index=1 required=True translate=True
  • ref_ir_act_window_id Many2one → ir.actions.act_window
    comodel_name='ir.actions.act_window' help='Action to make this template available on records of the related document model.' readonly=True string='Action'
  • sub_field_ids One2many → chained.swapper.sub.field
    comodel_name='chained.swapper.sub.field' inverse_name='chained_swapper_id' string='Sub-fields'
Public methods (4)
  • add_action(self)
    @api.multi
  • unlink(self)
    @api.multi
  • unlink_action(self)
    @api.multi
  • write(self, vals)

New fields (3)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • expression Text
    default='True' help="Boolean python expression. You can use the keyword 'records' as the records selected to execute the contextual action. Ex.: bool(records.mapped('parent_id'))" required=True string='Constraint expression'
  • name Char
    required=True translate=True
Public methods (0)

No public methods.

New fields (2)
  • chained_swapper_id Many2one → chained.swapper
    comodel_name='chained.swapper' ondelete='cascade'
  • sub_field_chain Char
    help="You can specify here a field of related fields as dotted names. Ex.: 'child_ids.lang'." required=True
Public methods (0)

No public methods.

New fields (0)

No new fields.

Public methods (6)
  • change_action(self)
    @api.multi
  • create(self, vals)
    @api.model
    As we don't have any field in this model, the key-value pair received in vals dict are only used to change the value in the active models.
  • default_get(self, fields)
    @api.model
  • fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False)
    @api.model
    As we don't have any field in this model, result['fields'] and result['arch'] are modified to add dynamically the corresponding field.
  • post_chained_swap(self, model, field_name, original_values, new_value)
    @api.model
  • read(self, fields, load='_classic_read')
    Without this call, dynamic fields build by fields_view_get() generate a crash and warning, i.e.: read() with unknown field 'myfield'
STATUS
STATUSOpen migration PR - not merged yet for this version
REPOSITORY
REPOSITORYOCA/server-ux
PULL REQUEST
PULL REQUEST[19.0][MIG] chained_swapper: Migration to 19.0 (#1297)