From 09d44e92fa0dd616e4fb19e9dda1575bd8cbfb4c Mon Sep 17 00:00:00 2001
From: Ernesto Tejeda
Date: Wed, 12 Aug 2020 10:53:17 -0400
Subject: [PATCH 01/18] [ADD] chained_swapper: new module
---
chained_swapper/README.rst | 113 +++++
chained_swapper/__init__.py | 5 +
chained_swapper/__manifest__.py | 21 +
chained_swapper/demo/chained_swapper_demo.xml | 32 ++
chained_swapper/hooks.py | 14 +
chained_swapper/i18n/chained_swapper.pot | 250 ++++++++++
chained_swapper/i18n/es.po | 274 +++++++++++
chained_swapper/models/__init__.py | 3 +
chained_swapper/models/chained_swapper.py | 180 +++++++
chained_swapper/readme/CONFIGURE.rst | 14 +
chained_swapper/readme/CONTRIBUTORS.rst | 4 +
chained_swapper/readme/DESCRIPTION.rst | 6 +
chained_swapper/readme/USAGE.rst | 10 +
chained_swapper/security/ir.model.access.csv | 7 +
chained_swapper/static/description/index.html | 462 ++++++++++++++++++
chained_swapper/tests/__init__.py | 3 +
chained_swapper/tests/test_chained_swapper.py | 73 +++
.../views/chained_swapper_views.xml | 123 +++++
chained_swapper/wizard/__init__.py | 3 +
.../wizard/chained_swapper_wizard.py | 140 ++++++
.../wizard/chained_swapper_wizard_views.xml | 23 +
21 files changed, 1760 insertions(+)
create mode 100644 chained_swapper/README.rst
create mode 100644 chained_swapper/__init__.py
create mode 100644 chained_swapper/__manifest__.py
create mode 100644 chained_swapper/demo/chained_swapper_demo.xml
create mode 100644 chained_swapper/hooks.py
create mode 100644 chained_swapper/i18n/chained_swapper.pot
create mode 100644 chained_swapper/i18n/es.po
create mode 100644 chained_swapper/models/__init__.py
create mode 100644 chained_swapper/models/chained_swapper.py
create mode 100644 chained_swapper/readme/CONFIGURE.rst
create mode 100644 chained_swapper/readme/CONTRIBUTORS.rst
create mode 100644 chained_swapper/readme/DESCRIPTION.rst
create mode 100644 chained_swapper/readme/USAGE.rst
create mode 100644 chained_swapper/security/ir.model.access.csv
create mode 100644 chained_swapper/static/description/index.html
create mode 100644 chained_swapper/tests/__init__.py
create mode 100644 chained_swapper/tests/test_chained_swapper.py
create mode 100644 chained_swapper/views/chained_swapper_views.xml
create mode 100644 chained_swapper/wizard/__init__.py
create mode 100644 chained_swapper/wizard/chained_swapper_wizard.py
create mode 100644 chained_swapper/wizard/chained_swapper_wizard_views.xml
diff --git a/chained_swapper/README.rst b/chained_swapper/README.rst
new file mode 100644
index 0000000000..562c949117
--- /dev/null
+++ b/chained_swapper/README.rst
@@ -0,0 +1,113 @@
+===============
+Chained Swapper
+===============
+
+.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--ux-lightgray.png?logo=github
+ :target: https://github.com/OCA/server-ux/tree/12.0/chained_swapper
+ :alt: OCA/server-ux
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/server-ux-12-0/server-ux-12-0-chained_swapper
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
+ :target: https://runbot.odoo-community.org/runbot/250/12.0
+ :alt: Try me on Runbot
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+With this module you can set the properly configuration
+to add/remove whenever you want a option in the top actions
+menu of any object. That action will launch a wizard that
+will allow you to set a value to a certain field in the
+selected objects and also set that value in other fields
+of other objects related with the original ones.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Configuration
+=============
+
+To configure this module, you need to:
+
+#. Go to *Setting > Fiend Swaps > Fiend Swaps*.
+#. Create a new object and set the following data as an example:
+
+ #. Set name 'Change Language'.
+ #. Select 'Contact' as a Model.
+ #. Select 'Language (res.partner)' as 'Field'.
+ #. Add a new 'Sub-field chain' with this value 'child_ids.lang'.
+ #. Add a new Constraint with this 'Expression':
+ ```bool(records.mapped('parent_id')```
+
+#. Click on 'Add action' smart button to add a new menu action in
+ 'res.partner' tree view named 'Change Language'.
+
+Usage
+=====
+
+To use this module, you need to:
+
+#. Follow the described steps in the configuration section.
+#. If 'Contact' app is installed, go to *Contacts > Contacts*.
+#. Open a parent company contact form view or select one or more
+ parent company contacts in the tree view.
+#. Go to action menu and click on *Change Language* option.
+#. A wizard will be launched allowing you to set a new language.
+#. Click on 'Change' button and the selected contacts will change
+ their language as well as their children.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Tecnativa
+
+Contributors
+~~~~~~~~~~~~
+
+* `Tecnativa `_:
+
+ * Ernesto Tejeda
+ * Pedro M. Baeza
+
+Maintainers
+~~~~~~~~~~~
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+This module is part of the `OCA/server-ux `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/chained_swapper/__init__.py b/chained_swapper/__init__.py
new file mode 100644
index 0000000000..b23af66646
--- /dev/null
+++ b/chained_swapper/__init__.py
@@ -0,0 +1,5 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from . import models
+from . import wizard
+from .hooks import uninstall_hook
diff --git a/chained_swapper/__manifest__.py b/chained_swapper/__manifest__.py
new file mode 100644
index 0000000000..a157bd894f
--- /dev/null
+++ b/chained_swapper/__manifest__.py
@@ -0,0 +1,21 @@
+# Copyright 2020 Tecnativa - Ernesto Tejeda
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+{
+ 'name': 'Chained Swapper',
+ 'summary': 'Chained Swapper',
+ 'version': '12.0.1.0.0',
+ 'author': 'Tecnativa, Odoo Community Association (OCA)',
+ 'category': 'Tools',
+ 'website': 'https://github.com/OCA/server-ux',
+ 'license': 'AGPL-3',
+ 'depends': ['base'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'views/chained_swapper_views.xml',
+ 'wizard/chained_swapper_wizard_views.xml',
+ ],
+ "demo": [
+ "demo/chained_swapper_demo.xml",
+ ],
+ 'uninstall_hook': 'uninstall_hook',
+}
diff --git a/chained_swapper/demo/chained_swapper_demo.xml b/chained_swapper/demo/chained_swapper_demo.xml
new file mode 100644
index 0000000000..7892924d6d
--- /dev/null
+++ b/chained_swapper/demo/chained_swapper_demo.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ Change language
+
+
+
+
+
+ child_ids.lang
+
+
+
+ Only parent company
+ bool(records.mapped('parent_id'))
+
+
+
+
+
+
+
diff --git a/chained_swapper/hooks.py b/chained_swapper/hooks.py
new file mode 100644
index 0000000000..9c8a9c55e4
--- /dev/null
+++ b/chained_swapper/hooks.py
@@ -0,0 +1,14 @@
+# Copyright 2020 Tecnativa - Ernesto Tejeda
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.api import Environment, SUPERUSER_ID
+
+
+def uninstall_hook(cr, registry):
+ """Delete the actions that were created with chained_swapper when
+ the module is uninstalled"""
+ env = Environment(cr, SUPERUSER_ID, {})
+ env['ir.actions.act_window'].search([
+ ('res_model', '=', 'chained.swapper.wizard')
+ ]).unlink()
+ return True
diff --git a/chained_swapper/i18n/chained_swapper.pot b/chained_swapper/i18n/chained_swapper.pot
new file mode 100644
index 0000000000..93a5c1859e
--- /dev/null
+++ b/chained_swapper/i18n/chained_swapper.pot
@@ -0,0 +1,250 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * chained_swapper
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-08-13 17:13+0000\n"
+"PO-Revision-Date: 2020-08-13 17:13+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:138
+#, python-format
+msgid "Chained swap done: %s: %s > %s"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__ref_ir_act_window_id
+msgid "Action"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper__ref_ir_act_window_id
+msgid "Action to make this template available on records of the related document model."
+msgstr ""
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Add Action"
+msgstr ""
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Add a new contextual action of related documents to open a composition wizard"
+msgstr ""
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Advanced"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__allowed_field_ids
+msgid "Allowed Field"
+msgstr ""
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:158
+#, python-format
+msgid "At least one sub-field is no compatible with the main field."
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_constraint__expression
+msgid "Boolean python expression. You can use the key word 'records' as the records selected to execute the contextual action. Ex.: bool(records.mapped('parent_id'))"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__chained_swapper_id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__chained_swapper_id
+msgid "Chained Swapper"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper_constraint
+msgid "Chained Swapper Constraint"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper_sub_field
+msgid "Chained Swapper Sub-field"
+msgstr ""
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
+msgid "Change"
+msgstr ""
+
+#. module: chained_swapper
+#: model:chained.swapper,name:chained_swapper.chained_swapper_demo
+#: model:ir.actions.act_window,name:chained_swapper.partner_chained_swap_lang_action_demo
+msgid "Change language"
+msgstr ""
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
+msgid "Close"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__expression
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_constraint_view_form
+msgid "Constraint expression"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__constraint_ids
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Constraints"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__create_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__create_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__create_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__create_uid
+msgid "Created by"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__create_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__create_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__create_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__create_date
+msgid "Created on"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__display_name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__display_name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__display_name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__field_id
+msgid "Field"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.actions.act_window,name:chained_swapper.chained_swapper_action
+#: model:ir.ui.menu,name:chained_swapper.chained_swapper_menu
+#: model:ir.ui.menu,name:chained_swapper.chained_swapper_submenu
+msgid "Field Swaps"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__group_ids
+msgid "Groups"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__id
+msgid "ID"
+msgstr ""
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:148
+#, python-format
+msgid "Incorrect sub-field expression."
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper____last_update
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint____last_update
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field____last_update
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__write_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__write_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__write_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__write_uid
+msgid "Last Updated by"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__write_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__write_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__write_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__write_date
+msgid "Last Updated on"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__model_id
+msgid "Model"
+msgstr ""
+
+#. module: chained_swapper
+#: sql_constraint:chained.swapper:0
+msgid "Model and Field must be unique!"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper__model_id
+msgid "Model is used for Selecting Field. This is editable until Contextual Action is not created."
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__name
+msgid "Name"
+msgstr ""
+
+#. module: chained_swapper
+#: model:chained.swapper.constraint,name:chained_swapper.chained_swapper_constraint_demo
+msgid "Only parent company"
+msgstr ""
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Remove Action"
+msgstr ""
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Remove the contextual action to use this template on related documents"
+msgstr ""
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Security"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__sub_field_chain
+msgid "Sub Field Chain"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__sub_field_ids
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Sub-fields"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper_wizard
+msgid "Wizard chained swapper"
+msgstr ""
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_sub_field__sub_field_chain
+msgid "You can specify here a field of related fields as dotted names. Ex.: 'child_ids.lang'."
+msgstr ""
+
diff --git a/chained_swapper/i18n/es.po b/chained_swapper/i18n/es.po
new file mode 100644
index 0000000000..2d0f634f79
--- /dev/null
+++ b/chained_swapper/i18n/es.po
@@ -0,0 +1,274 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * chained_swapper
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 12.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-08-13 17:13+0000\n"
+"PO-Revision-Date: 2020-08-13 13:14-0400\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: \n"
+"X-Generator: Poedit 2.0.6\n"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:138
+#, python-format
+msgid "Chained swap done: %s: %s > %s"
+msgstr "Intercambio encadenado realizado: %s: %s > %s"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__ref_ir_act_window_id
+msgid "Action"
+msgstr "Acción"
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper__ref_ir_act_window_id
+msgid ""
+"Action to make this template available on records of the related document "
+"model."
+msgstr ""
+"Acción contextual para hacer que esta plantilla esté disponible en los "
+"registros del modelo de documento relacionado."
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Add Action"
+msgstr "Añadir acción"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid ""
+"Add a new contextual action of related documents to open a composition wizard"
+msgstr ""
+"Añade una acción contextual para los documentos relacionados para abrir un "
+"asistente de intercambio encadenado"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Advanced"
+msgstr "Avanzado"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__allowed_field_ids
+msgid "Allowed Field"
+msgstr "Campos permitidos"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:158
+#, python-format
+msgid "At least one sub-field is no compatible with the main field."
+msgstr "Al menos un sub-campo no es compatible con el campo principal."
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_constraint__expression
+msgid ""
+"Boolean python expression. You can use the key word 'records' as the records "
+"selected to execute the contextual action. Ex.: bool(records."
+"mapped('parent_id'))"
+msgstr ""
+"Expresión Python booleana. Puede usar la palabra clave 'records' para hacer "
+"referencia a los registros seleccionado al ejecutar la acción contextual. "
+"Ej.: bool(records.mapped('parent_id'))"
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__chained_swapper_id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__chained_swapper_id
+msgid "Chained Swapper"
+msgstr "Intercambiador encadenado"
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper_constraint
+msgid "Chained Swapper Constraint"
+msgstr "Intercambiador encadenado - Restricciones"
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper_sub_field
+msgid "Chained Swapper Sub-field"
+msgstr "Intercambiador encadenado - Sub-campos"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
+msgid "Change"
+msgstr "Cambiar"
+
+#. module: chained_swapper
+#: model:chained.swapper,name:chained_swapper.chained_swapper_demo
+#: model:ir.actions.act_window,name:chained_swapper.partner_chained_swap_lang_action_demo
+msgid "Change language"
+msgstr "Cambiar idioma"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
+msgid "Close"
+msgstr "Cerrar"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__expression
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_constraint_view_form
+msgid "Constraint expression"
+msgstr "Expresión de restricción"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__constraint_ids
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Constraints"
+msgstr "Restricción"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__create_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__create_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__create_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__create_uid
+msgid "Created by"
+msgstr "Creado por"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__create_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__create_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__create_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__create_date
+msgid "Created on"
+msgstr "Creado el"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__display_name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__display_name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__display_name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__display_name
+msgid "Display Name"
+msgstr "Nombre mostrado"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__field_id
+msgid "Field"
+msgstr "Campo"
+
+#. module: chained_swapper
+#: model:ir.actions.act_window,name:chained_swapper.chained_swapper_action
+#: model:ir.ui.menu,name:chained_swapper.chained_swapper_menu
+#: model:ir.ui.menu,name:chained_swapper.chained_swapper_submenu
+msgid "Field Swaps"
+msgstr "Intercambios de campo"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__group_ids
+msgid "Groups"
+msgstr "Grupos"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__id
+msgid "ID"
+msgstr ""
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:148
+#, python-format
+msgid "Incorrect sub-field expression."
+msgstr "Expresión de sub-campo incorrecta."
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper____last_update
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint____last_update
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field____last_update
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard____last_update
+msgid "Last Modified on"
+msgstr "Última modificación en"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__write_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__write_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__write_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__write_uid
+msgid "Last Updated by"
+msgstr "Última actualización por"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__write_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__write_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__write_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__write_date
+msgid "Last Updated on"
+msgstr "Última actualización el"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__model_id
+msgid "Model"
+msgstr "Modelo"
+
+#. module: chained_swapper
+#: sql_constraint:chained.swapper:0
+msgid "Model and Field must be unique!"
+msgstr "La combinación Modelo-Campo debe ser única!"
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper__model_id
+msgid ""
+"Model is used for Selecting Field. This is editable until Contextual Action "
+"is not created."
+msgstr ""
+"El modelo se utiliza para seleccionar el campo. Este es editable mientras la "
+"acción contextual no esté creada."
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__name
+msgid "Name"
+msgstr "Nombre"
+
+#. module: chained_swapper
+#: model:chained.swapper.constraint,name:chained_swapper.chained_swapper_constraint_demo
+msgid "Only parent company"
+msgstr "Solo compañías padres"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Remove Action"
+msgstr "Eliminar acción"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Remove the contextual action to use this template on related documents"
+msgstr ""
+"Borrar la acción conceptual para usar esta plantilla en documentos "
+"relacionados"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Security"
+msgstr "Seguridad"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__sub_field_chain
+msgid "Sub Field Chain"
+msgstr "Cadena de sub-campo"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__sub_field_ids
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Sub-fields"
+msgstr "Sub-campo"
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper_wizard
+msgid "Wizard chained swapper"
+msgstr "Asistente de intercambiador encadenado"
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_sub_field__sub_field_chain
+msgid ""
+"You can specify here a field of related fields as dotted names. Ex.: "
+"'child_ids.lang'."
+msgstr ""
+"Puede especificar aquí un campo de campos relacionados como una cadena de "
+"nombres separada por puntos. Ej.: 'child_ids.lang'."
diff --git a/chained_swapper/models/__init__.py b/chained_swapper/models/__init__.py
new file mode 100644
index 0000000000..10e942d94e
--- /dev/null
+++ b/chained_swapper/models/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from . import chained_swapper
diff --git a/chained_swapper/models/chained_swapper.py b/chained_swapper/models/chained_swapper.py
new file mode 100644
index 0000000000..61461ce8cb
--- /dev/null
+++ b/chained_swapper/models/chained_swapper.py
@@ -0,0 +1,180 @@
+# Copyright 2020 Tecnativa - Ernesto Tejeda
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo import api, exceptions, fields, models, _
+
+
+class ChainedSwapper(models.Model):
+ _name = "chained.swapper"
+ _description = "Chained Swapper"
+
+ name = fields.Char(
+ required=True,
+ translate=True,
+ index=1,
+ )
+ model_id = fields.Many2one(
+ comodel_name='ir.model',
+ required=True,
+ help="Model is used for Selecting Field. This is editable "
+ "until Contextual Action is not created.",
+ )
+ allowed_field_ids = fields.Many2many(
+ comodel_name='ir.model.fields',
+ compute='_compute_allowed_field_ids'
+ )
+ field_id = fields.Many2one(
+ comodel_name='ir.model.fields',
+ required=True,
+ domain="[('id', 'in', allowed_field_ids)]",
+ )
+ sub_field_ids = fields.One2many(
+ comodel_name='chained.swapper.sub.field',
+ inverse_name='chained_swapper_id',
+ string='Sub-fields',
+ )
+ constraint_ids = fields.One2many(
+ comodel_name='chained.swapper.constraint',
+ inverse_name='chained_swapper_id',
+ string='Constraints',
+ )
+ ref_ir_act_window_id = fields.Many2one(
+ comodel_name='ir.actions.act_window',
+ string='Action',
+ readonly=True,
+ help="Action to make this template available on records "
+ "of the related document model.",
+ )
+ group_ids = fields.Many2many(
+ comodel_name="res.groups",
+ relation="mass_group_rel",
+ column1="mass_id",
+ column2="group_id",
+ string="Groups",
+ )
+
+ _sql_constraints = [
+ ('model_id_field_id_unique', 'unique (model_id, field_id)',
+ 'Model and Field must be unique!'),
+ ]
+
+ @api.depends('model_id')
+ def _compute_allowed_field_ids(self):
+ model_obj = self.env['ir.model']
+ field_obj = self.env['ir.model.fields']
+ for record in self:
+ allowed_field_ids = False
+ if record.model_id:
+ all_models = record.model_id
+ active_model_obj = self.env[record.model_id.model]
+ if active_model_obj._inherits:
+ keys = list(active_model_obj._inherits.keys())
+ all_models |= model_obj.search([('model', 'in', keys)])
+ allowed_field_ids = field_obj.search([
+ ('ttype', 'not in', ['reference', 'function', 'one2many']),
+ ('model_id', 'in', all_models.ids),
+ ])
+ record.allowed_field_ids = allowed_field_ids
+
+ @api.constrains('model_id', 'field_id')
+ def _check_sub_field_ids(self):
+ self.mapped('sub_field_ids')._check_sub_field_chain()
+
+ @api.onchange('model_id')
+ def _onchange_model_id(self):
+ self.field_id = False
+
+ def write(self, vals):
+ super().write(vals)
+ if 'name' in vals:
+ self.ref_ir_act_window_id.name = self.name
+
+ @api.multi
+ def unlink(self):
+ self.unlink_action()
+ return super().unlink()
+
+ @api.multi
+ def add_action(self):
+ self.ensure_one()
+ action = self.env['ir.actions.act_window'].create({
+ 'name': self.name,
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'chained.swapper.wizard',
+ 'src_model': self.model_id.model,
+ 'groups_id': [(4, x.id) for x in self.group_ids],
+ 'view_type': 'form',
+ 'context': "{'chained_swapper_id': %d}" % (self.id),
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'binding_model_id': self.model_id.id,
+ 'binding_type': 'action',
+ })
+ self.write({'ref_ir_act_window_id': action.id})
+ return True
+
+ @api.multi
+ def unlink_action(self):
+ self.mapped('ref_ir_act_window_id').unlink()
+ return True
+
+
+class ChainedSwapperSubField(models.Model):
+ _name = "chained.swapper.sub.field"
+ _description = "Chained Swapper Sub-field"
+
+ chained_swapper_id = fields.Many2one(
+ comodel_name='chained.swapper'
+ )
+ sub_field_chain = fields.Char(
+ required=True,
+ help="You can specify here a field of related fields as "
+ "dotted names. Ex.: 'child_ids.lang'."
+ )
+
+ @api.constrains('chained_swapper_id', 'sub_field_chain')
+ def _check_sub_field_chain(self):
+ for rec in self:
+ # Check sub-field exist
+ try:
+ chain_list = rec.sub_field_chain.split('.')
+ chain_field_name = chain_list.pop()
+ chain_model = self.env[rec.chained_swapper_id.model_id.model]
+ for name in chain_list:
+ chain_model = chain_model[name]
+ chain_model[chain_field_name]
+ except KeyError:
+ raise exceptions.ValidationError(
+ _("Incorrect sub-field expression."))
+ # Check sub-field and original field are the same type
+ swap_field = rec.chained_swapper_id.field_id
+ chain_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', rec.chained_swapper_id.model_id.id),
+ ('name', '=', chain_field_name),
+ ])
+ if (chain_field.ttype != swap_field.ttype or
+ chain_field.relation != swap_field.relation):
+ raise exceptions.ValidationError(
+ _("At least one sub-field is no compatible with "
+ "the main field."))
+
+
+class ChainedSwapperConstraint(models.Model):
+ _name = "chained.swapper.constraint"
+ _description = "Chained Swapper Constraint"
+
+ chained_swapper_id = fields.Many2one(
+ comodel_name='chained.swapper'
+ )
+ name = fields.Char(
+ required=True,
+ translate=True,
+ )
+ expression = fields.Text(
+ string='Constraint expression',
+ required=True,
+ help="Boolean python expression. You can use the key word "
+ "'records' as the records selected to execute the "
+ "contextual action. Ex.: bool(records.mapped('parent_id'))",
+ default="return False",
+ )
diff --git a/chained_swapper/readme/CONFIGURE.rst b/chained_swapper/readme/CONFIGURE.rst
new file mode 100644
index 0000000000..41b8afb7fd
--- /dev/null
+++ b/chained_swapper/readme/CONFIGURE.rst
@@ -0,0 +1,14 @@
+To configure this module, you need to:
+
+#. Go to *Setting > Fiend Swaps > Fiend Swaps*.
+#. Create a new object and set the following data as an example:
+
+ #. Set name 'Change Language'.
+ #. Select 'Contact' as a Model.
+ #. Select 'Language (res.partner)' as 'Field'.
+ #. Add a new 'Sub-field chain' with this value 'child_ids.lang'.
+ #. Add a new Constraint with this 'Expression':
+ ```bool(records.mapped('parent_id')```
+
+#. Click on 'Add action' smart button to add a new menu action in
+ 'res.partner' tree view named 'Change Language'.
diff --git a/chained_swapper/readme/CONTRIBUTORS.rst b/chained_swapper/readme/CONTRIBUTORS.rst
new file mode 100644
index 0000000000..b31cef3217
--- /dev/null
+++ b/chained_swapper/readme/CONTRIBUTORS.rst
@@ -0,0 +1,4 @@
+* `Tecnativa `_:
+
+ * Ernesto Tejeda
+ * Pedro M. Baeza
diff --git a/chained_swapper/readme/DESCRIPTION.rst b/chained_swapper/readme/DESCRIPTION.rst
new file mode 100644
index 0000000000..7829459e35
--- /dev/null
+++ b/chained_swapper/readme/DESCRIPTION.rst
@@ -0,0 +1,6 @@
+With this module you can set the properly configuration
+to add/remove whenever you want a option in the top actions
+menu of any object. That action will launch a wizard that
+will allow you to set a value to a certain field in the
+selected objects and also set that value in other fields
+of other objects related with the original ones.
diff --git a/chained_swapper/readme/USAGE.rst b/chained_swapper/readme/USAGE.rst
new file mode 100644
index 0000000000..3d94a95545
--- /dev/null
+++ b/chained_swapper/readme/USAGE.rst
@@ -0,0 +1,10 @@
+To use this module, you need to:
+
+#. Follow the described steps in the configuration section.
+#. If 'Contact' app is installed, go to *Contacts > Contacts*.
+#. Open a parent company contact form view or select one or more
+ parent company contacts in the tree view.
+#. Go to action menu and click on *Change Language* option.
+#. A wizard will be launched allowing you to set a new language.
+#. Click on 'Change' button and the selected contacts will change
+ their language as well as their children.
diff --git a/chained_swapper/security/ir.model.access.csv b/chained_swapper/security/ir.model.access.csv
new file mode 100644
index 0000000000..5f6c72b95b
--- /dev/null
+++ b/chained_swapper/security/ir.model.access.csv
@@ -0,0 +1,7 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_chained_swapper_user,chained.swapper.user,model_chained_swapper,base.group_user,1,0,0,0
+access_chained_swapper_erp_manager,chained.swapper.erp_manager,model_chained_swapper,base.group_erp_manager,1,1,1,1
+access_chained_swapper_sub_field_user,chained.swapper.sub.field.user,model_chained_swapper_sub_field,base.group_user,1,0,0,0
+access_chained_swapper_sub_field_erp_manager,chained.swapper.sub.field.erp_manager,model_chained_swapper_sub_field,base.group_erp_manager,1,1,1,1
+access_chained_swapper_constraint_user,chained.swapper.constraint.user,model_chained_swapper_constraint,base.group_user,1,0,0,0
+access_chained_swapper_constraint_erp_manager,chained.swapper.constraint.erp_manager,model_chained_swapper_constraint,base.group_erp_manager,1,1,1,1
diff --git a/chained_swapper/static/description/index.html b/chained_swapper/static/description/index.html
new file mode 100644
index 0000000000..1362634d22
--- /dev/null
+++ b/chained_swapper/static/description/index.html
@@ -0,0 +1,462 @@
+
+
+
+
+
+
+Chained Swapper
+
+
+
+
+
Chained Swapper
+
+
+
+
With this module you can set the properly configuration
+to add/remove whenever you want a option in the top actions
+menu of any object. That action will launch a wizard that
+will allow you to set a value to a certain field in the
+selected objects and also set that value in other fields
+of other objects related with the original ones.
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
This module is part of the OCA/server-ux project on GitHub.
+
+
diff --git a/chained_swapper/tests/__init__.py b/chained_swapper/tests/__init__.py
new file mode 100644
index 0000000000..ccd7e321fe
--- /dev/null
+++ b/chained_swapper/tests/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from . import test_chained_swapper
diff --git a/chained_swapper/tests/test_chained_swapper.py b/chained_swapper/tests/test_chained_swapper.py
new file mode 100644
index 0000000000..1eb79e8f99
--- /dev/null
+++ b/chained_swapper/tests/test_chained_swapper.py
@@ -0,0 +1,73 @@
+# Copyright 2020 Tecnativa - Ernesto Tejeda
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.tests import common
+from odoo.modules import registry
+from ..hooks import uninstall_hook
+
+
+class TestChainedSwapper(common.SavepointCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestChainedSwapper, cls).setUpClass()
+ cls.env['res.lang'].load_lang('es_ES')
+ res_partner = cls.env['res.partner']
+ cls.partner_parent = res_partner.create(
+ {'name': 'parent partner cs', 'lang': 'en_US'})
+ cls.partner_child_1 = res_partner.create(
+ {'name': 'partner child1 cs', 'parent_id': cls.partner_parent.id})
+ cls.partner_child_2 = res_partner.create(
+ {'name': 'partner child2 cs', 'parent_id': cls.partner_parent.id})
+ cls.chained_swapper = cls.env.ref(
+ "chained_swapper.chained_swapper_demo")
+
+ def test_create_unlink_action(self):
+ """Test if Sidebar Action is added / removed to / from give object."""
+ action = self.chained_swapper.ref_ir_act_window_id\
+ and self.chained_swapper.ref_ir_act_window_id.binding_model_id
+ self.assertTrue(action)
+ # Remove the action
+ self.chained_swapper.unlink_action()
+ action = self.chained_swapper.ref_ir_act_window_id
+ self.assertFalse(action)
+ # Add an action
+ self.chained_swapper.add_action()
+ action = self.chained_swapper.ref_ir_act_window_id \
+ and self.chained_swapper.ref_ir_act_window_id.binding_model_id
+ self.assertTrue(action)
+
+ def test_unlink_chained_swapper(self):
+ """Test if related actions are removed when a chained swapper
+ record is unlinked."""
+ action_id = self.chained_swapper.ref_ir_act_window_id.id
+ self.chained_swapper.unlink()
+ action = self.env['ir.actions.act_window'].search([
+ ('id', '=', action_id)])
+ self.assertFalse(action)
+
+ def test_change_constrained_partner_language(self):
+ self.env['chained.swapper.wizard'].with_context(
+ active_model='res.partner',
+ active_id=self.partner_parent.id,
+ active_ids=(self.partner_parent | self.partner_child_1).ids,
+ chained_swapper_id=self.chained_swapper.id
+ ).create({'lang': 'es_ES'})
+
+ def test_change_partner_language(self):
+ all_partner = (
+ self.partner_parent | self.partner_child_1 | self.partner_child_2)
+ self.assertEqual(all_partner.mapped('lang'), ['en_US']*3)
+ self.env['chained.swapper.wizard'].with_context(
+ active_model='res.partner',
+ active_id=self.partner_parent.id,
+ active_ids=[self.partner_parent.id],
+ chained_swapper_id=self.chained_swapper.id
+ ).create({'lang': 'es_ES'})
+ self.assertEqual(all_partner.mapped('lang'), ['es_ES']*3)
+
+ def _test_uninstall_hook(self):
+ """Test if related actions are removed when mass editing
+ record is uninstalled."""
+ action_id = self.chained_swapper.ref_ir_act_window_id.id
+ uninstall_hook(self.cr, registry)
+ self.assertFalse(self.env['ir.actions.act_window'].browse(action_id))
diff --git a/chained_swapper/views/chained_swapper_views.xml b/chained_swapper/views/chained_swapper_views.xml
new file mode 100644
index 0000000000..9f5d1d3f55
--- /dev/null
+++ b/chained_swapper/views/chained_swapper_views.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+ chained.swapper.form
+ chained.swapper
+
+
+
+
+
+ chained.swapper.tree
+ chained.swapper
+
+
+
+
+
+
+
+
+ Field Swaps
+ chained.swapper
+ form
+ tree,form
+
+
+
+ chained.swapper.sub.field.tree
+ chained.swapper.sub.field
+
+
+
+
+
+
+
+
+ chained.swapper.constraint.tree
+ chained.swapper.constraint
+
+
+
+
+
+
+
+ chained.swapper.constraint.form
+ chained.swapper.constraint
+
+
+
+
+
+
+
+
+
diff --git a/chained_swapper/wizard/__init__.py b/chained_swapper/wizard/__init__.py
new file mode 100644
index 0000000000..f841eb5e04
--- /dev/null
+++ b/chained_swapper/wizard/__init__.py
@@ -0,0 +1,3 @@
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from . import chained_swapper_wizard
diff --git a/chained_swapper/wizard/chained_swapper_wizard.py b/chained_swapper/wizard/chained_swapper_wizard.py
new file mode 100644
index 0000000000..6d8fe22d32
--- /dev/null
+++ b/chained_swapper/wizard/chained_swapper_wizard.py
@@ -0,0 +1,140 @@
+# Copyright 2020 Tecnativa - Ernesto Tejeda
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from lxml import etree
+
+from odoo.tools.safe_eval import safe_eval
+from odoo import _, api, models
+from odoo.exceptions import UserError
+
+
+class ChainedSwapperWizard(models.TransientModel):
+ _name = 'chained.swapper.wizard'
+ _description = "Wizard chained swapper"
+
+ @api.model
+ def default_get(self, fields):
+ UserError("lolo")
+ context = self.env.context
+ if context.get('chained_swapper_id'):
+ model = self.env[context.get('active_model')].browse(
+ context.get('active_ids'))
+ chained_swapper = self.env['chained.swapper'].browse(
+ context.get('chained_swapper_id'))
+ for constraint in chained_swapper.constraint_ids:
+ expr = constraint.expression
+ if safe_eval(expr, {'records': model}):
+ UserError(constraint.name)
+ return super().default_get(fields)
+
+ @api.model
+ def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
+ submenu=False):
+ """ As we don't have any field in this model, result['fields']
+ and result['arch'] are modified to add dynamically the
+ corresponding field.
+ """
+ res = super().fields_view_get(
+ view_id=view_id, view_type=view_type, toolbar=toolbar,
+ submenu=submenu)
+ if not self.env.context.get('chained_swapper_id'):
+ return res
+ chained_swapper = self.env['chained.swapper'].browse(
+ self.env.context.get('chained_swapper_id'))
+ model_obj = self.env[self.env.context.get('active_model')]
+ field_info = model_obj.fields_get()
+ field = chained_swapper.field_id
+ # Fields dict
+ all_fields = {
+ field.name: {
+ 'type': field.ttype,
+ 'string': field.field_description,
+ }
+ }
+ if field.ttype in ["many2many", "many2one"]:
+ all_fields[field.name]['relation'] = field.relation
+ elif field.ttype == 'selection':
+ field_selection = field_info[field.name]['selection']
+ all_fields[field.name]['selection'] = field_selection
+ # XML view definition
+ doc = etree.XML(res['arch'])
+ group_node = doc.xpath("//group[@name='swap_field_group']")[0]
+ etree.SubElement(
+ group_node, 'field', {'name': field.name, 'colspan': '4'})
+ if field.ttype in ["one2many", "many2many", "text"]:
+ group_node.set('string', field.field_description)
+ group_node.set('nolabel', '1')
+ res.update(
+ arch=etree.tostring(doc, encoding='unicode'), fields=all_fields)
+ return res
+
+ @api.model
+ def create(self, vals):
+ """ 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.
+ """
+ model_obj = self.env[self.env.context.get('active_model')]
+ context = self.env.context
+ field_name, new_value = list(vals.items())[0]
+ # write the active model
+ model = model_obj.browse(self.env.context.get('active_ids'))
+ original_values = {m.id: m[field_name] for m in model}
+ model.write(vals)
+ if hasattr(model, 'message_post'):
+ self.post_chained_swap(
+ model, field_name, original_values, new_value)
+ # write chained models
+ chained_swapper_obj = self.env['chained.swapper']
+ chained_swapper = chained_swapper_obj.browse(
+ context.get('chained_swapper_id'))
+ for sub_field in chained_swapper.sub_field_ids:
+ chain_fields = sub_field.sub_field_chain.split('.')
+ field_name = chain_fields.pop()
+ chain_model = model
+ for chain_field in chain_fields:
+ chain_model = chain_model.mapped(chain_field)
+ original_values = {cm.id: cm[field_name] for cm in chain_model}
+ chain_model.write({field_name: new_value})
+ # post swap
+ if hasattr(chain_model, 'message_post'):
+ self.post_chained_swap(
+ chain_model, field_name, original_values, new_value)
+ return super().create({})
+
+ @api.multi
+ def change_action(self):
+ return {'type': 'ir.actions.act_window_close'}
+
+ @api.model
+ def post_chained_swap(self, model, field_name, original_values, new_value):
+ def human_readable_field(value):
+ result = value
+ field_def = model._fields[field_name]
+ if field_def.type == "selection":
+ if type(field_def.selection) == list:
+ selection = field_def.selection
+ else:
+ selection = field_def.selection(self)
+ for selection_item in selection:
+ if selection_item[0] == value:
+ result = selection_item[1]
+ break
+ elif field_def.type == "many2one":
+ if type(value) == int:
+ result = self.env[field_def.comodel_name].browse(value)
+ result = result.display_name
+ elif field_def.type == "many2many":
+ if type(value) == list:
+ ids = value[0][2]
+ value = self.env[field_def.comodel_name].browse(ids)
+ result = str(value.mapped('display_name'))
+ return result
+ field_desc = model._fields[field_name].string
+ new_value = human_readable_field(new_value)
+ for m in model:
+ original_value = human_readable_field(original_values[m.id])
+ m.message_post(
+ body=_("Chained swap done: %s: %s > %s") % (
+ field_desc, original_value, new_value)
+ )
diff --git a/chained_swapper/wizard/chained_swapper_wizard_views.xml b/chained_swapper/wizard/chained_swapper_wizard_views.xml
new file mode 100644
index 0000000000..fbbcbec512
--- /dev/null
+++ b/chained_swapper/wizard/chained_swapper_wizard_views.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ chained.swapper.wizard.form
+ chained.swapper.wizard
+
+
+
+
+
From f673ec39e64786168afb5d35b4c2ba7e8894369a Mon Sep 17 00:00:00 2001
From: "Pedro M. Baeza"
Date: Fri, 14 Aug 2020 12:33:40 +0200
Subject: [PATCH 02/18] [FIX+IMP] chained_swapper: Make constraints to work +
refinements
---
chained_swapper/README.rst | 70 +++++++++++------
chained_swapper/demo/chained_swapper_demo.xml | 11 +--
chained_swapper/i18n/chained_swapper.pot | 54 +++++++++----
chained_swapper/i18n/es.po | 70 +++++++++++------
chained_swapper/models/chained_swapper.py | 32 +++++---
chained_swapper/readme/CONFIGURE.rst | 33 +++++---
chained_swapper/readme/DESCRIPTION.rst | 19 +++--
chained_swapper/readme/USAGE.rst | 18 +++--
chained_swapper/static/description/index.html | 78 ++++++++++++-------
chained_swapper/tests/test_chained_swapper.py | 53 +++++++++----
.../views/chained_swapper_views.xml | 8 +-
.../wizard/chained_swapper_wizard.py | 22 ++++--
12 files changed, 318 insertions(+), 150 deletions(-)
diff --git a/chained_swapper/README.rst b/chained_swapper/README.rst
index 562c949117..3880b866d8 100644
--- a/chained_swapper/README.rst
+++ b/chained_swapper/README.rst
@@ -25,12 +25,19 @@ Chained Swapper
|badge1| |badge2| |badge3| |badge4| |badge5|
-With this module you can set the properly configuration
-to add/remove whenever you want a option in the top actions
-menu of any object. That action will launch a wizard that
-will allow you to set a value to a certain field in the
-selected objects and also set that value in other fields
-of other objects related with the original ones.
+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 .
**Table of contents**
@@ -42,32 +49,49 @@ Configuration
To configure this module, you need to:
-#. Go to *Setting > Fiend Swaps > Fiend Swaps*.
+#. Go to *Setting > Field Swaps > Field Swaps*.
#. Create a new object and set the following data as an example:
- #. Set name 'Change Language'.
- #. Select 'Contact' as a Model.
- #. Select 'Language (res.partner)' as 'Field'.
- #. Add a new 'Sub-field chain' with this value 'child_ids.lang'.
- #. Add a new Constraint with this 'Expression':
- ```bool(records.mapped('parent_id')```
+ * Name for identifying it and use it for the action name.
+ * Select the source model where the swap will be started.
+ * Select the starting field for which the swap will be done.
+ * Add several chained fields. They are expressed as a string using
+ dot notation, taking the source model as beginning for looking there
+ the first field, and continuing from there drilling through. Example:
+ `picking_ids.partner_id` for `sale.order` model will go to the linked
+ deliveries orders, and change the customer there.
+ * Add possible constraints for restricting the chained swap. They are
+ Python expressions that must be one line that is evaluated as boolean.
+ If the evaluation is true, then a message will be thrown and no swap
+ will be allowed. You can use the variable `records` in your code, that
+ will be referring the selected records for doing the swap. Example: for
+ restricting sales orders that have a delivery order validated:
+
+ `any(p.state == 'done' for p in records.mapped('picking_ids.state'))`
+
+ Each constraint has a name for identifying it, but also for showing that
+ name when displaying the error trying to do the swap.
+
+#. Click on 'Add action' smart button to add a new action in the source model.
-#. Click on 'Add action' smart button to add a new menu action in
- 'res.partner' tree view named 'Change Language'.
+On demo databases, you can check the example "Language", that changes the
+language of a contact, and propagate it to children contacts.
Usage
=====
To use this module, you need to:
-#. Follow the described steps in the configuration section.
-#. If 'Contact' app is installed, go to *Contacts > Contacts*.
-#. Open a parent company contact form view or select one or more
- parent company contacts in the tree view.
-#. Go to action menu and click on *Change Language* option.
-#. A wizard will be launched allowing you to set a new language.
-#. Click on 'Change' button and the selected contacts will change
- their language as well as their children.
+#. Go to the source document in list mode.
+#. Select one or several records.
+#. Click on Action and locate the option "Chained swap: ".
+#. If one of the selected records doesn't comply with one of the constraints,
+ a message will be shown, and the swap won't continue.
+#. If everything is OK, a popup will arise, and you will see a field for
+ filling the new value.
+#. Click on "Change", and the swap will be done.
+#. On the chatter of the source document, an entry will be logged for
+ reflecting the done swap.
Bug Tracker
===========
diff --git a/chained_swapper/demo/chained_swapper_demo.xml b/chained_swapper/demo/chained_swapper_demo.xml
index 7892924d6d..cdd92edfb3 100644
--- a/chained_swapper/demo/chained_swapper_demo.xml
+++ b/chained_swapper/demo/chained_swapper_demo.xml
@@ -1,14 +1,15 @@
-
+
- Change language
+ Language
-
+ child_ids.lang
@@ -19,14 +20,14 @@
-
+
diff --git a/chained_swapper/i18n/chained_swapper.pot b/chained_swapper/i18n/chained_swapper.pot
index 93a5c1859e..01182aff12 100644
--- a/chained_swapper/i18n/chained_swapper.pot
+++ b/chained_swapper/i18n/chained_swapper.pot
@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-08-13 17:13+0000\n"
-"PO-Revision-Date: 2020-08-13 17:13+0000\n"
+"POT-Creation-Date: 2020-08-14 10:32+0000\n"
+"PO-Revision-Date: 2020-08-14 10:32+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -18,7 +18,7 @@ msgstr ""
#. module: chained_swapper
#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:138
#, python-format
-msgid "Chained swap done: %s: %s > %s"
+msgid "Chained swap done:"
msgstr ""
#. module: chained_swapper
@@ -51,15 +51,9 @@ msgstr ""
msgid "Allowed Field"
msgstr ""
-#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:158
-#, python-format
-msgid "At least one sub-field is no compatible with the main field."
-msgstr ""
-
#. module: chained_swapper
#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_constraint__expression
-msgid "Boolean python expression. You can use the key word 'records' as the records selected to execute the contextual action. Ex.: bool(records.mapped('parent_id'))"
+msgid "Boolean python expression. You can use the keyword 'records' as the records selected to execute the contextual action. Ex.: bool(records.mapped('parent_id'))"
msgstr ""
#. module: chained_swapper
@@ -80,14 +74,19 @@ msgid "Chained Swapper Sub-field"
msgstr ""
#. module: chained_swapper
-#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
-msgid "Change"
+#: code:addons/chained_swapper/models/chained_swapper.py:103
+#, python-format
+msgid "Chained swap"
msgstr ""
#. module: chained_swapper
-#: model:chained.swapper,name:chained_swapper.chained_swapper_demo
#: model:ir.actions.act_window,name:chained_swapper.partner_chained_swap_lang_action_demo
-msgid "Change language"
+msgid "Chained swap: Language"
+msgstr ""
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
+msgid "Change"
msgstr ""
#. module: chained_swapper
@@ -157,9 +156,20 @@ msgid "ID"
msgstr ""
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:148
+#: code:addons/chained_swapper/models/chained_swapper.py:150
#, python-format
-msgid "Incorrect sub-field expression."
+msgid "Incorrect sub-field expression:"
+msgstr ""
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:192
+#, python-format
+msgid "Invalid constraint expression: "
+msgstr ""
+
+#. module: chained_swapper
+#: model:chained.swapper,name:chained_swapper.chained_swapper_demo
+msgid "Language"
msgstr ""
#. module: chained_swapper
@@ -207,6 +217,12 @@ msgstr ""
msgid "Name"
msgstr ""
+#. module: chained_swapper
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:25
+#, python-format
+msgid "Not possible to swap the field due to the constraint"
+msgstr ""
+
#. module: chained_swapper
#: model:chained.swapper.constraint,name:chained_swapper.chained_swapper_constraint_demo
msgid "Only parent company"
@@ -238,6 +254,12 @@ msgstr ""
msgid "Sub-fields"
msgstr ""
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:161
+#, python-format
+msgid "The sub-field '%s' is not compatible with the main field."
+msgstr ""
+
#. module: chained_swapper
#: model:ir.model,name:chained_swapper.model_chained_swapper_wizard
msgid "Wizard chained swapper"
diff --git a/chained_swapper/i18n/es.po b/chained_swapper/i18n/es.po
index 2d0f634f79..32e43fddce 100644
--- a/chained_swapper/i18n/es.po
+++ b/chained_swapper/i18n/es.po
@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-08-13 17:13+0000\n"
-"PO-Revision-Date: 2020-08-13 13:14-0400\n"
+"POT-Creation-Date: 2020-08-14 10:32+0000\n"
+"PO-Revision-Date: 2020-08-14 12:33+0200\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"Language: es\n"
@@ -15,13 +15,13 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
-"X-Generator: Poedit 2.0.6\n"
+"X-Generator: Poedit 2.3\n"
#. module: chained_swapper
#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:138
#, python-format
-msgid "Chained swap done: %s: %s > %s"
-msgstr "Intercambio encadenado realizado: %s: %s > %s"
+msgid "Chained swap done:"
+msgstr "Cambio encadenado realizado:"
#. module: chained_swapper
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__ref_ir_act_window_id
@@ -60,21 +60,15 @@ msgstr "Avanzado"
msgid "Allowed Field"
msgstr "Campos permitidos"
-#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:158
-#, python-format
-msgid "At least one sub-field is no compatible with the main field."
-msgstr "Al menos un sub-campo no es compatible con el campo principal."
-
#. module: chained_swapper
#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_constraint__expression
msgid ""
-"Boolean python expression. You can use the key word 'records' as the records "
+"Boolean python expression. You can use the keyword 'records' as the records "
"selected to execute the contextual action. Ex.: bool(records."
"mapped('parent_id'))"
msgstr ""
"Expresión Python booleana. Puede usar la palabra clave 'records' para hacer "
-"referencia a los registros seleccionado al ejecutar la acción contextual. "
+"referencia a los registros seleccionados al ejecutar la acción contextual. "
"Ej.: bool(records.mapped('parent_id'))"
#. module: chained_swapper
@@ -95,15 +89,20 @@ msgid "Chained Swapper Sub-field"
msgstr "Intercambiador encadenado - Sub-campos"
#. module: chained_swapper
-#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
-msgid "Change"
-msgstr "Cambiar"
+#: code:addons/chained_swapper/models/chained_swapper.py:103
+#, python-format
+msgid "Chained swap"
+msgstr "Cambio encadenado"
#. module: chained_swapper
-#: model:chained.swapper,name:chained_swapper.chained_swapper_demo
#: model:ir.actions.act_window,name:chained_swapper.partner_chained_swap_lang_action_demo
-msgid "Change language"
-msgstr "Cambiar idioma"
+msgid "Chained swap: Language"
+msgstr "Cambio encadenado: Idioma"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
+msgid "Change"
+msgstr "Cambiar"
#. module: chained_swapper
#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
@@ -120,7 +119,7 @@ msgstr "Expresión de restricción"
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__constraint_ids
#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
msgid "Constraints"
-msgstr "Restricción"
+msgstr "Restricciones"
#. module: chained_swapper
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__create_uid
@@ -169,13 +168,24 @@ msgstr "Grupos"
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__id
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__id
msgid "ID"
-msgstr ""
+msgstr "ID"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:150
+#, python-format
+msgid "Incorrect sub-field expression:"
+msgstr "Expresión de sub-campo incorrecta:"
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:148
+#: code:addons/chained_swapper/models/chained_swapper.py:192
#, python-format
-msgid "Incorrect sub-field expression."
-msgstr "Expresión de sub-campo incorrecta."
+msgid "Invalid constraint expression: "
+msgstr "Expresión de restricción no válida:"
+
+#. module: chained_swapper
+#: model:chained.swapper,name:chained_swapper.chained_swapper_demo
+msgid "Language"
+msgstr "Idioma"
#. module: chained_swapper
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper____last_update
@@ -226,6 +236,12 @@ msgstr ""
msgid "Name"
msgstr "Nombre"
+#. module: chained_swapper
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:25
+#, python-format
+msgid "Not possible to swap the field due to the constraint"
+msgstr "No es posible cambiar el campo debido a la restricción"
+
#. module: chained_swapper
#: model:chained.swapper.constraint,name:chained_swapper.chained_swapper_constraint_demo
msgid "Only parent company"
@@ -259,6 +275,12 @@ msgstr "Cadena de sub-campo"
msgid "Sub-fields"
msgstr "Sub-campo"
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:161
+#, python-format
+msgid "The sub-field '%s' is not compatible with the main field."
+msgstr "El sub-campo '%s' no es compatible con el campo principal."
+
#. module: chained_swapper
#: model:ir.model,name:chained_swapper.model_chained_swapper_wizard
msgid "Wizard chained swapper"
diff --git a/chained_swapper/models/chained_swapper.py b/chained_swapper/models/chained_swapper.py
index 61461ce8cb..9bfff65495 100644
--- a/chained_swapper/models/chained_swapper.py
+++ b/chained_swapper/models/chained_swapper.py
@@ -1,7 +1,9 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
+# Copyright 2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, exceptions, fields, models, _
+from odoo.tools.safe_eval import safe_eval
class ChainedSwapper(models.Model):
@@ -98,7 +100,7 @@ def unlink(self):
def add_action(self):
self.ensure_one()
action = self.env['ir.actions.act_window'].create({
- 'name': self.name,
+ 'name': _("Chained swap") + ": " + self.name,
'type': 'ir.actions.act_window',
'res_model': 'chained.swapper.wizard',
'src_model': self.model_id.model,
@@ -124,7 +126,7 @@ class ChainedSwapperSubField(models.Model):
_description = "Chained Swapper Sub-field"
chained_swapper_id = fields.Many2one(
- comodel_name='chained.swapper'
+ comodel_name='chained.swapper', ondelete="cascade"
)
sub_field_chain = fields.Char(
required=True,
@@ -142,10 +144,11 @@ def _check_sub_field_chain(self):
chain_model = self.env[rec.chained_swapper_id.model_id.model]
for name in chain_list:
chain_model = chain_model[name]
- chain_model[chain_field_name]
+ chain_model[chain_field_name] # pylint: disable=W0104
except KeyError:
raise exceptions.ValidationError(
- _("Incorrect sub-field expression."))
+ _("Incorrect sub-field expression:") + " " +
+ rec.sub_field_chain)
# Check sub-field and original field are the same type
swap_field = rec.chained_swapper_id.field_id
chain_field = self.env['ir.model.fields'].search([
@@ -155,8 +158,8 @@ def _check_sub_field_chain(self):
if (chain_field.ttype != swap_field.ttype or
chain_field.relation != swap_field.relation):
raise exceptions.ValidationError(
- _("At least one sub-field is no compatible with "
- "the main field."))
+ _("The sub-field '%s' is not compatible with the main"
+ " field.") % rec.sub_field_chain)
class ChainedSwapperConstraint(models.Model):
@@ -164,7 +167,7 @@ class ChainedSwapperConstraint(models.Model):
_description = "Chained Swapper Constraint"
chained_swapper_id = fields.Many2one(
- comodel_name='chained.swapper'
+ comodel_name='chained.swapper', ondelete="cascade"
)
name = fields.Char(
required=True,
@@ -173,8 +176,19 @@ class ChainedSwapperConstraint(models.Model):
expression = fields.Text(
string='Constraint expression',
required=True,
- help="Boolean python expression. You can use the key word "
+ 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'))",
- default="return False",
+ default="True",
)
+
+ @api.constrains("expression")
+ def _check_expression(self):
+ for record in self:
+ model = self.env[record.chained_swapper_id.model_id.model]
+ try:
+ safe_eval(record.expression, {'records': model})
+ except Exception:
+ raise exceptions.ValidationError(_(
+ "Invalid constraint expression:" + " " + record.expression
+ ))
diff --git a/chained_swapper/readme/CONFIGURE.rst b/chained_swapper/readme/CONFIGURE.rst
index 41b8afb7fd..56bd428d50 100644
--- a/chained_swapper/readme/CONFIGURE.rst
+++ b/chained_swapper/readme/CONFIGURE.rst
@@ -1,14 +1,29 @@
To configure this module, you need to:
-#. Go to *Setting > Fiend Swaps > Fiend Swaps*.
+#. Go to *Setting > Field Swaps > Field Swaps*.
#. Create a new object and set the following data as an example:
- #. Set name 'Change Language'.
- #. Select 'Contact' as a Model.
- #. Select 'Language (res.partner)' as 'Field'.
- #. Add a new 'Sub-field chain' with this value 'child_ids.lang'.
- #. Add a new Constraint with this 'Expression':
- ```bool(records.mapped('parent_id')```
+ * Name for identifying it and use it for the action name.
+ * Select the source model where the swap will be started.
+ * Select the starting field for which the swap will be done.
+ * Add several chained fields. They are expressed as a string using
+ dot notation, taking the source model as beginning for looking there
+ the first field, and continuing from there drilling through. Example:
+ `picking_ids.partner_id` for `sale.order` model will go to the linked
+ deliveries orders, and change the customer there.
+ * Add possible constraints for restricting the chained swap. They are
+ Python expressions that must be one line that is evaluated as boolean.
+ If the evaluation is true, then a message will be thrown and no swap
+ will be allowed. You can use the variable `records` in your code, that
+ will be referring the selected records for doing the swap. Example: for
+ restricting sales orders that have a delivery order validated:
-#. Click on 'Add action' smart button to add a new menu action in
- 'res.partner' tree view named 'Change Language'.
+ `any(p.state == 'done' for p in records.mapped('picking_ids.state'))`
+
+ Each constraint has a name for identifying it, but also for showing that
+ name when displaying the error trying to do the swap.
+
+#. Click on 'Add action' smart button to add a new action in the source model.
+
+On demo databases, you can check the example "Language", that changes the
+language of a contact, and propagate it to children contacts.
diff --git a/chained_swapper/readme/DESCRIPTION.rst b/chained_swapper/readme/DESCRIPTION.rst
index 7829459e35..43c7ff518d 100644
--- a/chained_swapper/readme/DESCRIPTION.rst
+++ b/chained_swapper/readme/DESCRIPTION.rst
@@ -1,6 +1,13 @@
-With this module you can set the properly configuration
-to add/remove whenever you want a option in the top actions
-menu of any object. That action will launch a wizard that
-will allow you to set a value to a certain field in the
-selected objects and also set that value in other fields
-of other objects related with the original ones.
+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 .
diff --git a/chained_swapper/readme/USAGE.rst b/chained_swapper/readme/USAGE.rst
index 3d94a95545..cf1c1c1904 100644
--- a/chained_swapper/readme/USAGE.rst
+++ b/chained_swapper/readme/USAGE.rst
@@ -1,10 +1,12 @@
To use this module, you need to:
-#. Follow the described steps in the configuration section.
-#. If 'Contact' app is installed, go to *Contacts > Contacts*.
-#. Open a parent company contact form view or select one or more
- parent company contacts in the tree view.
-#. Go to action menu and click on *Change Language* option.
-#. A wizard will be launched allowing you to set a new language.
-#. Click on 'Change' button and the selected contacts will change
- their language as well as their children.
+#. Go to the source document in list mode.
+#. Select one or several records.
+#. Click on Action and locate the option "Chained swap: ".
+#. If one of the selected records doesn't comply with one of the constraints,
+ a message will be shown, and the swap won't continue.
+#. If everything is OK, a popup will arise, and you will see a field for
+ filling the new value.
+#. Click on "Change", and the swap will be done.
+#. On the chatter of the source document, an entry will be logged for
+ reflecting the done swap.
diff --git a/chained_swapper/static/description/index.html b/chained_swapper/static/description/index.html
index 1362634d22..5eb65a68c6 100644
--- a/chained_swapper/static/description/index.html
+++ b/chained_swapper/static/description/index.html
@@ -368,12 +368,16 @@
Chained Swapper
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
With this module you can set the properly configuration
-to add/remove whenever you want a option in the top actions
-menu of any object. That action will launch a wizard that
-will allow you to set a value to a certain field in the
-selected objects and also set that value in other fields
-of other objects related with the original ones.
+
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 .
Create a new object and set the following data as an example:
-
Set name ‘Change Language’.
-
Select ‘Contact’ as a Model.
-
Select ‘Language (res.partner)’ as ‘Field’.
-
Add a new ‘Sub-field chain’ with this value ‘child_ids.lang’.
-
Add a new Constraint with this ‘Expression’:
-`bool(records.mapped('parent_id')`
-
+
+
Go to Setting > Field Swaps > Field Swaps.
+
+
Create a new object and set the following data as an example:
+
+
Name for identifying it and use it for the action name.
+
+
Select the source model where the swap will be started.
+
+
Select the starting field for which the swap will be done.
+
+
Add several chained fields. They are expressed as a string using
+dot notation, taking the source model as beginning for looking there
+the first field, and continuing from there drilling through. Example:
+picking_ids.partner_id for sale.order model will go to the linked
+deliveries orders, and change the customer there.
+
+
Add possible constraints for restricting the chained swap. They are
+Python expressions that must be one line that is evaluated as boolean.
+If the evaluation is true, then a message will be thrown and no swap
+will be allowed. You can use the variable records in your code, that
+will be referring the selected records for doing the swap. Example: for
+restricting sales orders that have a delivery order validated:
+
any(p.state == ‘done’ for p in records.mapped(‘picking_ids.state’))
+
Each constraint has a name for identifying it, but also for showing that
+name when displaying the error trying to do the swap.
+
+
+
+
Click on ‘Add action’ smart button to add a new action in the source model.
-
Click on ‘Add action’ smart button to add a new menu action in
-‘res.partner’ tree view named ‘Change Language’.
+
On demo databases, you can check the example “Language”, that changes the
+language of a contact, and propagate it to children contacts.
Follow the described steps in the configuration section.
-
If ‘Contact’ app is installed, go to Contacts > Contacts.
-
Open a parent company contact form view or select one or more
-parent company contacts in the tree view.
-
Go to action menu and click on Change Language option.
-
A wizard will be launched allowing you to set a new language.
-
Click on ‘Change’ button and the selected contacts will change
-their language as well as their children.
+
Go to the source document in list mode.
+
Select one or several records.
+
Click on Action and locate the option “Chained swap: <name of the swap>”.
+
If one of the selected records doesn’t comply with one of the constraints,
+a message will be shown, and the swap won’t continue.
+
If everything is OK, a popup will arise, and you will see a field for
+filling the new value.
+
Click on “Change”, and the swap will be done.
+
On the chatter of the source document, an entry will be logged for
+reflecting the done swap.
diff --git a/chained_swapper/tests/test_chained_swapper.py b/chained_swapper/tests/test_chained_swapper.py
index 1eb79e8f99..645a421000 100644
--- a/chained_swapper/tests/test_chained_swapper.py
+++ b/chained_swapper/tests/test_chained_swapper.py
@@ -1,6 +1,8 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
+# Copyright 2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from odoo import exceptions
from odoo.tests import common
from odoo.modules import registry
from ..hooks import uninstall_hook
@@ -18,11 +20,24 @@ def setUpClass(cls):
{'name': 'partner child1 cs', 'parent_id': cls.partner_parent.id})
cls.partner_child_2 = res_partner.create(
{'name': 'partner child2 cs', 'parent_id': cls.partner_parent.id})
- cls.chained_swapper = cls.env.ref(
- "chained_swapper.chained_swapper_demo")
+ # Prevent duplicate error removing demo data if exists
+ record = cls.env.ref("chained_swapper.chained_swapper_demo", False)
+ if record:
+ record.unlink()
+ cls.chained_swapper = cls.env["chained.swapper"].create({
+ "name": "Language",
+ "model_id": cls.env.ref("base.model_res_partner").id,
+ "field_id": cls.env.ref("base.field_res_partner__lang").id,
+ "sub_field_ids": [(0, 0, {"sub_field_chain": "child_ids.lang"})],
+ "constraint_ids": [(0, 0, {
+ "name": "Only parent company",
+ "expression": "bool(records.mapped('parent_id'))",
+ })],
+ })
+ cls.chained_swapper.add_action()
def test_create_unlink_action(self):
- """Test if Sidebar Action is added / removed to / from give object."""
+ """Test if Sidebar Action is added / removed to / from given object."""
action = self.chained_swapper.ref_ir_act_window_id\
and self.chained_swapper.ref_ir_act_window_id.binding_model_id
self.assertTrue(action)
@@ -46,28 +61,36 @@ def test_unlink_chained_swapper(self):
self.assertFalse(action)
def test_change_constrained_partner_language(self):
- self.env['chained.swapper.wizard'].with_context(
- active_model='res.partner',
- active_id=self.partner_parent.id,
- active_ids=(self.partner_parent | self.partner_child_1).ids,
- chained_swapper_id=self.chained_swapper.id
- ).create({'lang': 'es_ES'})
+ with self.assertRaises(exceptions.UserError):
+ self.env['chained.swapper.wizard'].with_context(
+ active_model='res.partner',
+ active_id=self.partner_parent.id,
+ active_ids=(self.partner_parent | self.partner_child_1).ids,
+ chained_swapper_id=self.chained_swapper.id
+ ).create({'lang': 'es_ES'})
def test_change_partner_language(self):
- all_partner = (
- self.partner_parent | self.partner_child_1 | self.partner_child_2)
- self.assertEqual(all_partner.mapped('lang'), ['en_US']*3)
self.env['chained.swapper.wizard'].with_context(
active_model='res.partner',
active_id=self.partner_parent.id,
active_ids=[self.partner_parent.id],
chained_swapper_id=self.chained_swapper.id
).create({'lang': 'es_ES'})
- self.assertEqual(all_partner.mapped('lang'), ['es_ES']*3)
+ self.assertEqual(self.partner_parent.lang, 'es_ES')
+ self.assertEqual(self.partner_child_1.lang, 'es_ES')
+ self.assertEqual(self.partner_child_2.lang, 'es_ES')
- def _test_uninstall_hook(self):
+ def test_uninstall_hook(self):
"""Test if related actions are removed when mass editing
record is uninstalled."""
action_id = self.chained_swapper.ref_ir_act_window_id.id
uninstall_hook(self.cr, registry)
- self.assertFalse(self.env['ir.actions.act_window'].browse(action_id))
+ self.assertFalse(
+ self.env['ir.actions.act_window'].browse(action_id).exists()
+ )
+
+ def test_invalid_constraint(self):
+ with self.assertRaises(exceptions.ValidationError):
+ self.chained_swapper.constraint_ids.write({
+ "expression": "Something incorrect"
+ })
diff --git a/chained_swapper/views/chained_swapper_views.xml b/chained_swapper/views/chained_swapper_views.xml
index 9f5d1d3f55..049521f4a6 100644
--- a/chained_swapper/views/chained_swapper_views.xml
+++ b/chained_swapper/views/chained_swapper_views.xml
@@ -1,5 +1,6 @@
@@ -16,6 +17,7 @@
type="object"
string="Add Action"
attrs="{'invisible':[('ref_ir_act_window_id','!=',False)]}"
+ class="oe_stat_button"
icon="fa-plus"
help="Add a new contextual action of related documents to open a composition wizard"/>
diff --git a/chained_swapper/hooks.py b/chained_swapper/hooks.py
index 9c8a9c55e4..a6d105ee7d 100644
--- a/chained_swapper/hooks.py
+++ b/chained_swapper/hooks.py
@@ -1,14 +1,14 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-from odoo.api import Environment, SUPERUSER_ID
+from odoo.api import SUPERUSER_ID, Environment
def uninstall_hook(cr, registry):
"""Delete the actions that were created with chained_swapper when
the module is uninstalled"""
env = Environment(cr, SUPERUSER_ID, {})
- env['ir.actions.act_window'].search([
- ('res_model', '=', 'chained.swapper.wizard')
- ]).unlink()
+ env["ir.actions.act_window"].search(
+ [("res_model", "=", "chained.swapper.wizard")]
+ ).unlink()
return True
diff --git a/chained_swapper/models/chained_swapper.py b/chained_swapper/models/chained_swapper.py
index 68c0c92031..e60ebdcdc6 100644
--- a/chained_swapper/models/chained_swapper.py
+++ b/chained_swapper/models/chained_swapper.py
@@ -2,7 +2,7 @@
# Copyright 2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-from odoo import api, exceptions, fields, models, _
+from odoo import _, api, exceptions, fields, models
from odoo.tools.safe_eval import safe_eval
@@ -10,42 +10,37 @@ class ChainedSwapper(models.Model):
_name = "chained.swapper"
_description = "Chained Swapper"
- name = fields.Char(
- required=True,
- translate=True,
- index=1,
- )
+ name = fields.Char(required=True, translate=True, index=1,)
model_id = fields.Many2one(
- comodel_name='ir.model',
+ comodel_name="ir.model",
required=True,
help="Model is used for Selecting Field. This is editable "
- "until Contextual Action is not created.",
+ "until Contextual Action is not created.",
)
allowed_field_ids = fields.Many2many(
- comodel_name='ir.model.fields',
- compute='_compute_allowed_field_ids'
+ comodel_name="ir.model.fields", compute="_compute_allowed_field_ids"
)
field_id = fields.Many2one(
- comodel_name='ir.model.fields',
+ comodel_name="ir.model.fields",
required=True,
domain="[('id', 'in', allowed_field_ids)]",
)
sub_field_ids = fields.One2many(
- comodel_name='chained.swapper.sub.field',
- inverse_name='chained_swapper_id',
- string='Sub-fields',
+ comodel_name="chained.swapper.sub.field",
+ inverse_name="chained_swapper_id",
+ string="Sub-fields",
)
constraint_ids = fields.One2many(
- comodel_name='chained.swapper.constraint',
- inverse_name='chained_swapper_id',
- string='Constraints',
+ comodel_name="chained.swapper.constraint",
+ inverse_name="chained_swapper_id",
+ string="Constraints",
)
ref_ir_act_window_id = fields.Many2one(
- comodel_name='ir.actions.act_window',
- string='Action',
+ comodel_name="ir.actions.act_window",
+ string="Action",
readonly=True,
help="Action to make this template available on records "
- "of the related document model.",
+ "of the related document model.",
)
group_ids = fields.Many2many(
comodel_name="res.groups",
@@ -56,14 +51,17 @@ class ChainedSwapper(models.Model):
)
_sql_constraints = [
- ('model_id_field_id_unique', 'unique (model_id, field_id)',
- 'Model and Field must be unique!'),
+ (
+ "model_id_field_id_unique",
+ "unique (model_id, field_id)",
+ "Model and Field must be unique!",
+ ),
]
- @api.depends('model_id')
+ @api.depends("model_id")
def _compute_allowed_field_ids(self):
- model_obj = self.env['ir.model']
- field_obj = self.env['ir.model.fields']
+ model_obj = self.env["ir.model"]
+ field_obj = self.env["ir.model.fields"]
for record in self:
allowed_field_ids = False
if record.model_id:
@@ -71,24 +69,26 @@ def _compute_allowed_field_ids(self):
active_model_obj = self.env[record.model_id.model]
if active_model_obj._inherits:
keys = list(active_model_obj._inherits.keys())
- all_models |= model_obj.search([('model', 'in', keys)])
- allowed_field_ids = field_obj.search([
- ('ttype', 'not in', ['reference', 'function', 'one2many']),
- ('model_id', 'in', all_models.ids),
- ])
+ all_models |= model_obj.search([("model", "in", keys)])
+ allowed_field_ids = field_obj.search(
+ [
+ ("ttype", "not in", ["reference", "function", "one2many"]),
+ ("model_id", "in", all_models.ids),
+ ]
+ )
record.allowed_field_ids = allowed_field_ids
- @api.constrains('model_id', 'field_id')
+ @api.constrains("model_id", "field_id")
def _check_sub_field_ids(self):
- self.mapped('sub_field_ids')._check_sub_field_chain()
+ self.mapped("sub_field_ids")._check_sub_field_chain()
- @api.onchange('model_id')
+ @api.onchange("model_id")
def _onchange_model_id(self):
self.field_id = False
def write(self, vals):
res = super().write(vals)
- if 'name' in vals:
+ if "name" in vals:
self.mapped("ref_ir_act_window_id").write({"name": vals["name"]})
return res
@@ -100,25 +100,27 @@ def unlink(self):
@api.multi
def add_action(self):
self.ensure_one()
- action = self.env['ir.actions.act_window'].create({
- 'name': _("Chained swap") + ": " + self.name,
- 'type': 'ir.actions.act_window',
- 'res_model': 'chained.swapper.wizard',
- 'src_model': self.model_id.model,
- 'groups_id': [(4, x.id) for x in self.group_ids],
- 'view_type': 'form',
- 'context': "{'chained_swapper_id': %d}" % (self.id),
- 'view_mode': 'form',
- 'target': 'new',
- 'binding_model_id': self.model_id.id,
- 'binding_type': 'action',
- })
- self.write({'ref_ir_act_window_id': action.id})
+ action = self.env["ir.actions.act_window"].create(
+ {
+ "name": _("Chained swap") + ": " + self.name,
+ "type": "ir.actions.act_window",
+ "res_model": "chained.swapper.wizard",
+ "src_model": self.model_id.model,
+ "groups_id": [(4, x.id) for x in self.group_ids],
+ "view_type": "form",
+ "context": "{'chained_swapper_id': %d}" % (self.id),
+ "view_mode": "form",
+ "target": "new",
+ "binding_model_id": self.model_id.id,
+ "binding_type": "action",
+ }
+ )
+ self.write({"ref_ir_act_window_id": action.id})
return True
@api.multi
def unlink_action(self):
- self.mapped('ref_ir_act_window_id').unlink()
+ self.mapped("ref_ir_act_window_id").unlink()
return True
@@ -127,20 +129,20 @@ class ChainedSwapperSubField(models.Model):
_description = "Chained Swapper Sub-field"
chained_swapper_id = fields.Many2one(
- comodel_name='chained.swapper', ondelete="cascade"
+ comodel_name="chained.swapper", ondelete="cascade"
)
sub_field_chain = fields.Char(
required=True,
help="You can specify here a field of related fields as "
- "dotted names. Ex.: 'child_ids.lang'."
+ "dotted names. Ex.: 'child_ids.lang'.",
)
- @api.constrains('chained_swapper_id', 'sub_field_chain')
+ @api.constrains("chained_swapper_id", "sub_field_chain")
def _check_sub_field_chain(self):
for rec in self:
# Check sub-field exist
try:
- chain_list = rec.sub_field_chain.split('.')
+ chain_list = rec.sub_field_chain.split(".")
chain_field_name = chain_list.pop()
chain_model = self.env[rec.chained_swapper_id.model_id.model]
for name in chain_list:
@@ -148,19 +150,24 @@ def _check_sub_field_chain(self):
chain_model[chain_field_name] # pylint: disable=W0104
except KeyError:
raise exceptions.ValidationError(
- _("Incorrect sub-field expression:") + " " +
- rec.sub_field_chain)
+ _("Incorrect sub-field expression:") + " " + rec.sub_field_chain
+ )
# Check sub-field and original field are the same type
swap_field = rec.chained_swapper_id.field_id
- chain_field = self.env['ir.model.fields'].search([
- ('model_id', '=', rec.chained_swapper_id.model_id.id),
- ('name', '=', chain_field_name),
- ])
- if (chain_field.ttype != swap_field.ttype or
- chain_field.relation != swap_field.relation):
+ chain_field = self.env["ir.model.fields"].search(
+ [
+ ("model_id", "=", rec.chained_swapper_id.model_id.id),
+ ("name", "=", chain_field_name),
+ ]
+ )
+ if (
+ chain_field.ttype != swap_field.ttype
+ or chain_field.relation != swap_field.relation
+ ):
raise exceptions.ValidationError(
- _("The sub-field '%s' is not compatible with the main"
- " field.") % rec.sub_field_chain)
+ _("The sub-field '%s' is not compatible with the main" " field.")
+ % rec.sub_field_chain
+ )
class ChainedSwapperConstraint(models.Model):
@@ -168,18 +175,15 @@ class ChainedSwapperConstraint(models.Model):
_description = "Chained Swapper Constraint"
chained_swapper_id = fields.Many2one(
- comodel_name='chained.swapper', ondelete="cascade"
- )
- name = fields.Char(
- required=True,
- translate=True,
+ comodel_name="chained.swapper", ondelete="cascade"
)
+ name = fields.Char(required=True, translate=True,)
expression = fields.Text(
- string='Constraint expression',
+ string="Constraint expression",
required=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'))",
+ "'records' as the records selected to execute the "
+ "contextual action. Ex.: bool(records.mapped('parent_id'))",
default="True",
)
@@ -188,8 +192,8 @@ def _check_expression(self):
for record in self:
model = self.env[record.chained_swapper_id.model_id.model]
try:
- safe_eval(record.expression, {'records': model})
+ safe_eval(record.expression, {"records": model})
except Exception:
- raise exceptions.ValidationError(_(
- "Invalid constraint expression:" + " " + record.expression
- ))
+ raise exceptions.ValidationError(
+ _("Invalid constraint expression:" + " " + record.expression)
+ )
diff --git a/chained_swapper/tests/test_chained_swapper.py b/chained_swapper/tests/test_chained_swapper.py
index 645a421000..8b7568d329 100644
--- a/chained_swapper/tests/test_chained_swapper.py
+++ b/chained_swapper/tests/test_chained_swapper.py
@@ -3,8 +3,9 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import exceptions
-from odoo.tests import common
from odoo.modules import registry
+from odoo.tests import common
+
from ..hooks import uninstall_hook
@@ -12,34 +13,47 @@ class TestChainedSwapper(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestChainedSwapper, cls).setUpClass()
- cls.env['res.lang'].load_lang('es_ES')
- res_partner = cls.env['res.partner']
+ cls.env["res.lang"].load_lang("es_ES")
+ res_partner = cls.env["res.partner"]
cls.partner_parent = res_partner.create(
- {'name': 'parent partner cs', 'lang': 'en_US'})
+ {"name": "parent partner cs", "lang": "en_US"}
+ )
cls.partner_child_1 = res_partner.create(
- {'name': 'partner child1 cs', 'parent_id': cls.partner_parent.id})
+ {"name": "partner child1 cs", "parent_id": cls.partner_parent.id}
+ )
cls.partner_child_2 = res_partner.create(
- {'name': 'partner child2 cs', 'parent_id': cls.partner_parent.id})
+ {"name": "partner child2 cs", "parent_id": cls.partner_parent.id}
+ )
# Prevent duplicate error removing demo data if exists
record = cls.env.ref("chained_swapper.chained_swapper_demo", False)
if record:
record.unlink()
- cls.chained_swapper = cls.env["chained.swapper"].create({
- "name": "Language",
- "model_id": cls.env.ref("base.model_res_partner").id,
- "field_id": cls.env.ref("base.field_res_partner__lang").id,
- "sub_field_ids": [(0, 0, {"sub_field_chain": "child_ids.lang"})],
- "constraint_ids": [(0, 0, {
- "name": "Only parent company",
- "expression": "bool(records.mapped('parent_id'))",
- })],
- })
+ cls.chained_swapper = cls.env["chained.swapper"].create(
+ {
+ "name": "Language",
+ "model_id": cls.env.ref("base.model_res_partner").id,
+ "field_id": cls.env.ref("base.field_res_partner__lang").id,
+ "sub_field_ids": [(0, 0, {"sub_field_chain": "child_ids.lang"})],
+ "constraint_ids": [
+ (
+ 0,
+ 0,
+ {
+ "name": "Only parent company",
+ "expression": "bool(records.mapped('parent_id'))",
+ },
+ )
+ ],
+ }
+ )
cls.chained_swapper.add_action()
def test_create_unlink_action(self):
"""Test if Sidebar Action is added / removed to / from given object."""
- action = self.chained_swapper.ref_ir_act_window_id\
+ action = (
+ self.chained_swapper.ref_ir_act_window_id
and self.chained_swapper.ref_ir_act_window_id.binding_model_id
+ )
self.assertTrue(action)
# Remove the action
self.chained_swapper.unlink_action()
@@ -47,8 +61,10 @@ def test_create_unlink_action(self):
self.assertFalse(action)
# Add an action
self.chained_swapper.add_action()
- action = self.chained_swapper.ref_ir_act_window_id \
+ action = (
+ self.chained_swapper.ref_ir_act_window_id
and self.chained_swapper.ref_ir_act_window_id.binding_model_id
+ )
self.assertTrue(action)
def test_unlink_chained_swapper(self):
@@ -56,41 +72,38 @@ def test_unlink_chained_swapper(self):
record is unlinked."""
action_id = self.chained_swapper.ref_ir_act_window_id.id
self.chained_swapper.unlink()
- action = self.env['ir.actions.act_window'].search([
- ('id', '=', action_id)])
+ action = self.env["ir.actions.act_window"].search([("id", "=", action_id)])
self.assertFalse(action)
def test_change_constrained_partner_language(self):
with self.assertRaises(exceptions.UserError):
- self.env['chained.swapper.wizard'].with_context(
- active_model='res.partner',
+ self.env["chained.swapper.wizard"].with_context(
+ active_model="res.partner",
active_id=self.partner_parent.id,
active_ids=(self.partner_parent | self.partner_child_1).ids,
- chained_swapper_id=self.chained_swapper.id
- ).create({'lang': 'es_ES'})
+ chained_swapper_id=self.chained_swapper.id,
+ ).create({"lang": "es_ES"})
def test_change_partner_language(self):
- self.env['chained.swapper.wizard'].with_context(
- active_model='res.partner',
+ self.env["chained.swapper.wizard"].with_context(
+ active_model="res.partner",
active_id=self.partner_parent.id,
active_ids=[self.partner_parent.id],
- chained_swapper_id=self.chained_swapper.id
- ).create({'lang': 'es_ES'})
- self.assertEqual(self.partner_parent.lang, 'es_ES')
- self.assertEqual(self.partner_child_1.lang, 'es_ES')
- self.assertEqual(self.partner_child_2.lang, 'es_ES')
+ chained_swapper_id=self.chained_swapper.id,
+ ).create({"lang": "es_ES"})
+ self.assertEqual(self.partner_parent.lang, "es_ES")
+ self.assertEqual(self.partner_child_1.lang, "es_ES")
+ self.assertEqual(self.partner_child_2.lang, "es_ES")
def test_uninstall_hook(self):
"""Test if related actions are removed when mass editing
record is uninstalled."""
action_id = self.chained_swapper.ref_ir_act_window_id.id
uninstall_hook(self.cr, registry)
- self.assertFalse(
- self.env['ir.actions.act_window'].browse(action_id).exists()
- )
+ self.assertFalse(self.env["ir.actions.act_window"].browse(action_id).exists())
def test_invalid_constraint(self):
with self.assertRaises(exceptions.ValidationError):
- self.chained_swapper.constraint_ids.write({
- "expression": "Something incorrect"
- })
+ self.chained_swapper.constraint_ids.write(
+ {"expression": "Something incorrect"}
+ )
diff --git a/chained_swapper/views/chained_swapper_views.xml b/chained_swapper/views/chained_swapper_views.xml
index 049521f4a6..d46e498a6b 100644
--- a/chained_swapper/views/chained_swapper_views.xml
+++ b/chained_swapper/views/chained_swapper_views.xml
@@ -1,4 +1,4 @@
-
+
@@ -11,52 +11,64 @@
@@ -66,9 +78,9 @@
chained.swapper
-
-
-
+
+
+
@@ -84,7 +96,7 @@
chained.swapper.sub.field
-
+
@@ -94,8 +106,8 @@
chained.swapper.constraint
-
-
+
+
@@ -105,21 +117,30 @@
-
-
+
+
diff --git a/chained_swapper/wizard/chained_swapper_wizard.py b/chained_swapper/wizard/chained_swapper_wizard.py
index e3236cce3f..a0b9d50680 100644
--- a/chained_swapper/wizard/chained_swapper_wizard.py
+++ b/chained_swapper/wizard/chained_swapper_wizard.py
@@ -1,79 +1,84 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-from lxml import etree
from datetime import date, datetime
-from odoo.tools.safe_eval import safe_eval
+from lxml import etree
+
from odoo import _, api, models
from odoo.exceptions import UserError
+from odoo.tools.safe_eval import safe_eval
class ChainedSwapperWizard(models.TransientModel):
- _name = 'chained.swapper.wizard'
+ _name = "chained.swapper.wizard"
_description = "Wizard chained swapper"
@api.model
def default_get(self, fields):
context = self.env.context
- if context.get('chained_swapper_id'):
- records = self.env[context.get('active_model')].browse(
- context.get('active_ids'))
+ if context.get("chained_swapper_id"):
+ records = self.env[context.get("active_model")].browse(
+ context.get("active_ids")
+ )
exp_dict = {
- 'records': records,
- 'env': self.env,
- 'date': date,
- 'datetime': datetime,
+ "records": records,
+ "env": self.env,
+ "date": date,
+ "datetime": datetime,
}
- chained_swapper = self.env['chained.swapper'].browse(
- context.get('chained_swapper_id'))
+ chained_swapper = self.env["chained.swapper"].browse(
+ context.get("chained_swapper_id")
+ )
for constraint in chained_swapper.constraint_ids:
if safe_eval(constraint.expression, exp_dict):
- raise UserError(_(
- "Not possible to swap the field due to the constraint"
- ) + ": " + constraint.name)
+ raise UserError(
+ _("Not possible to swap the field due to the constraint")
+ + ": "
+ + constraint.name
+ )
return super().default_get(fields)
@api.model
- def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
- submenu=False):
+ def fields_view_get(
+ self, view_id=None, view_type="form", toolbar=False, submenu=False
+ ):
""" As we don't have any field in this model, result['fields']
and result['arch'] are modified to add dynamically the
corresponding field.
"""
res = super().fields_view_get(
- view_id=view_id, view_type=view_type, toolbar=toolbar,
- submenu=submenu)
- if not self.env.context.get('chained_swapper_id'):
+ view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
+ )
+ if not self.env.context.get("chained_swapper_id"):
return res
- chained_swapper = self.env['chained.swapper'].browse(
- self.env.context.get('chained_swapper_id'))
- model_obj = self.env[self.env.context.get('active_model')]
+ chained_swapper = self.env["chained.swapper"].browse(
+ self.env.context.get("chained_swapper_id")
+ )
+ model_obj = self.env[self.env.context.get("active_model")]
field_info = model_obj.fields_get()
field = chained_swapper.field_id
# Fields dict
all_fields = {
field.name: {
- 'type': field.ttype,
- 'string': field.field_description,
+ "type": field.ttype,
+ "string": field.field_description,
"views": {},
}
}
if field.ttype in ["many2many", "many2one"]:
- all_fields[field.name]['relation'] = field.relation
- elif field.ttype == 'selection':
- field_selection = field_info[field.name]['selection']
- all_fields[field.name]['selection'] = field_selection
+ all_fields[field.name]["relation"] = field.relation
+ elif field.ttype == "selection":
+ field_selection = field_info[field.name]["selection"]
+ all_fields[field.name]["selection"] = field_selection
# XML view definition
- doc = etree.XML(res['arch'])
+ doc = etree.XML(res["arch"])
group_node = doc.xpath("//group[@name='swap_field_group']")[0]
- etree.SubElement(
- group_node, 'field', {'name': field.name, 'colspan': '4'})
+ etree.SubElement(group_node, "field", {"name": field.name, "colspan": "4"})
if field.ttype in ["one2many", "many2many", "text"]:
- group_node.set('string', field.field_description)
- group_node.set('nolabel', '1')
- res.update(
- arch=etree.tostring(doc, encoding='unicode'), fields=all_fields)
+ group_node.set("string", field.field_description)
+ group_node.set("nolabel", "1")
+ res.update(arch=etree.tostring(doc, encoding="unicode"), fields=all_fields)
return res
@api.model
@@ -82,22 +87,20 @@ def create(self, vals):
received in vals dict are only used to change the value in the active
models.
"""
- model_obj = self.env[self.env.context.get('active_model')]
+ model_obj = self.env[self.env.context.get("active_model")]
context = self.env.context
field_name, new_value = list(vals.items())[0]
# write the active model
- model = model_obj.browse(self.env.context.get('active_ids'))
+ model = model_obj.browse(self.env.context.get("active_ids"))
original_values = {m.id: m[field_name] for m in model}
model.write(vals)
- if hasattr(model, 'message_post'):
- self.post_chained_swap(
- model, field_name, original_values, new_value)
+ if hasattr(model, "message_post"):
+ self.post_chained_swap(model, field_name, original_values, new_value)
# write chained models
- chained_swapper_obj = self.env['chained.swapper']
- chained_swapper = chained_swapper_obj.browse(
- context.get('chained_swapper_id'))
+ chained_swapper_obj = self.env["chained.swapper"]
+ chained_swapper = chained_swapper_obj.browse(context.get("chained_swapper_id"))
for sub_field in chained_swapper.sub_field_ids:
- chain_fields = sub_field.sub_field_chain.split('.')
+ chain_fields = sub_field.sub_field_chain.split(".")
field_name = chain_fields.pop()
chain_model = model
for chain_field in chain_fields:
@@ -105,14 +108,15 @@ def create(self, vals):
original_values = {cm.id: cm[field_name] for cm in chain_model}
chain_model.write({field_name: new_value})
# post swap
- if hasattr(chain_model, 'message_post'):
+ if hasattr(chain_model, "message_post"):
self.post_chained_swap(
- chain_model, field_name, original_values, new_value)
+ chain_model, field_name, original_values, new_value
+ )
return super().create({})
@api.multi
def change_action(self):
- return {'type': 'ir.actions.act_window_close'}
+ return {"type": "ir.actions.act_window_close"}
@api.model
def post_chained_swap(self, model, field_name, original_values, new_value):
@@ -136,18 +140,19 @@ def human_readable_field(value):
if type(value) == list:
ids = value[0][2]
value = self.env[field_def.comodel_name].browse(ids)
- result = str(value.mapped('display_name'))
+ result = str(value.mapped("display_name"))
return result
+
field_desc = model._fields[field_name].string
new_value = human_readable_field(new_value)
for m in model:
original_value = human_readable_field(original_values[m.id])
m.message_post(
- body=_("Chained swap done:") + " %s: %s ⇒ %s" % (
- field_desc, original_value, new_value)
+ body=_("Chained swap done:")
+ + " {}: {} ⇒ {}".format(field_desc, original_value, new_value)
)
- def read(self, fields, load='_classic_read'):
+ def 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'
"""
diff --git a/chained_swapper/wizard/chained_swapper_wizard_views.xml b/chained_swapper/wizard/chained_swapper_wizard_views.xml
index fbbcbec512..14fa58eeb4 100644
--- a/chained_swapper/wizard/chained_swapper_wizard_views.xml
+++ b/chained_swapper/wizard/chained_swapper_wizard_views.xml
@@ -1,4 +1,4 @@
-
+
@@ -7,15 +7,15 @@
chained.swapper.wizard
From aabdd4b004a3ccbb236c715d35f2a56ee8cf27cc Mon Sep 17 00:00:00 2001
From: Ernesto Tejeda
Date: Mon, 18 Jan 2021 13:35:18 -0500
Subject: [PATCH 06/18] [MIG] chained_swapper: Migration to 13.0
---
chained_swapper/README.rst | 10 ++---
chained_swapper/__manifest__.py | 4 +-
chained_swapper/demo/chained_swapper_demo.xml | 4 +-
chained_swapper/i18n/chained_swapper.pot | 42 ++++++++++++-------
chained_swapper/models/chained_swapper.py | 9 +---
chained_swapper/static/description/index.html | 6 +--
.../views/chained_swapper_views.xml | 1 -
.../wizard/chained_swapper_wizard.py | 7 ++--
8 files changed, 43 insertions(+), 40 deletions(-)
diff --git a/chained_swapper/README.rst b/chained_swapper/README.rst
index c0d767608b..3c52e351ec 100644
--- a/chained_swapper/README.rst
+++ b/chained_swapper/README.rst
@@ -14,13 +14,13 @@ Chained Swapper
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--ux-lightgray.png?logo=github
- :target: https://github.com/OCA/server-ux/tree/12.0/chained_swapper
+ :target: https://github.com/OCA/server-ux/tree/13.0/chained_swapper
:alt: OCA/server-ux
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/server-ux-12-0/server-ux-12-0-chained_swapper
+ :target: https://translation.odoo-community.org/projects/server-ux-13-0/server-ux-13-0-chained_swapper
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/250/12.0
+ :target: https://runbot.odoo-community.org/runbot/250/13.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -100,7 +100,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -133,6 +133,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-This module is part of the `OCA/server-ux `_ project on GitHub.
+This module is part of the `OCA/server-ux `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/chained_swapper/__manifest__.py b/chained_swapper/__manifest__.py
index 5c45db64b0..be08ae33a2 100644
--- a/chained_swapper/__manifest__.py
+++ b/chained_swapper/__manifest__.py
@@ -3,7 +3,7 @@
{
"name": "Chained Swapper",
"summary": "Chained Swapper",
- "version": "12.0.1.0.1",
+ "version": "13.0.1.0.0",
"author": "Tecnativa, Odoo Community Association (OCA)",
"category": "Tools",
"website": "https://github.com/OCA/server-ux",
@@ -14,6 +14,6 @@
"views/chained_swapper_views.xml",
"wizard/chained_swapper_wizard_views.xml",
],
- "demo": ["demo/chained_swapper_demo.xml",],
+ "demo": ["demo/chained_swapper_demo.xml"],
"uninstall_hook": "uninstall_hook",
}
diff --git a/chained_swapper/demo/chained_swapper_demo.xml b/chained_swapper/demo/chained_swapper_demo.xml
index 14a46a3995..61570d7947 100644
--- a/chained_swapper/demo/chained_swapper_demo.xml
+++ b/chained_swapper/demo/chained_swapper_demo.xml
@@ -23,12 +23,12 @@
id="partner_chained_swap_lang_action_demo"
name="Chained swap: Language"
res_model="chained.swapper.wizard"
- src_model="res.partner"
+ binding_model="res.partner"
view_mode="form"
context="{'chained_swapper_id': ref('chained_swapper_demo')}"
- key2="client_action_multi"
target="new"
/>
+
\n"
+"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -14,7 +14,7 @@ msgstr ""
"Plural-Forms: \n"
#. module: chained_swapper
-#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:146
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:0
#, python-format
msgid "Chained swap done:"
msgstr ""
@@ -26,7 +26,9 @@ msgstr ""
#. module: chained_swapper
#: model:ir.model.fields,help:chained_swapper.field_chained_swapper__ref_ir_act_window_id
-msgid "Action to make this template available on records of the related document model."
+msgid ""
+"Action to make this template available on records of the related document "
+"model."
msgstr ""
#. module: chained_swapper
@@ -36,7 +38,9 @@ msgstr ""
#. module: chained_swapper
#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
-msgid "Add a new contextual action of related documents to open a composition wizard"
+msgid ""
+"Add a new contextual action of related documents to open a composition "
+"wizard"
msgstr ""
#. module: chained_swapper
@@ -51,7 +55,10 @@ msgstr ""
#. module: chained_swapper
#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_constraint__expression
-msgid "Boolean python expression. You can use the keyword 'records' as the records selected to execute the contextual action. Ex.: bool(records.mapped('parent_id'))"
+msgid ""
+"Boolean python expression. You can use the keyword 'records' as the records "
+"selected to execute the contextual action. Ex.: "
+"bool(records.mapped('parent_id'))"
msgstr ""
#. module: chained_swapper
@@ -72,7 +79,7 @@ msgid "Chained Swapper Sub-field"
msgstr ""
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:104
+#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
msgid "Chained swap"
msgstr ""
@@ -154,13 +161,13 @@ msgid "ID"
msgstr ""
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:151
+#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
msgid "Incorrect sub-field expression:"
msgstr ""
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:193
+#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
msgid "Invalid constraint expression: "
msgstr ""
@@ -200,13 +207,15 @@ msgid "Model"
msgstr ""
#. module: chained_swapper
-#: sql_constraint:chained.swapper:0
+#: model:ir.model.constraint,message:chained_swapper.constraint_chained_swapper_model_id_field_id_unique
msgid "Model and Field must be unique!"
msgstr ""
#. module: chained_swapper
#: model:ir.model.fields,help:chained_swapper.field_chained_swapper__model_id
-msgid "Model is used for Selecting Field. This is editable until Contextual Action is not created."
+msgid ""
+"Model is used for Selecting Field. This is editable until Contextual Action "
+"is not created."
msgstr ""
#. module: chained_swapper
@@ -216,7 +225,7 @@ msgid "Name"
msgstr ""
#. module: chained_swapper
-#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:32
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:0
#, python-format
msgid "Not possible to swap the field due to the constraint"
msgstr ""
@@ -253,7 +262,7 @@ msgid "Sub-fields"
msgstr ""
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:162
+#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
msgid "The sub-field '%s' is not compatible with the main field."
msgstr ""
@@ -265,6 +274,7 @@ msgstr ""
#. module: chained_swapper
#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_sub_field__sub_field_chain
-msgid "You can specify here a field of related fields as dotted names. Ex.: 'child_ids.lang'."
+msgid ""
+"You can specify here a field of related fields as dotted names. Ex.: "
+"'child_ids.lang'."
msgstr ""
-
diff --git a/chained_swapper/models/chained_swapper.py b/chained_swapper/models/chained_swapper.py
index e60ebdcdc6..df2e714a3e 100644
--- a/chained_swapper/models/chained_swapper.py
+++ b/chained_swapper/models/chained_swapper.py
@@ -10,7 +10,7 @@ class ChainedSwapper(models.Model):
_name = "chained.swapper"
_description = "Chained Swapper"
- name = fields.Char(required=True, translate=True, index=1,)
+ name = fields.Char(required=True, translate=True, index=1)
model_id = fields.Many2one(
comodel_name="ir.model",
required=True,
@@ -92,12 +92,10 @@ def write(self, vals):
self.mapped("ref_ir_act_window_id").write({"name": vals["name"]})
return res
- @api.multi
def unlink(self):
self.unlink_action()
return super().unlink()
- @api.multi
def add_action(self):
self.ensure_one()
action = self.env["ir.actions.act_window"].create(
@@ -105,9 +103,7 @@ def add_action(self):
"name": _("Chained swap") + ": " + self.name,
"type": "ir.actions.act_window",
"res_model": "chained.swapper.wizard",
- "src_model": self.model_id.model,
"groups_id": [(4, x.id) for x in self.group_ids],
- "view_type": "form",
"context": "{'chained_swapper_id': %d}" % (self.id),
"view_mode": "form",
"target": "new",
@@ -118,7 +114,6 @@ def add_action(self):
self.write({"ref_ir_act_window_id": action.id})
return True
- @api.multi
def unlink_action(self):
self.mapped("ref_ir_act_window_id").unlink()
return True
@@ -177,7 +172,7 @@ class ChainedSwapperConstraint(models.Model):
chained_swapper_id = fields.Many2one(
comodel_name="chained.swapper", ondelete="cascade"
)
- name = fields.Char(required=True, translate=True,)
+ name = fields.Char(required=True, translate=True)
expression = fields.Text(
string="Constraint expression",
required=True,
diff --git a/chained_swapper/static/description/index.html b/chained_swapper/static/description/index.html
index ab2aed1683..a976f27cc7 100644
--- a/chained_swapper/static/description/index.html
+++ b/chained_swapper/static/description/index.html
@@ -367,7 +367,7 @@
Chained Swapper
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
+
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.
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-feedback.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
This module is part of the OCA/server-ux project on GitHub.
+
This module is part of the OCA/server-ux project on GitHub.
diff --git a/chained_swapper/views/chained_swapper_views.xml b/chained_swapper/views/chained_swapper_views.xml
index d46e498a6b..8e9e175336 100644
--- a/chained_swapper/views/chained_swapper_views.xml
+++ b/chained_swapper/views/chained_swapper_views.xml
@@ -87,7 +87,6 @@
Field Swapschained.swapper
- formtree,form
diff --git a/chained_swapper/wizard/chained_swapper_wizard.py b/chained_swapper/wizard/chained_swapper_wizard.py
index a0b9d50680..2992504bd3 100644
--- a/chained_swapper/wizard/chained_swapper_wizard.py
+++ b/chained_swapper/wizard/chained_swapper_wizard.py
@@ -43,12 +43,12 @@ def default_get(self, fields):
def fields_view_get(
self, view_id=None, view_type="form", toolbar=False, submenu=False
):
- """ As we don't have any field in this model, result['fields']
+ """As we don't have any field in this model, result['fields']
and result['arch'] are modified to add dynamically the
corresponding field.
"""
res = super().fields_view_get(
- view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
+ view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu,
)
if not self.env.context.get("chained_swapper_id"):
return res
@@ -83,7 +83,7 @@ def fields_view_get(
@api.model
def create(self, vals):
- """ As we don't have any field in this model, the key-value pair
+ """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.
"""
@@ -114,7 +114,6 @@ def create(self, vals):
)
return super().create({})
- @api.multi
def change_action(self):
return {"type": "ir.actions.act_window_close"}
From 7dc26c4494b27db86dc1ba720233db44a75dd888 Mon Sep 17 00:00:00 2001
From: schhatbar
Date: Thu, 25 Feb 2021 15:26:11 +0530
Subject: [PATCH 07/18] [IMP] chained_swapper: black, isort, prettier
---
chained_swapper/__manifest__.py | 2 +-
chained_swapper/wizard/chained_swapper_wizard.py | 5 ++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/chained_swapper/__manifest__.py b/chained_swapper/__manifest__.py
index be08ae33a2..6031a3fa1a 100644
--- a/chained_swapper/__manifest__.py
+++ b/chained_swapper/__manifest__.py
@@ -3,7 +3,7 @@
{
"name": "Chained Swapper",
"summary": "Chained Swapper",
- "version": "13.0.1.0.0",
+ "version": "14.0.1.0.0",
"author": "Tecnativa, Odoo Community Association (OCA)",
"category": "Tools",
"website": "https://github.com/OCA/server-ux",
diff --git a/chained_swapper/wizard/chained_swapper_wizard.py b/chained_swapper/wizard/chained_swapper_wizard.py
index 2992504bd3..9ae3b9550d 100644
--- a/chained_swapper/wizard/chained_swapper_wizard.py
+++ b/chained_swapper/wizard/chained_swapper_wizard.py
@@ -48,7 +48,10 @@ def fields_view_get(
corresponding field.
"""
res = super().fields_view_get(
- view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu,
+ view_id=view_id,
+ view_type=view_type,
+ toolbar=toolbar,
+ submenu=submenu,
)
if not self.env.context.get("chained_swapper_id"):
return res
From e98dc104a61be9f2b909ba2463929a86aac651f4 Mon Sep 17 00:00:00 2001
From: schhatbar-initos
Date: Thu, 25 Feb 2021 15:32:54 +0530
Subject: [PATCH 08/18] [14.0][MIG] chained_swapper
---
chained_swapper/README.rst | 12 +-
chained_swapper/demo/chained_swapper_demo.xml | 23 +-
chained_swapper/i18n/chained_swapper.pot | 2 +-
chained_swapper/i18n/it.po | 296 ++++++++++++++++++
chained_swapper/models/chained_swapper.py | 2 +
chained_swapper/readme/CONTRIBUTORS.rst | 2 +
chained_swapper/security/ir.model.access.csv | 1 +
chained_swapper/static/description/index.html | 7 +-
chained_swapper/tests/test_chained_swapper.py | 31 +-
9 files changed, 340 insertions(+), 36 deletions(-)
create mode 100644 chained_swapper/i18n/it.po
diff --git a/chained_swapper/README.rst b/chained_swapper/README.rst
index 3c52e351ec..5481a98c52 100644
--- a/chained_swapper/README.rst
+++ b/chained_swapper/README.rst
@@ -14,13 +14,13 @@ Chained Swapper
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--ux-lightgray.png?logo=github
- :target: https://github.com/OCA/server-ux/tree/13.0/chained_swapper
+ :target: https://github.com/OCA/server-ux/tree/14.0/chained_swapper
:alt: OCA/server-ux
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/server-ux-13-0/server-ux-13-0-chained_swapper
+ :target: https://translation.odoo-community.org/projects/server-ux-14-0/server-ux-14-0-chained_swapper
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/250/13.0
+ :target: https://runbot.odoo-community.org/runbot/250/14.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -100,7 +100,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -120,6 +120,8 @@ Contributors
* Ernesto Tejeda
* Pedro M. Baeza
+* Sunanda Chhatbar
+
Maintainers
~~~~~~~~~~~
@@ -133,6 +135,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-This module is part of the `OCA/server-ux `_ project on GitHub.
+This module is part of the `OCA/server-ux `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/chained_swapper/demo/chained_swapper_demo.xml b/chained_swapper/demo/chained_swapper_demo.xml
index 61570d7947..360dd6f5da 100644
--- a/chained_swapper/demo/chained_swapper_demo.xml
+++ b/chained_swapper/demo/chained_swapper_demo.xml
@@ -19,15 +19,20 @@
bool(records.mapped('parent_id'))
-
+
+ Chained swap: Language
+ chained.swapper.wizard
+ form
+ new
+
+ form
+
+ \n"
+"Language-Team: none\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.3.2\n"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:0
+#, python-format
+msgid "Chained swap done:"
+msgstr "Sostituzione a catena effettuata:"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__ref_ir_act_window_id
+msgid "Action"
+msgstr "Azione"
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper__ref_ir_act_window_id
+msgid ""
+"Action to make this template available on records of the related document "
+"model."
+msgstr ""
+"Azione per rendere questo template disponibile sui record del modello del "
+"documento relazionato."
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Add Action"
+msgstr "Aggiungi azione"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid ""
+"Add a new contextual action of related documents to open a composition "
+"wizard"
+msgstr ""
+"Aggiungi una nuova azione contestuale per i documenti relazionati per aprire "
+"una wizard di sostituzione a catena"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Advanced"
+msgstr "Avanzato"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__allowed_field_ids
+msgid "Allowed Field"
+msgstr "Campi permessi"
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_constraint__expression
+msgid ""
+"Boolean python expression. You can use the keyword 'records' as the records "
+"selected to execute the contextual action. Ex.: "
+"bool(records.mapped('parent_id'))"
+msgstr ""
+"Espressione Python booleana. Puoi utilizzare la parola chiave 'records' per "
+"riferirti ai record selezionati per eseguire l'azione contestuale. Es: "
+"bool(records.mapped('parent_id'))"
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__chained_swapper_id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__chained_swapper_id
+msgid "Chained Swapper"
+msgstr "Sostitutore a catena"
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper_constraint
+msgid "Chained Swapper Constraint"
+msgstr "Restrizione Sostitutore a catena"
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper_sub_field
+msgid "Chained Swapper Sub-field"
+msgstr "Sottocampo Sostitutore a catena"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:0
+#, python-format
+msgid "Chained swap"
+msgstr "Sostituzione a catena"
+
+#. module: chained_swapper
+#: model:ir.actions.act_window,name:chained_swapper.partner_chained_swap_lang_action_demo
+msgid "Chained swap: Language"
+msgstr "Sostituzione a catena: Lingua"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
+msgid "Change"
+msgstr "Cambia"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_wizard_view_form
+msgid "Close"
+msgstr "Chiudi"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__expression
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_constraint_view_form
+msgid "Constraint expression"
+msgstr "Espressione di restrizione"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__constraint_ids
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Constraints"
+msgstr "Restrizioni"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__create_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__create_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__create_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__create_uid
+msgid "Created by"
+msgstr "Creato da"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__create_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__create_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__create_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__create_date
+msgid "Created on"
+msgstr "Creato il"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__display_name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__display_name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__display_name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__display_name
+msgid "Display Name"
+msgstr "Nome Visualizzato"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__field_id
+msgid "Field"
+msgstr "Campo"
+
+#. module: chained_swapper
+#: model:ir.actions.act_window,name:chained_swapper.chained_swapper_action
+#: model:ir.ui.menu,name:chained_swapper.chained_swapper_menu
+#: model:ir.ui.menu,name:chained_swapper.chained_swapper_submenu
+msgid "Field Swaps"
+msgstr "Sostituzioni campi"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__group_ids
+msgid "Groups"
+msgstr "Gruppi"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__id
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__id
+msgid "ID"
+msgstr "ID"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:0
+#, python-format
+msgid "Incorrect sub-field expression:"
+msgstr "Espressione sottocampo non corretta:"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:0
+#, python-format
+msgid "Invalid constraint expression: "
+msgstr "Espressione di restrizione non valida: "
+
+#. module: chained_swapper
+#: model:chained.swapper,name:chained_swapper.chained_swapper_demo
+msgid "Language"
+msgstr "Lingua"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper____last_update
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint____last_update
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field____last_update
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard____last_update
+msgid "Last Modified on"
+msgstr "Ultima modifica il"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__write_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__write_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__write_uid
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__write_uid
+msgid "Last Updated by"
+msgstr "Ultimo aggiornamento di"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__write_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__write_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__write_date
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__write_date
+msgid "Last Updated on"
+msgstr "Ultimo aggiornamento il"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__model_id
+msgid "Model"
+msgstr "Modello"
+
+#. module: chained_swapper
+#: model:ir.model.constraint,message:chained_swapper.constraint_chained_swapper_model_id_field_id_unique
+msgid "Model and Field must be unique!"
+msgstr "Campo e Modello devono essere univoci!"
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper__model_id
+msgid ""
+"Model is used for Selecting Field. This is editable until Contextual Action "
+"is not created."
+msgstr ""
+"Il modello è utilizzato per selezionare il campo. Questo è modificabile solo "
+"fino alla creazione di un'azione contestuale."
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__name
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_constraint__name
+msgid "Name"
+msgstr "Nome"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:0
+#, python-format
+msgid "Not possible to swap the field due to the constraint"
+msgstr "Non è possibile sostituire il campo a causa della restrizione"
+
+#. module: chained_swapper
+#: model:chained.swapper.constraint,name:chained_swapper.chained_swapper_constraint_demo
+msgid "Only parent company"
+msgstr "Solo azienda padre"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Remove Action"
+msgstr "Rimuovi azione"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Remove the contextual action to use this template on related documents"
+msgstr ""
+"Rimuove l'azione contestuale per usare questo template in documenti "
+"relazionati"
+
+#. module: chained_swapper
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Security"
+msgstr "Sicurezza"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__sub_field_chain
+msgid "Sub Field Chain"
+msgstr "Catena del sottocampo"
+
+#. module: chained_swapper
+#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__sub_field_ids
+#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
+msgid "Sub-fields"
+msgstr "Sottocampi"
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:0
+#, python-format
+msgid "The sub-field '%s' is not compatible with the main field."
+msgstr "Il sottocampo '%s' non è compatibile con il campo principale."
+
+#. module: chained_swapper
+#: model:ir.model,name:chained_swapper.model_chained_swapper_wizard
+msgid "Wizard chained swapper"
+msgstr "Wizard sostitutore a catena"
+
+#. module: chained_swapper
+#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_sub_field__sub_field_chain
+msgid ""
+"You can specify here a field of related fields as dotted names. Ex.: "
+"'child_ids.lang'."
+msgstr ""
+"Puoi specificare qui un campo di campi relazionati usando un nome separato "
+"da punti. Es: 'child_ids.lang'."
diff --git a/chained_swapper/models/chained_swapper.py b/chained_swapper/models/chained_swapper.py
index df2e714a3e..b76b7634fe 100644
--- a/chained_swapper/models/chained_swapper.py
+++ b/chained_swapper/models/chained_swapper.py
@@ -14,6 +14,7 @@ class ChainedSwapper(models.Model):
model_id = fields.Many2one(
comodel_name="ir.model",
required=True,
+ ondelete="cascade",
help="Model is used for Selecting Field. This is editable "
"until Contextual Action is not created.",
)
@@ -23,6 +24,7 @@ class ChainedSwapper(models.Model):
field_id = fields.Many2one(
comodel_name="ir.model.fields",
required=True,
+ ondelete="cascade",
domain="[('id', 'in', allowed_field_ids)]",
)
sub_field_ids = fields.One2many(
diff --git a/chained_swapper/readme/CONTRIBUTORS.rst b/chained_swapper/readme/CONTRIBUTORS.rst
index b31cef3217..e1109c7ef8 100644
--- a/chained_swapper/readme/CONTRIBUTORS.rst
+++ b/chained_swapper/readme/CONTRIBUTORS.rst
@@ -2,3 +2,5 @@
* Ernesto Tejeda
* Pedro M. Baeza
+
+* Sunanda Chhatbar
diff --git a/chained_swapper/security/ir.model.access.csv b/chained_swapper/security/ir.model.access.csv
index 5f6c72b95b..4bd1828850 100644
--- a/chained_swapper/security/ir.model.access.csv
+++ b/chained_swapper/security/ir.model.access.csv
@@ -5,3 +5,4 @@ access_chained_swapper_sub_field_user,chained.swapper.sub.field.user,model_chain
access_chained_swapper_sub_field_erp_manager,chained.swapper.sub.field.erp_manager,model_chained_swapper_sub_field,base.group_erp_manager,1,1,1,1
access_chained_swapper_constraint_user,chained.swapper.constraint.user,model_chained_swapper_constraint,base.group_user,1,0,0,0
access_chained_swapper_constraint_erp_manager,chained.swapper.constraint.erp_manager,model_chained_swapper_constraint,base.group_erp_manager,1,1,1,1
+access_chained_swapper_wizard,chained.swapper.wizard.constraint.erp_manager,model_chained_swapper_wizard,base.group_user,1,1,1,1
diff --git a/chained_swapper/static/description/index.html b/chained_swapper/static/description/index.html
index a976f27cc7..20e2be89df 100644
--- a/chained_swapper/static/description/index.html
+++ b/chained_swapper/static/description/index.html
@@ -367,7 +367,7 @@
Chained Swapper
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
+
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.
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-feedback.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
This module is part of the OCA/server-ux project on GitHub.
+
This module is part of the OCA/server-ux project on GitHub.
diff --git a/chained_swapper/tests/test_chained_swapper.py b/chained_swapper/tests/test_chained_swapper.py
index 8b7568d329..7d2e9d280e 100644
--- a/chained_swapper/tests/test_chained_swapper.py
+++ b/chained_swapper/tests/test_chained_swapper.py
@@ -5,6 +5,7 @@
from odoo import exceptions
from odoo.modules import registry
from odoo.tests import common
+from odoo.tests.common import Form
from ..hooks import uninstall_hook
@@ -28,24 +29,18 @@ def setUpClass(cls):
record = cls.env.ref("chained_swapper.chained_swapper_demo", False)
if record:
record.unlink()
- cls.chained_swapper = cls.env["chained.swapper"].create(
- {
- "name": "Language",
- "model_id": cls.env.ref("base.model_res_partner").id,
- "field_id": cls.env.ref("base.field_res_partner__lang").id,
- "sub_field_ids": [(0, 0, {"sub_field_chain": "child_ids.lang"})],
- "constraint_ids": [
- (
- 0,
- 0,
- {
- "name": "Only parent company",
- "expression": "bool(records.mapped('parent_id'))",
- },
- )
- ],
- }
- )
+
+ chained_swapper_form = Form(cls.env["chained.swapper"])
+ chained_swapper_form.name = "Language"
+ chained_swapper_form.model_id = cls.env.ref("base.model_res_partner")
+ chained_swapper_form.field_id = cls.env.ref("base.field_res_partner__lang")
+ with chained_swapper_form.sub_field_ids.new() as sub_field_form:
+ sub_field_form.sub_field_chain = "child_ids.lang"
+ with chained_swapper_form.constraint_ids.new() as constraint_form:
+ constraint_form.name = "Only parent company"
+ constraint_form.expression = "bool(records.mapped('parent_id'))"
+
+ cls.chained_swapper = chained_swapper_form.save()
cls.chained_swapper.add_action()
def test_create_unlink_action(self):
From 2dce6ea10baa8b1f9ce2d3df6c98cada37812099 Mon Sep 17 00:00:00 2001
From: Cesar Andres Sanchez
Date: Mon, 6 Jun 2022 18:32:36 +0200
Subject: [PATCH 09/18] MIG] chained_swapper: Migration to 15.0
---
chained_swapper/README.rst | 16 +++---
chained_swapper/__manifest__.py | 3 +-
chained_swapper/i18n/chained_swapper.pot | 12 +++--
chained_swapper/i18n/es.po | 53 ++++++++++---------
chained_swapper/i18n/it.po | 37 +++++++------
chained_swapper/models/chained_swapper.py | 20 ++++---
chained_swapper/readme/CONTRIBUTORS.rst | 2 +-
chained_swapper/static/description/index.html | 7 +--
8 files changed, 88 insertions(+), 62 deletions(-)
diff --git a/chained_swapper/README.rst b/chained_swapper/README.rst
index 5481a98c52..246a322fdd 100644
--- a/chained_swapper/README.rst
+++ b/chained_swapper/README.rst
@@ -7,20 +7,20 @@ Chained Swapper
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+.. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png
:target: https://odoo-community.org/page/development-status
- :alt: Beta
+ :alt: Mature
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--ux-lightgray.png?logo=github
- :target: https://github.com/OCA/server-ux/tree/14.0/chained_swapper
+ :target: https://github.com/OCA/server-ux/tree/15.0/chained_swapper
:alt: OCA/server-ux
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/server-ux-14-0/server-ux-14-0-chained_swapper
+ :target: https://translation.odoo-community.org/projects/server-ux-15-0/server-ux-15-0-chained_swapper
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/250/14.0
+ :target: https://runbot.odoo-community.org/runbot/250/15.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -100,7 +100,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -119,7 +119,7 @@ Contributors
* Ernesto Tejeda
* Pedro M. Baeza
-
+ * César A. Sánchez
* Sunanda Chhatbar
Maintainers
@@ -135,6 +135,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-This module is part of the `OCA/server-ux `_ project on GitHub.
+This module is part of the `OCA/server-ux `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/chained_swapper/__manifest__.py b/chained_swapper/__manifest__.py
index 6031a3fa1a..ae88250b79 100644
--- a/chained_swapper/__manifest__.py
+++ b/chained_swapper/__manifest__.py
@@ -3,7 +3,8 @@
{
"name": "Chained Swapper",
"summary": "Chained Swapper",
- "version": "14.0.1.0.0",
+ "version": "15.0.1.0.0",
+ "development_status": "Mature",
"author": "Tecnativa, Odoo Community Association (OCA)",
"category": "Tools",
"website": "https://github.com/OCA/server-ux",
diff --git a/chained_swapper/i18n/chained_swapper.pot b/chained_swapper/i18n/chained_swapper.pot
index 7b0b71a52b..79affa394b 100644
--- a/chained_swapper/i18n/chained_swapper.pot
+++ b/chained_swapper/i18n/chained_swapper.pot
@@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 14.0\n"
+"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -163,13 +163,19 @@ msgstr ""
#. module: chained_swapper
#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
-msgid "Incorrect sub-field expression:"
+msgid "Incorrect sub-field expression: %(sub_field_chain)s. %(error)s"
msgstr ""
#. module: chained_swapper
#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
-msgid "Invalid constraint expression: "
+msgid "Invalid constraint expression: %(expression)s. %(error)s."
+msgstr ""
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:0
+#, python-format
+msgid "Invalid value for %(sub_field_chain)s. %(error)s"
msgstr ""
#. module: chained_swapper
diff --git a/chained_swapper/i18n/es.po b/chained_swapper/i18n/es.po
index 05a68e20a0..bb2e369fb6 100644
--- a/chained_swapper/i18n/es.po
+++ b/chained_swapper/i18n/es.po
@@ -4,21 +4,19 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 12.0\n"
+"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-08-14 10:32+0000\n"
-"PO-Revision-Date: 2020-08-14 12:33+0200\n"
-"Last-Translator: <>\n"
+"POT-Creation-Date: 2022-06-28 21:43+0000\n"
+"PO-Revision-Date: 2022-06-28 21:43+0000\n"
+"Last-Translator: \n"
"Language-Team: \n"
-"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
+"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
-"X-Generator: Poedit 2.3\n"
#. module: chained_swapper
-#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:146
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:0
#, python-format
msgid "Chained swap done:"
msgstr "Cambio encadenado realizado:"
@@ -45,7 +43,8 @@ msgstr "Añadir acción"
#. module: chained_swapper
#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
msgid ""
-"Add a new contextual action of related documents to open a composition wizard"
+"Add a new contextual action of related documents to open a composition "
+"wizard"
msgstr ""
"Añade una acción contextual para los documentos relacionados para abrir un "
"asistente de intercambio encadenado"
@@ -64,8 +63,8 @@ msgstr "Campos permitidos"
#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_constraint__expression
msgid ""
"Boolean python expression. You can use the keyword 'records' as the records "
-"selected to execute the contextual action. Ex.: bool(records."
-"mapped('parent_id'))"
+"selected to execute the contextual action. Ex.: "
+"bool(records.mapped('parent_id'))"
msgstr ""
"Expresión Python booleana. Puede usar la palabra clave 'records' para hacer "
"referencia a los registros seleccionados al ejecutar la acción contextual. "
@@ -89,7 +88,7 @@ msgid "Chained Swapper Sub-field"
msgstr "Intercambiador encadenado - Sub-campos"
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:104
+#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
msgid "Chained swap"
msgstr "Cambio encadenado"
@@ -168,19 +167,25 @@ msgstr "Grupos"
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__id
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__id
msgid "ID"
-msgstr "ID"
+msgstr "ID (Identificador)"
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:151
+#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
-msgid "Incorrect sub-field expression:"
-msgstr "Expresión de sub-campo incorrecta:"
+msgid "Incorrect sub-field expression: %(sub_field_chain)s. %(error)s"
+msgstr "Expresión incorrecta: %(sub_field_chain)s. %(error)s"
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:193
+#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
-msgid "Invalid constraint expression: "
-msgstr "Expresión de restricción no válida:"
+msgid "Invalid constraint expression: %(expression)s. %(error)s."
+msgstr "Expresión incorrecta: %(expression)s. %(error)s."
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:0
+#, python-format
+msgid "Invalid value for %(sub_field_chain)s. %(error)s"
+msgstr "Valor incorrecto para %(sub_field_chain)s. %(error)s"
#. module: chained_swapper
#: model:chained.swapper,name:chained_swapper.chained_swapper_demo
@@ -217,7 +222,7 @@ msgid "Model"
msgstr "Modelo"
#. module: chained_swapper
-#: sql_constraint:chained.swapper:0
+#: model:ir.model.constraint,message:chained_swapper.constraint_chained_swapper_model_id_field_id_unique
msgid "Model and Field must be unique!"
msgstr "La combinación Modelo-Campo debe ser única!"
@@ -227,8 +232,8 @@ msgid ""
"Model is used for Selecting Field. This is editable until Contextual Action "
"is not created."
msgstr ""
-"El modelo se utiliza para seleccionar el campo. Este es editable mientras la "
-"acción contextual no esté creada."
+"El modelo se utiliza para seleccionar el campo. Este es editable mientras la"
+" acción contextual no esté creada."
#. module: chained_swapper
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__name
@@ -237,7 +242,7 @@ msgid "Name"
msgstr "Nombre"
#. module: chained_swapper
-#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:32
+#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:0
#, python-format
msgid "Not possible to swap the field due to the constraint"
msgstr "No es posible cambiar el campo debido a la restricción"
@@ -276,7 +281,7 @@ msgid "Sub-fields"
msgstr "Sub-campo"
#. module: chained_swapper
-#: code:addons/chained_swapper/models/chained_swapper.py:162
+#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
msgid "The sub-field '%s' is not compatible with the main field."
msgstr "El sub-campo '%s' no es compatible con el campo principal."
diff --git a/chained_swapper/i18n/it.po b/chained_swapper/i18n/it.po
index 380c32961e..4858a812f0 100644
--- a/chained_swapper/i18n/it.po
+++ b/chained_swapper/i18n/it.po
@@ -4,17 +4,16 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 14.0\n"
+"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2022-03-21 18:32+0000\n"
-"Last-Translator: Francesco Foresti \n"
-"Language-Team: none\n"
-"Language: it\n"
+"POT-Creation-Date: 2022-06-28 21:43+0000\n"
+"PO-Revision-Date: 2022-06-28 21:43+0000\n"
+"Last-Translator: \n"
+"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
-"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 4.3.2\n"
+"Plural-Forms: \n"
#. module: chained_swapper
#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:0
@@ -47,8 +46,8 @@ msgid ""
"Add a new contextual action of related documents to open a composition "
"wizard"
msgstr ""
-"Aggiungi una nuova azione contestuale per i documenti relazionati per aprire "
-"una wizard di sostituzione a catena"
+"Aggiungi una nuova azione contestuale per i documenti relazionati per aprire"
+" una wizard di sostituzione a catena"
#. module: chained_swapper
#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
@@ -168,19 +167,25 @@ msgstr "Gruppi"
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__id
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__id
msgid "ID"
-msgstr "ID"
+msgstr ""
+
+#. module: chained_swapper
+#: code:addons/chained_swapper/models/chained_swapper.py:0
+#, python-format
+msgid "Incorrect sub-field expression: %(sub_field_chain)s. %(error)s"
+msgstr ""
#. module: chained_swapper
#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
-msgid "Incorrect sub-field expression:"
-msgstr "Espressione sottocampo non corretta:"
+msgid "Invalid constraint expression: %(expression)s. %(error)s."
+msgstr ""
#. module: chained_swapper
#: code:addons/chained_swapper/models/chained_swapper.py:0
#, python-format
-msgid "Invalid constraint expression: "
-msgstr "Espressione di restrizione non valida: "
+msgid "Invalid value for %(sub_field_chain)s. %(error)s"
+msgstr ""
#. module: chained_swapper
#: model:chained.swapper,name:chained_swapper.chained_swapper_demo
@@ -227,8 +232,8 @@ msgid ""
"Model is used for Selecting Field. This is editable until Contextual Action "
"is not created."
msgstr ""
-"Il modello è utilizzato per selezionare il campo. Questo è modificabile solo "
-"fino alla creazione di un'azione contestuale."
+"Il modello è utilizzato per selezionare il campo. Questo è modificabile solo"
+" fino alla creazione di un'azione contestuale."
#. module: chained_swapper
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__name
diff --git a/chained_swapper/models/chained_swapper.py b/chained_swapper/models/chained_swapper.py
index b76b7634fe..624c0b0441 100644
--- a/chained_swapper/models/chained_swapper.py
+++ b/chained_swapper/models/chained_swapper.py
@@ -3,6 +3,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, exceptions, fields, models
+from odoo.exceptions import ValidationError
from odoo.tools.safe_eval import safe_eval
@@ -145,10 +146,16 @@ def _check_sub_field_chain(self):
for name in chain_list:
chain_model = chain_model[name]
chain_model[chain_field_name] # pylint: disable=W0104
- except KeyError:
+ except KeyError as err:
raise exceptions.ValidationError(
- _("Incorrect sub-field expression:") + " " + rec.sub_field_chain
- )
+ _("Incorrect sub-field expression: %(sub_field_chain)s. %(error)s")
+ % {"sub_field_chain": rec.sub_field_chain, "error": err}
+ ) from err
+ except Exception as err:
+ raise ValidationError(
+ _("Invalid value for %(sub_field_chain)s. %(error)s")
+ % {"sub_field_chain": rec.sub_field_chain, "error": err}
+ ) from err
# Check sub-field and original field are the same type
swap_field = rec.chained_swapper_id.field_id
chain_field = self.env["ir.model.fields"].search(
@@ -190,7 +197,8 @@ def _check_expression(self):
model = self.env[record.chained_swapper_id.model_id.model]
try:
safe_eval(record.expression, {"records": model})
- except Exception:
+ except Exception as err:
raise exceptions.ValidationError(
- _("Invalid constraint expression:" + " " + record.expression)
- )
+ _("Invalid constraint expression: %(expression)s. %(error)s.")
+ % {"expression": record.expression, "error": err}
+ ) from err
diff --git a/chained_swapper/readme/CONTRIBUTORS.rst b/chained_swapper/readme/CONTRIBUTORS.rst
index e1109c7ef8..c38c23eca8 100644
--- a/chained_swapper/readme/CONTRIBUTORS.rst
+++ b/chained_swapper/readme/CONTRIBUTORS.rst
@@ -2,5 +2,5 @@
* Ernesto Tejeda
* Pedro M. Baeza
-
+ * César A. Sánchez
* Sunanda Chhatbar
diff --git a/chained_swapper/static/description/index.html b/chained_swapper/static/description/index.html
index 20e2be89df..b5c9926aa8 100644
--- a/chained_swapper/static/description/index.html
+++ b/chained_swapper/static/description/index.html
@@ -367,7 +367,7 @@
Chained Swapper
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-
+
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.
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-feedback.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
This module is part of the OCA/server-ux project on GitHub.
+
This module is part of the OCA/server-ux project on GitHub.
From 39378a0d3b00214597990da84e2d3d798f7c5ea3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Enrique=20Mart=C3=ADn?=
Date: Fri, 11 Mar 2022 11:41:16 +0100
Subject: [PATCH 10/18] [FIX] chained_swapper - search chain field with correct
model
---
chained_swapper/__manifest__.py | 2 +-
chained_swapper/models/chained_swapper.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/chained_swapper/__manifest__.py b/chained_swapper/__manifest__.py
index ae88250b79..3794e4f7bf 100644
--- a/chained_swapper/__manifest__.py
+++ b/chained_swapper/__manifest__.py
@@ -3,7 +3,7 @@
{
"name": "Chained Swapper",
"summary": "Chained Swapper",
- "version": "15.0.1.0.0",
+ "version": "15.0.1.0.1",
"development_status": "Mature",
"author": "Tecnativa, Odoo Community Association (OCA)",
"category": "Tools",
diff --git a/chained_swapper/models/chained_swapper.py b/chained_swapper/models/chained_swapper.py
index 624c0b0441..c2cb9f969a 100644
--- a/chained_swapper/models/chained_swapper.py
+++ b/chained_swapper/models/chained_swapper.py
@@ -160,7 +160,7 @@ def _check_sub_field_chain(self):
swap_field = rec.chained_swapper_id.field_id
chain_field = self.env["ir.model.fields"].search(
[
- ("model_id", "=", rec.chained_swapper_id.model_id.id),
+ ("model_id.model", "=", chain_model._name),
("name", "=", chain_field_name),
]
)
From d6a05e17d19b5ef1c7d551c41f0fe5853982532e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?=
Date: Wed, 23 Nov 2022 12:16:02 +0100
Subject: [PATCH 11/18] [FIX] chained_swapper: Change SavepointCase to
TransactionCase
---
chained_swapper/tests/test_chained_swapper.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/chained_swapper/tests/test_chained_swapper.py b/chained_swapper/tests/test_chained_swapper.py
index 7d2e9d280e..107f9d73fc 100644
--- a/chained_swapper/tests/test_chained_swapper.py
+++ b/chained_swapper/tests/test_chained_swapper.py
@@ -10,7 +10,7 @@
from ..hooks import uninstall_hook
-class TestChainedSwapper(common.SavepointCase):
+class TestChainedSwapper(common.TransactionCase):
@classmethod
def setUpClass(cls):
super(TestChainedSwapper, cls).setUpClass()
From b20ce574eef8a791f2db9c8c47fc719bbd3041da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?=
Date: Wed, 23 Nov 2022 12:16:42 +0100
Subject: [PATCH 12/18] [FIX] chained_swapper: Change load_lang method to
_activate_lang to prevent warning log
---
chained_swapper/README.rst | 15 ++++---
chained_swapper/__manifest__.py | 2 +-
chained_swapper/i18n/es.po | 12 +++---
chained_swapper/i18n/it.po | 25 +++++------
chained_swapper/static/description/index.html | 42 ++++++++++---------
chained_swapper/tests/test_chained_swapper.py | 2 +-
6 files changed, 52 insertions(+), 46 deletions(-)
diff --git a/chained_swapper/README.rst b/chained_swapper/README.rst
index 246a322fdd..4a26fa7123 100644
--- a/chained_swapper/README.rst
+++ b/chained_swapper/README.rst
@@ -2,10 +2,13 @@
Chained Swapper
===============
-.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:419c4bfb877b7e16457a1b158e53f15e07785c85cdafbd81759b485ae1f17d55
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png
:target: https://odoo-community.org/page/development-status
@@ -19,11 +22,11 @@ Chained Swapper
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-ux-15-0/server-ux-15-0-chained_swapper
:alt: Translate me on Weblate
-.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/250/15.0
- :alt: Try me on Runbot
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/server-ux&target_branch=15.0
+ :alt: Try me on Runboat
-|badge1| |badge2| |badge3| |badge4| |badge5|
+|badge1| |badge2| |badge3| |badge4| |badge5|
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
@@ -99,7 +102,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
-If you spotted it first, help us smashing it by providing a detailed and welcomed
+If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback `_.
Do not contact contributors directly about support or help with technical issues.
diff --git a/chained_swapper/__manifest__.py b/chained_swapper/__manifest__.py
index 3794e4f7bf..eed871b8e3 100644
--- a/chained_swapper/__manifest__.py
+++ b/chained_swapper/__manifest__.py
@@ -3,7 +3,7 @@
{
"name": "Chained Swapper",
"summary": "Chained Swapper",
- "version": "15.0.1.0.1",
+ "version": "15.0.1.0.2",
"development_status": "Mature",
"author": "Tecnativa, Odoo Community Association (OCA)",
"category": "Tools",
diff --git a/chained_swapper/i18n/es.po b/chained_swapper/i18n/es.po
index bb2e369fb6..245385b64e 100644
--- a/chained_swapper/i18n/es.po
+++ b/chained_swapper/i18n/es.po
@@ -10,6 +10,7 @@ msgstr ""
"PO-Revision-Date: 2022-06-28 21:43+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
+"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
@@ -43,8 +44,7 @@ msgstr "Añadir acción"
#. module: chained_swapper
#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
msgid ""
-"Add a new contextual action of related documents to open a composition "
-"wizard"
+"Add a new contextual action of related documents to open a composition wizard"
msgstr ""
"Añade una acción contextual para los documentos relacionados para abrir un "
"asistente de intercambio encadenado"
@@ -63,8 +63,8 @@ msgstr "Campos permitidos"
#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_constraint__expression
msgid ""
"Boolean python expression. You can use the keyword 'records' as the records "
-"selected to execute the contextual action. Ex.: "
-"bool(records.mapped('parent_id'))"
+"selected to execute the contextual action. Ex.: bool(records."
+"mapped('parent_id'))"
msgstr ""
"Expresión Python booleana. Puede usar la palabra clave 'records' para hacer "
"referencia a los registros seleccionados al ejecutar la acción contextual. "
@@ -232,8 +232,8 @@ msgid ""
"Model is used for Selecting Field. This is editable until Contextual Action "
"is not created."
msgstr ""
-"El modelo se utiliza para seleccionar el campo. Este es editable mientras la"
-" acción contextual no esté creada."
+"El modelo se utiliza para seleccionar el campo. Este es editable mientras la "
+"acción contextual no esté creada."
#. module: chained_swapper
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__name
diff --git a/chained_swapper/i18n/it.po b/chained_swapper/i18n/it.po
index 4858a812f0..bb9ae02693 100644
--- a/chained_swapper/i18n/it.po
+++ b/chained_swapper/i18n/it.po
@@ -7,13 +7,15 @@ msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-06-28 21:43+0000\n"
-"PO-Revision-Date: 2022-06-28 21:43+0000\n"
-"Last-Translator: \n"
+"PO-Revision-Date: 2023-06-07 13:09+0000\n"
+"Last-Translator: mymage \n"
"Language-Team: \n"
+"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
-"Plural-Forms: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.17\n"
#. module: chained_swapper
#: code:addons/chained_swapper/wizard/chained_swapper_wizard.py:0
@@ -43,11 +45,10 @@ msgstr "Aggiungi azione"
#. module: chained_swapper
#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
msgid ""
-"Add a new contextual action of related documents to open a composition "
-"wizard"
+"Add a new contextual action of related documents to open a composition wizard"
msgstr ""
-"Aggiungi una nuova azione contestuale per i documenti relazionati per aprire"
-" una wizard di sostituzione a catena"
+"Aggiungi una nuova azione contestuale per i documenti relazionati per aprire "
+"una wizard di sostituzione a catena"
#. module: chained_swapper
#: model_terms:ir.ui.view,arch_db:chained_swapper.chained_swapper_view_form
@@ -63,8 +64,8 @@ msgstr "Campi permessi"
#: model:ir.model.fields,help:chained_swapper.field_chained_swapper_constraint__expression
msgid ""
"Boolean python expression. You can use the keyword 'records' as the records "
-"selected to execute the contextual action. Ex.: "
-"bool(records.mapped('parent_id'))"
+"selected to execute the contextual action. Ex.: bool(records."
+"mapped('parent_id'))"
msgstr ""
"Espressione Python booleana. Puoi utilizzare la parola chiave 'records' per "
"riferirti ai record selezionati per eseguire l'azione contestuale. Es: "
@@ -142,7 +143,7 @@ msgstr "Creato il"
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_sub_field__display_name
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper_wizard__display_name
msgid "Display Name"
-msgstr "Nome Visualizzato"
+msgstr "Nome visualizzato"
#. module: chained_swapper
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__field_id
@@ -232,8 +233,8 @@ msgid ""
"Model is used for Selecting Field. This is editable until Contextual Action "
"is not created."
msgstr ""
-"Il modello è utilizzato per selezionare il campo. Questo è modificabile solo"
-" fino alla creazione di un'azione contestuale."
+"Il modello è utilizzato per selezionare il campo. Questo è modificabile solo "
+"fino alla creazione di un'azione contestuale."
#. module: chained_swapper
#: model:ir.model.fields,field_description:chained_swapper.field_chained_swapper__name
diff --git a/chained_swapper/static/description/index.html b/chained_swapper/static/description/index.html
index b5c9926aa8..0195896f9a 100644
--- a/chained_swapper/static/description/index.html
+++ b/chained_swapper/static/description/index.html
@@ -1,20 +1,20 @@
-
+
-
+
Chained Swapper
-
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
@@ -397,7 +402,7 @@
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
@@ -463,27 +468,29 @@