From 0a12ce4138ba0628e3ac0a4abcd2fd26e501f939 Mon Sep 17 00:00:00 2001 From: Tom Blauwendraat Date: Thu, 28 Jan 2021 10:04:06 +0100 Subject: [PATCH 01/16] [ADD] sequence_python --- sequence_python/README.rst | 113 +++++ sequence_python/__init__.py | 1 + sequence_python/__manifest__.py | 13 + sequence_python/i18n/sequence_python.pot | 108 +++++ sequence_python/models/__init__.py | 1 + sequence_python/models/ir_sequence.py | 78 +++ sequence_python/readme/CONTRIBUTORS.rst | 1 + sequence_python/readme/DESCRIPTION.rst | 16 + sequence_python/readme/USAGE.rst | 21 + sequence_python/static/description/icon.png | Bin 0 -> 9455 bytes sequence_python/static/description/index.html | 455 ++++++++++++++++++ sequence_python/tests/__init__.py | 3 + sequence_python/tests/test_ir_sequence.py | 39 ++ sequence_python/views/ir_sequence.xml | 52 ++ 14 files changed, 901 insertions(+) create mode 100644 sequence_python/README.rst create mode 100644 sequence_python/__init__.py create mode 100644 sequence_python/__manifest__.py create mode 100644 sequence_python/i18n/sequence_python.pot create mode 100644 sequence_python/models/__init__.py create mode 100644 sequence_python/models/ir_sequence.py create mode 100644 sequence_python/readme/CONTRIBUTORS.rst create mode 100644 sequence_python/readme/DESCRIPTION.rst create mode 100644 sequence_python/readme/USAGE.rst create mode 100644 sequence_python/static/description/icon.png create mode 100644 sequence_python/static/description/index.html create mode 100644 sequence_python/tests/__init__.py create mode 100644 sequence_python/tests/test_ir_sequence.py create mode 100644 sequence_python/views/ir_sequence.xml diff --git a/sequence_python/README.rst b/sequence_python/README.rst new file mode 100644 index 00000000000..51a1cdbd471 --- /dev/null +++ b/sequence_python/README.rst @@ -0,0 +1,113 @@ +=============================== +Sequence from Python expression +=============================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/13.0/sequence_python + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-13-0/server-tools-13-0-sequence_python + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/149/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to generate a sequence by a Python formula expression. + +Besides common Python functions and operators, it provides several functions +like 'random' and 'uuid' in the expression, as well as variables such as the +next number in the sequence. These are also listed next to the input field +on the sequence form view. + +If you want to add more variables for use in the expression, you can extend +this module. + +Use cases for this module could be: + +- You want to generate alphanumeric numbering +- You want to apply some math to the number to prevent customers from knowing + their place in the sequence +- You want to use UUID + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need to: + +* Go to the form view of an `ir.sequence` record +* Go to the Python tab +* Enable the 'Use Python' checkbox +* Change the default 'number' expression to something more fancy. + +Examples: + +.. code-block:: python + + # To separate the Odoo-generated number with hyphens eg. 0-0-0-0-1 + '-'.join(number_padded) + + # To have an UUID as the sequence value + uuid.uuid4().hex + + # To use an 8-digit binary number + '{0:#010b}'.format(number + 300)[2:] + +And so on. + +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 +~~~~~~~ + +* Sunflower IT + +Contributors +~~~~~~~~~~~~ + +* Tom Blauwendraat + +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-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sequence_python/__init__.py b/sequence_python/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/sequence_python/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sequence_python/__manifest__.py b/sequence_python/__manifest__.py new file mode 100644 index 00000000000..9b6b1eeb83e --- /dev/null +++ b/sequence_python/__manifest__.py @@ -0,0 +1,13 @@ +# Copyright 2020 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Sequence from Python expression", + "summary": """Calculate a sequence number from a Python expression""", + "author": "Sunflower IT,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/server-tools", + "version": "13.0.1.0.0", + "license": "AGPL-3", + "category": "Generic Modules", + "depends": ["base"], + "data": ["views/ir_sequence.xml"], +} diff --git a/sequence_python/i18n/sequence_python.pot b/sequence_python/i18n/sequence_python.pot new file mode 100644 index 00000000000..87cb2ef655e --- /dev/null +++ b/sequence_python/i18n/sequence_python.pot @@ -0,0 +1,108 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sequence_python +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \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: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "number: The next number of the sequence (integer)" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"number_padded: Padded string of the next number of the sequence" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"random: The Python random module, eg. to use " +"random.randint(0, 9)" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "sequence: Odoo record of the sequence being used" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"string: The Python string module, eg. to use " +"random.choices(string.ascii_letters + string.digits, k=4)" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"uuid: The Python uuid module, eg. to use " +"uuid.uuid4()" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Aside from this, you may use several" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Help with Python expressions" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code_preview +msgid "Preview" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Python Code" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code +msgid "Python expression" +msgstr "" + +#. module: sequence_python +#: model:ir.model,name:sequence_python.model_ir_sequence +msgid "Sequence" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"The expression you type here will be evaluated as the next number. The " +"following variables can be used:" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__use_python_code +msgid "Use Python" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,help:sequence_python.field_ir_sequence__python_code +msgid "Write Python code that generates the sequence body." +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "builtin Python functions" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "number" +msgstr "" diff --git a/sequence_python/models/__init__.py b/sequence_python/models/__init__.py new file mode 100644 index 00000000000..5b015772ab2 --- /dev/null +++ b/sequence_python/models/__init__.py @@ -0,0 +1 @@ +from . import ir_sequence diff --git a/sequence_python/models/ir_sequence.py b/sequence_python/models/ir_sequence.py new file mode 100644 index 00000000000..7c1535e7b00 --- /dev/null +++ b/sequence_python/models/ir_sequence.py @@ -0,0 +1,78 @@ +# Copyright 2020 Sunflower IT () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging +import random +import string +import uuid + +from odoo import fields, models +from odoo.tools import safe_eval + +_logger = logging.getLogger(__name__) + + +DEFAULT_PYTHON_CODE = "number_padded" + + +class IrSequence(models.Model): + """ + Inherit standard ir.sequence to let the possibility of using a Python formula + to calculate the sequence from input variables such as the sequence number. + This allows obfuscation of the order in which sequences are given out, + but can also be used for any other purpose. + """ + + _inherit = "ir.sequence" + + # Python code + use_python_code = fields.Boolean(string="Use Python", default=False) + python_code = fields.Text( + string="Python expression", + default=DEFAULT_PYTHON_CODE, + help="Write Python code that generates the sequence body.", + ) + python_code_preview = fields.Char("Preview", compute="_compute_python_code_preview") + + def _get_python_eval_context(self, number_next): + """ + Get the eval context to evaluate the Python code with. + The format is (variable name, description, value) + You can inherit this in your custom module. + :return: tuple + """ + return { + "number": number_next[0] if isinstance(number_next, tuple) else number_next, + "number_padded": "%%0%sd" % self.padding % number_next, + "sequence": self, + "random": random, + "uuid": uuid, + "string": string, + } + + def _get_python_value(self, number_next): + """ + Use the python formula to get the value. + :return: string + """ + eval_context = self._get_python_eval_context(number_next) + return safe_eval(self.python_code.strip(), eval_context) + + def _compute_python_code_preview(self): + for this in self: + try: + this.python_code_preview = self.get_next_char( + (self.number_next_actual,) + ) + except Exception as e: # noqa + this.python_code_preview = str(e) + + def get_next_char(self, number_next): + if self.use_python_code: + interpolated_prefix, interpolated_suffix = self._get_prefix_suffix() + return ( + interpolated_prefix + + self._get_python_value(number_next) + + interpolated_suffix + ) + else: + return super(IrSequence, self).get_next_char(number_next) diff --git a/sequence_python/readme/CONTRIBUTORS.rst b/sequence_python/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..68c0876aa4b --- /dev/null +++ b/sequence_python/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Tom Blauwendraat diff --git a/sequence_python/readme/DESCRIPTION.rst b/sequence_python/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..8dc3416572e --- /dev/null +++ b/sequence_python/readme/DESCRIPTION.rst @@ -0,0 +1,16 @@ +This module allows to generate a sequence by a Python formula expression. + +Besides common Python functions and operators, it provides several functions +like 'random' and 'uuid' in the expression, as well as variables such as the +next number in the sequence. These are also listed next to the input field +on the sequence form view. + +If you want to add more variables for use in the expression, you can extend +this module. + +Use cases for this module could be: + +- You want to generate alphanumeric numbering +- You want to apply some math to the number to prevent customers from knowing + their place in the sequence +- You want to use UUID diff --git a/sequence_python/readme/USAGE.rst b/sequence_python/readme/USAGE.rst new file mode 100644 index 00000000000..67deb469116 --- /dev/null +++ b/sequence_python/readme/USAGE.rst @@ -0,0 +1,21 @@ +To use this module, you need to: + +* Go to the form view of an `ir.sequence` record +* Go to the Python tab +* Enable the 'Use Python' checkbox +* Change the default 'number' expression to something more fancy. + +Examples: + +.. code-block:: python + + # To separate the Odoo-generated number with hyphens eg. 0-0-0-0-1 + '-'.join(number_padded) + + # To have an UUID as the sequence value + uuid.uuid4().hex + + # To use an 8-digit binary number + '{0:#010b}'.format(number + 300)[2:] + +And so on. diff --git a/sequence_python/static/description/icon.png b/sequence_python/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/sequence_python/static/description/index.html b/sequence_python/static/description/index.html new file mode 100644 index 00000000000..ce598bd4d49 --- /dev/null +++ b/sequence_python/static/description/index.html @@ -0,0 +1,455 @@ + + + + + + +Sequence from Python expression + + + +
+

Sequence from Python expression

+ + +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runbot

+

This module allows to generate a sequence by a Python formula expression.

+

Besides common Python functions and operators, it provides several functions +like ‘random’ and ‘uuid’ in the expression, as well as variables such as the +next number in the sequence. These are also listed next to the input field +on the sequence form view.

+

If you want to add more variables for use in the expression, you can extend +this module.

+

Use cases for this module could be:

+
    +
  • You want to generate alphanumeric numbering
  • +
  • You want to apply some math to the number to prevent customers from knowing +their place in the sequence
  • +
  • You want to use UUID
  • +
+

Table of contents

+ +
+

Usage

+

To use this module, you need to:

+
    +
  • Go to the form view of an ir.sequence record
  • +
  • Go to the Python tab
  • +
  • Enable the ‘Use Python’ checkbox
  • +
  • Change the default ‘number’ expression to something more fancy.
  • +
+

Examples:

+
+# To separate the Odoo-generated number with hyphens eg. 0-0-0-0-1
+'-'.join(number_padded)
+
+# To have an UUID as the sequence value
+uuid.uuid4().hex
+
+# To use an 8-digit binary number
+'{0:#010b}'.format(number + 300)[2:]
+
+

And so on.

+
+
+

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

+
    +
  • Sunflower IT
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/sequence_python/tests/__init__.py b/sequence_python/tests/__init__.py new file mode 100644 index 00000000000..b32ac6c6ab7 --- /dev/null +++ b/sequence_python/tests/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2020 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import test_ir_sequence diff --git a/sequence_python/tests/test_ir_sequence.py b/sequence_python/tests/test_ir_sequence.py new file mode 100644 index 00000000000..29a083da5d4 --- /dev/null +++ b/sequence_python/tests/test_ir_sequence.py @@ -0,0 +1,39 @@ +# Copyright 2020 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import SavepointCase + + +class TestIrSequence(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.Sequence = cls.env["ir.sequence"] + cls.sequence = cls.Sequence.create( + { + "name": "Test sequence", + "implementation": "standard", + "code": "test.python.sequence", + "prefix": "A", + "padding": 2, + "number_next": 1, + "number_increment": 1, + "company_id": False, + "use_python_code": True, + "python_code": "'B' + number_padded + 'C'", + } + ) + + def test_standard_sequence(self): + self.assertEquals(self.sequence.python_code_preview, "AB01C") + next_number = self.sequence._next() + self.assertEquals(next_number, "AB01C") + next_number = self.sequence._next() + self.assertEquals(next_number, "AB02C") + + def test_nogap_sequence(self): + self.sequence.write(dict(implementation="no_gap")) + next_number = self.sequence._next() + self.assertEquals(next_number, "AB01C") + next_number = self.sequence._next() + self.assertEquals(next_number, "AB02C") diff --git a/sequence_python/views/ir_sequence.xml b/sequence_python/views/ir_sequence.xml new file mode 100644 index 00000000000..743a628b8e0 --- /dev/null +++ b/sequence_python/views/ir_sequence.xml @@ -0,0 +1,52 @@ + + + + ir.sequence + + + + + + + + + +
+

Help with Python expressions

+

The expression you type here will be evaluated as the next number. The following variables can be used:

+
    +
  • number: The next number of the sequence (integer)
  • +
  • number_padded: Padded string of the next number of the sequence
  • +
  • sequence: Odoo record of the sequence being used
  • +
  • uuid: The Python uuid module, eg. to use uuid.uuid4()
  • +
  • random: The Python random module, eg. to use random.randint(0, 9)
  • +
  • string: The Python string module, eg. to use random.choices(string.ascii_letters + string.digits, k=4)
  • +
+

Aside from this, you may use several builtin Python functions

+
+
+
+
+
+
From e8fede3d982004944abd5db7fcfe9c62d6b90140 Mon Sep 17 00:00:00 2001 From: Francesco Apruzzese Date: Thu, 23 Sep 2021 16:55:03 +0200 Subject: [PATCH 02/16] [MIG] sequence_python: Migration to 14.0 --- sequence_python/README.rst | 11 +++++----- sequence_python/__manifest__.py | 2 +- sequence_python/i18n/sequence_python.pot | 22 ++++++++++++++++++- sequence_python/models/ir_sequence.py | 15 +++++++++---- sequence_python/readme/CONTRIBUTORS.rst | 1 + sequence_python/static/description/index.html | 7 +++--- sequence_python/tests/test_ir_sequence.py | 11 +++++----- 7 files changed, 50 insertions(+), 19 deletions(-) diff --git a/sequence_python/README.rst b/sequence_python/README.rst index 51a1cdbd471..ae63ee5e916 100644 --- a/sequence_python/README.rst +++ b/sequence_python/README.rst @@ -14,13 +14,13 @@ Sequence from Python expression :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--tools-lightgray.png?logo=github - :target: https://github.com/OCA/server-tools/tree/13.0/sequence_python + :target: https://github.com/OCA/server-tools/tree/14.0/sequence_python :alt: OCA/server-tools .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/server-tools-13-0/server-tools-13-0-sequence_python + :target: https://translation.odoo-community.org/projects/server-tools-14-0/server-tools-14-0-sequence_python :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/149/13.0 + :target: https://runbot.odoo-community.org/runbot/149/14.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -78,7 +78,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. @@ -94,6 +94,7 @@ Contributors ~~~~~~~~~~~~ * Tom Blauwendraat +* Francesco Apruzzese Maintainers ~~~~~~~~~~~ @@ -108,6 +109,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-tools `_ project on GitHub. +This module is part of the `OCA/server-tools `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sequence_python/__manifest__.py b/sequence_python/__manifest__.py index 9b6b1eeb83e..0365689a90d 100644 --- a/sequence_python/__manifest__.py +++ b/sequence_python/__manifest__.py @@ -5,7 +5,7 @@ "summary": """Calculate a sequence number from a Python expression""", "author": "Sunflower IT,Odoo Community Association (OCA)", "website": "https://github.com/OCA/server-tools", - "version": "13.0.1.0.0", + "version": "14.0.1.0.0", "license": "AGPL-3", "category": "Generic Modules", "depends": ["base"], diff --git a/sequence_python/i18n/sequence_python.pot b/sequence_python/i18n/sequence_python.pot index 87cb2ef655e..38107f936f4 100644 --- a/sequence_python/i18n/sequence_python.pot +++ b/sequence_python/i18n/sequence_python.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 13.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -55,11 +55,26 @@ msgstr "" msgid "Aside from this, you may use several" msgstr "" +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__display_name +msgid "Display Name" +msgstr "" + #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "Help with Python expressions" msgstr "" +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__id +msgid "ID" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence____last_update +msgid "Last Modified on" +msgstr "" + #. module: sequence_python #: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code_preview msgid "Preview" @@ -80,6 +95,11 @@ msgstr "" msgid "Sequence" msgstr "" +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__smart_search +msgid "Smart Search" +msgstr "" + #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "" diff --git a/sequence_python/models/ir_sequence.py b/sequence_python/models/ir_sequence.py index 7c1535e7b00..4da75f9fc38 100644 --- a/sequence_python/models/ir_sequence.py +++ b/sequence_python/models/ir_sequence.py @@ -4,9 +4,10 @@ import random import string import uuid +from inspect import getmembers, isclass, isfunction from odoo import fields, models -from odoo.tools import safe_eval +from odoo.tools.safe_eval import safe_eval, wrap_module _logger = logging.getLogger(__name__) @@ -40,13 +41,19 @@ def _get_python_eval_context(self, number_next): You can inherit this in your custom module. :return: tuple """ + wrap_random = wrap_module(random, random.__all__) + uuid_elements = [e[0] for e in getmembers(uuid, isfunction)] + [ + e[0] for e in getmembers(uuid, isclass) + ] + wrap_uuid = wrap_module(uuid, uuid_elements) + wrap_string = wrap_module(string, string.__all__) return { "number": number_next[0] if isinstance(number_next, tuple) else number_next, "number_padded": "%%0%sd" % self.padding % number_next, "sequence": self, - "random": random, - "uuid": uuid, - "string": string, + "random": wrap_random, + "uuid": wrap_uuid, + "string": wrap_string, } def _get_python_value(self, number_next): diff --git a/sequence_python/readme/CONTRIBUTORS.rst b/sequence_python/readme/CONTRIBUTORS.rst index 68c0876aa4b..fa027040f9c 100644 --- a/sequence_python/readme/CONTRIBUTORS.rst +++ b/sequence_python/readme/CONTRIBUTORS.rst @@ -1 +1,2 @@ * Tom Blauwendraat +* Francesco Apruzzese diff --git a/sequence_python/static/description/index.html b/sequence_python/static/description/index.html index ce598bd4d49..64b6758cd76 100644 --- a/sequence_python/static/description/index.html +++ b/sequence_python/static/description/index.html @@ -367,7 +367,7 @@

Sequence from Python expression

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runbot

+

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runbot

This module allows to generate a sequence by a Python formula expression.

Besides common Python functions and operators, it provides several functions like ‘random’ and ‘uuid’ in the expression, as well as variables such as the @@ -422,7 +422,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.

@@ -437,6 +437,7 @@

Authors

Contributors

@@ -446,7 +447,7 @@

Maintainers

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-tools project on GitHub.

+

This module is part of the OCA/server-tools project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/sequence_python/tests/test_ir_sequence.py b/sequence_python/tests/test_ir_sequence.py index 29a083da5d4..6dd5835929a 100644 --- a/sequence_python/tests/test_ir_sequence.py +++ b/sequence_python/tests/test_ir_sequence.py @@ -25,15 +25,16 @@ def setUpClass(cls): ) def test_standard_sequence(self): - self.assertEquals(self.sequence.python_code_preview, "AB01C") + # import pdb;pdb.set_trace() + self.assertEqual(self.sequence.python_code_preview, "AB01C") next_number = self.sequence._next() - self.assertEquals(next_number, "AB01C") + self.assertEqual(next_number, "AB01C") next_number = self.sequence._next() - self.assertEquals(next_number, "AB02C") + self.assertEqual(next_number, "AB02C") def test_nogap_sequence(self): self.sequence.write(dict(implementation="no_gap")) next_number = self.sequence._next() - self.assertEquals(next_number, "AB01C") + self.assertEqual(next_number, "AB01C") next_number = self.sequence._next() - self.assertEquals(next_number, "AB02C") + self.assertEqual(next_number, "AB02C") From f72c3757b0cb1cf3e4290a04007bf02fc86dc0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dept=2E=20T=C3=A9cnico?= Date: Mon, 14 Feb 2022 11:27:56 +0000 Subject: [PATCH 03/16] Added translation using Weblate (Catalan) --- sequence_python/i18n/ca.po | 129 +++++++++++++++++++++++++++++++ sequence_python/i18n/es_AR.po | 141 ++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 sequence_python/i18n/ca.po create mode 100644 sequence_python/i18n/es_AR.po diff --git a/sequence_python/i18n/ca.po b/sequence_python/i18n/ca.po new file mode 100644 index 00000000000..904e5b917f3 --- /dev/null +++ b/sequence_python/i18n/ca.po @@ -0,0 +1,129 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sequence_python +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ca\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" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "number: The next number of the sequence (integer)" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"number_padded: Padded string of the next number of the sequence" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"random: The Python random module, eg. to use " +"random.randint(0, 9)" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "sequence: Odoo record of the sequence being used" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"string: The Python string module, eg. to use " +"random.choices(string.ascii_letters + string.digits, k=4)" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"uuid: The Python uuid module, eg. to use " +"uuid.uuid4()" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Aside from this, you may use several" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__display_name +msgid "Display Name" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Help with Python expressions" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__id +msgid "ID" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence____last_update +msgid "Last Modified on" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code_preview +msgid "Preview" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Python Code" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code +msgid "Python expression" +msgstr "" + +#. module: sequence_python +#: model:ir.model,name:sequence_python.model_ir_sequence +msgid "Sequence" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__smart_search +msgid "Smart Search" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"The expression you type here will be evaluated as the next number. The " +"following variables can be used:" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__use_python_code +msgid "Use Python" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,help:sequence_python.field_ir_sequence__python_code +msgid "Write Python code that generates the sequence body." +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "builtin Python functions" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "number" +msgstr "" diff --git a/sequence_python/i18n/es_AR.po b/sequence_python/i18n/es_AR.po new file mode 100644 index 00000000000..22bc069b2bd --- /dev/null +++ b/sequence_python/i18n/es_AR.po @@ -0,0 +1,141 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sequence_python +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-09-05 01:07+0000\n" +"Last-Translator: Ignacio Buioli \n" +"Language-Team: none\n" +"Language: es_AR\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: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "number: The next number of the sequence (integer)" +msgstr "number: El siguiente número de la secuencia (entero)" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"number_padded: Padded string of the next number of the sequence" +msgstr "" +"number_padded: Relleno del siguiente número de la secuencia" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"random: The Python random module, eg. to use " +"random.randint(0, 9)" +msgstr "" +"random: El módulo aleatorio de Python, ej. para utilizar " +"random.randint(0, 9)" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "sequence: Odoo record of the sequence being used" +msgstr "" +"sequence: Registro Odoo de la secuencia que se está utilizando" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"string: The Python string module, eg. to use " +"random.choices(string.ascii_letters + string.digits, k=4)" +msgstr "" +"string: El módulo string de Python, ej. para utilizar " +"random.choices(string.ascii_letters + string.digits, k=4)" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"uuid: The Python uuid module, eg. to use " +"uuid.uuid4()" +msgstr "" +"uuid: El módulo uuid de Python, ej. para utilizar uuid." +"uuid4()" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Aside from this, you may use several" +msgstr "Aparte de esto, puede utilizar varios" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Help with Python expressions" +msgstr "Ayuda con expresiones Python" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__id +msgid "ID" +msgstr "ID" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence____last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code_preview +msgid "Preview" +msgstr "Previsualización" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Python Code" +msgstr "Código de Python" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code +msgid "Python expression" +msgstr "Expresión de Python" + +#. module: sequence_python +#: model:ir.model,name:sequence_python.model_ir_sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__smart_search +msgid "Smart Search" +msgstr "Búsqueda Inteligente" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"The expression you type here will be evaluated as the next number. The " +"following variables can be used:" +msgstr "" +"La expresión que escriba acá se evaluará como el siguiente número. Se pueden " +"utilizar las siguientes variables:" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__use_python_code +msgid "Use Python" +msgstr "Usar Python" + +#. module: sequence_python +#: model:ir.model.fields,help:sequence_python.field_ir_sequence__python_code +msgid "Write Python code that generates the sequence body." +msgstr "Escriba el código de Python que genera el cuerpo de la secuencia." + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "builtin Python functions" +msgstr "Funciones de Python incorporadas" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "number" +msgstr "number" From 42f09fa206382fd13e818e9c0b0a815a2351cd52 Mon Sep 17 00:00:00 2001 From: Emeric Panisset Date: Wed, 30 Nov 2022 15:00:05 +0100 Subject: [PATCH 04/16] [MIG] sequence_python: Migration to 15.0 --- sequence_python/README.rst | 23 ++++---- sequence_python/__manifest__.py | 2 +- sequence_python/i18n/sequence_python.pot | 22 +------- sequence_python/static/description/index.html | 53 ++++++++++--------- 4 files changed, 42 insertions(+), 58 deletions(-) diff --git a/sequence_python/README.rst b/sequence_python/README.rst index ae63ee5e916..810ffff2e0c 100644 --- a/sequence_python/README.rst +++ b/sequence_python/README.rst @@ -2,10 +2,13 @@ Sequence from Python expression =============================== -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:a8de58fae139e80e530b657b68f0b305d59789a34359e5932317ec82f227b0eb + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -14,16 +17,16 @@ Sequence from Python expression :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--tools-lightgray.png?logo=github - :target: https://github.com/OCA/server-tools/tree/14.0/sequence_python + :target: https://github.com/OCA/server-tools/tree/15.0/sequence_python :alt: OCA/server-tools .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/server-tools-14-0/server-tools-14-0-sequence_python + :target: https://translation.odoo-community.org/projects/server-tools-15-0/server-tools-15-0-sequence_python :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/149/14.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-tools&target_branch=15.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module allows to generate a sequence by a Python formula expression. @@ -77,8 +80,8 @@ 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 `_. +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. @@ -109,6 +112,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-tools `_ project on GitHub. +This module is part of the `OCA/server-tools `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sequence_python/__manifest__.py b/sequence_python/__manifest__.py index 0365689a90d..58948382c7a 100644 --- a/sequence_python/__manifest__.py +++ b/sequence_python/__manifest__.py @@ -5,7 +5,7 @@ "summary": """Calculate a sequence number from a Python expression""", "author": "Sunflower IT,Odoo Community Association (OCA)", "website": "https://github.com/OCA/server-tools", - "version": "14.0.1.0.0", + "version": "15.0.1.0.0", "license": "AGPL-3", "category": "Generic Modules", "depends": ["base"], diff --git a/sequence_python/i18n/sequence_python.pot b/sequence_python/i18n/sequence_python.pot index 38107f936f4..2f0749c8ed5 100644 --- a/sequence_python/i18n/sequence_python.pot +++ b/sequence_python/i18n/sequence_python.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" @@ -55,26 +55,11 @@ msgstr "" msgid "Aside from this, you may use several" msgstr "" -#. module: sequence_python -#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__display_name -msgid "Display Name" -msgstr "" - #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "Help with Python expressions" msgstr "" -#. module: sequence_python -#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__id -msgid "ID" -msgstr "" - -#. module: sequence_python -#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence____last_update -msgid "Last Modified on" -msgstr "" - #. module: sequence_python #: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code_preview msgid "Preview" @@ -95,11 +80,6 @@ msgstr "" msgid "Sequence" msgstr "" -#. module: sequence_python -#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__smart_search -msgid "Smart Search" -msgstr "" - #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "" diff --git a/sequence_python/static/description/index.html b/sequence_python/static/description/index.html index 64b6758cd76..a0a4dda84ab 100644 --- a/sequence_python/static/description/index.html +++ b/sequence_python/static/description/index.html @@ -1,20 +1,19 @@ - - + Sequence from Python expression + + +
+

Base - Write Diff

+ + +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

This module allows filtering values to update on records according to +whether they are actually different from the records’ current values.

+

Table of contents

+ +
+

Usage

+

Summary

+

This module allows you to update records by filtering out fields whose +values are going to be left unchanged by BaseModel.write(); for +example, let’s assume you have:

+
+>>> self
+sale.order.line(1,)
+>>> self.price_unit
+10.00
+
+

If you use self.write({"price_unit": 10.00}) or +self.price_unit = 10.00, Odoo may end up executing unnecessary +operations, like triggering the update on the field, recompute computed +fields that depend on price_unit, and so on, even if the value is +actually unchanged.

+

By using this module, you can prevent all of that.

+

You can use this module in 3 different ways. All of them require you to +add this module as a dependency of your module.

+

1 - Context key ``”write_use_diff_values”``

+

By adding write_use_diff_values=True to the context when updating a +field value, the BaseModel.write() patch will take care of filtering +out the fields’ values that are the same as the record’s current ones.

+

⚠️ Beware: the context key is propagated down to other write() calls

+

Example:

+
+from odoo import models
+
+
+class ProductTemplate(models.Model):
+    _inherit = "product.template"
+
+    def write(self, vals):
+        # Update only fields that are actually different
+        self = self.with_context(write_use_diff_values=True)
+        return super().write(vals)
+
+
+class ProductProduct(models.Model):
+    _inherit = "product.product"
+
+    def update_code_if_necessary(self, code: str):
+        # Update ``default_code`` only if different from the current value
+        self.with_context(write_use_diff_values=True).default_code = code
+
+

2 - Method ``BaseModel.write_diff()``

+

It is the same as calling write(), but it automatically enables the +"write_use_diff_values" context flag: self.write_diff(vals) is a +shortcut for +self.with_context(write_use_diff_values=True).write(vals)

+

⚠️ Beware: the context key is propagated down to other write() calls

+

3 - Method ``BaseModel._get_write_diff_values(vals)``

+

This method accepts a write-like dict as param, and returns a new +dict made of the fields who will actually update the record’s +values. This allows for a more flexible and customizable behavior than +the context key usage, because:

+
    +
  • you’ll be able to filter out specific fields, instead of filtering +out all the fields whose values won’t be changed after the update;
  • +
  • you’ll be able to execute the filtering on specific models, instead +of executing it on all the models involved in the stack of +write() calls from the first usage of the context key down to the +base method BaseModel.write().
  • +
+

Example:

+
+from collections import defaultdict
+
+from odoo import api, models
+from odoo.tools.misc import frozendict
+
+
+class ProductProduct(models.Model):
+    _inherit = "product.product"
+
+    def write(self, vals):
+        # OVERRIDE: ``odoo.addons.product.models.product_product.Product.write()``
+        # override will clear the whole registry cache if either 'active' or
+        # 'product_template_attribute_value_ids' are found in the ``vals`` dictionary:
+        # remove them unless it's necessary to update them
+        fnames = {"active", "product_template_attribute_value_ids"}
+        if vals_to_check := {f: vals.pop(f) for f in fnames.intersection(vals)}:
+            groups = defaultdict(lambda: self.browse())
+            for prod in self:
+                groups[frozendict(prod._get_write_diff_values(vals_to_check))] += prod
+            for diff_vals, prods in groups.items():
+                if res_vals := (vals | dict(diff_vals)):
+                    super(ProductProduct, prods).write(res_vals)
+            return True
+        return super().write(vals)
+
+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/base_write_diff/tests/__init__.py b/base_write_diff/tests/__init__.py new file mode 100644 index 00000000000..aa320247467 --- /dev/null +++ b/base_write_diff/tests/__init__.py @@ -0,0 +1 @@ +from . import test_base_write_diff diff --git a/base_write_diff/tests/test_base_write_diff.py b/base_write_diff/tests/test_base_write_diff.py new file mode 100644 index 00000000000..a630dbbccab --- /dev/null +++ b/base_write_diff/tests/test_base_write_diff.py @@ -0,0 +1,423 @@ +# Copyright 2025 Camptocamp SA +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from logging import getLogger + +from odoo_test_helper import FakeModelLoader + +from odoo import api, fields, models +from odoo.tests import TransactionCase +from odoo.tools.misc import mute_logger + +from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT + + +class TestRecordDiffCommon(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Setup env + cls.env = cls.env["base"].with_context(**DISABLED_MAIL_CONTEXT).env + + # ``_register_hook()`` is usually called at the end of the test process, but + # we need to be able to test it here + cls.env["base"]._register_hook() + + # Load test model + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + + class BWDTestModel(models.Model): + _name = "bwd.test.model" + _description = "Base Write Diff - Test Model" + _test_logger = getLogger("bwd.test.model.log") + + # To test a non-relational field + name = fields.Char() + # To test single-relational fields + m2o_id = fields.Many2one("bwd.test.model") + # To test multi-relational fields + o2m_ids = fields.One2many("bwd.test.model", inverse_name="m2o_id") + m2m_ids = fields.Many2many("bwd.test.model", "test_rel", "id_1", "id_2") + # To test computed fields + # ``perimeter``: computed, stored field that depends on stored fields + # ``area``: computed, non-stored field that depends on stored fields + # ``volume``: computed, non-stored field that depends on non-stored fields + length = fields.Integer() # pylint: disable=W8105 (Pylint complains this?) + width = fields.Integer() + height = fields.Integer() + perimeter = fields.Integer(compute="_compute_perimeter", store=True) + area = fields.Integer(compute="_compute_area", store=False) + volume = fields.Integer(compute="_compute_volume", store=False) + + @api.depends("length", "width") + def _compute_perimeter(self): + self._test_logger.warning("Computing perimeter") + for rec in self: + rec.perimeter = 2 * (rec.length + rec.width) + + @api.depends("length", "width") + def _compute_area(self): + self._test_logger.warning("Computing area") + for rec in self: + rec.area = rec.length * rec.width + + @api.depends("area", "height") + def _compute_volume(self): + self._test_logger.warning("Computing volume") + for rec in self: + rec.volume = rec.area * rec.height + + cls.loader.update_registry([BWDTestModel]) + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + super().tearDownClass() + + def _create_records(self, count=1): + records = self.env["bwd.test.model"].create([{} for _ in range(1, count + 1)]) + for rec in records: + rec.name = f"Record {rec.id}" + return records + + +class TestRecordDiff(TestRecordDiffCommon): + @mute_logger("bwd.test.model.log") + def test_00_get_write_diff_values_simple(self): + """Test ``_get_write_diff_values()`` on fields that are not multi-relational""" + record = self._create_records() + # Try to write the same value on non-relational field + # => ``_get_write_diff_values()`` returns an empty dict + vals = {"name": record.name} + self.assertEqual(record._get_write_diff_values(vals), {}) + # Try to write another value on non-relational field + # => ``_get_write_diff_values()`` returns the same dict + vals = {"name": record.name + " something else"} + self.assertEqual(record._get_write_diff_values(vals), vals) + # Try to write the same value on M2O field + # => ``_get_write_diff_values()`` returns an empty dict + vals = {"m2o_id": record.m2o_id.id} + self.assertEqual(record._get_write_diff_values(vals), {}) + # Try to write another value on M2O field + # => ``_get_write_diff_values()`` returns the same dict + vals = {"m2o_id": self._create_records().id} + self.assertEqual(record._get_write_diff_values(vals), vals) + + @mute_logger("bwd.test.model.log") + def test_10_get_write_diff_values_x2many_command_create(self): + """Test ``_get_write_diff_values()`` on fields.Command.create() + + ``_get_write_diff_values()`` always returns the original dict, even after the + corecords are actually created (because the values will create a new, different + corecord if used on ``write()`` again) + """ + record = self._create_records() + vals = { + "o2m_ids": [fields.Command.create({"name": "O2M Co-record"})], + "m2m_ids": [fields.Command.create({"name": "M2M Co-record"})], + } + self.assertEqual(record._get_write_diff_values(vals), vals) + record.write(vals) # Do the real update => the diff is not empty anyway + self.assertEqual(record._get_write_diff_values(vals), vals) + + @mute_logger("bwd.test.model.log") + def test_11_get_write_diff_values_x2many_command_update(self): + """Test ``_get_write_diff_values()`` on fields.Command.update() + + ``_get_write_diff_values()`` returns only the subset of IDs/values that should + be updated + """ + record = self._create_records() + # Create and assign 2 corecords to each X2M field + record.o2m_ids = o2m_corecords = self._create_records(2) + record.m2m_ids = m2m_corecords = self._create_records(2) + # Set vals to update 1 corecord on each X2M field + vals = { + "o2m_ids": [ + fields.Command.update(o2m_corecords[0].id, {"name": "O2M Corec"}), + fields.Command.update( + o2m_corecords[1].id, {"name": o2m_corecords[1].name} + ), + ], + "m2m_ids": [ + fields.Command.update( + m2m_corecords[0].id, {"name": m2m_corecords[0].name} + ), + fields.Command.update(m2m_corecords[1].id, {"name": "M2M Corec"}), + ], + } + # The diff should include only the IDs we want to update, and the fields we are + # actually different on them + self.assertEqual( + record._get_write_diff_values(vals), + { + "o2m_ids": [ + fields.Command.update(o2m_corecords[0].id, {"name": "O2M Corec"}) + ], + "m2m_ids": [ + fields.Command.update(m2m_corecords[1].id, {"name": "M2M Corec"}) + ], + }, + ) + record.write(vals) # Do the real update => the diff should be empty now + self.assertEqual(record._get_write_diff_values(vals), {}) + + @mute_logger("bwd.test.model.log") + def test_12_get_write_diff_values_x2many_command_delete(self): + """Test ``_get_write_diff_values()`` on fields.Command.delete() + + ``_get_write_diff_values()`` returns only the subset of IDs that should be + deleted/unlinked + """ + record = self._create_records() + # Create and assign 2 corecords to each X2M field + record.o2m_ids = o2m_corecords = self._create_records(2) + record.m2m_ids = m2m_corecords = self._create_records(2) + # Set vals to delete 1 corecord in each X2M field + vals = { + "o2m_ids": [fields.Command.delete(o2m_corecords[0].id)], + "m2m_ids": [fields.Command.delete(m2m_corecords[1].id)], + } + # The diff should include only the IDs we want to delete + self.assertEqual( + record._get_write_diff_values(vals), + # Odoo assigns command "delete" or "unlink" according to the field type + # and its definition (not important for our purposes here) + { + "o2m_ids": [fields.Command.delete(o2m_corecords[0].id)], + "m2m_ids": [fields.Command.unlink(m2m_corecords[1].id)], + }, + ) + record.write(vals) # Do the real update => the diff should be empty now + self.assertEqual(record._get_write_diff_values(vals), {}) + + @mute_logger("bwd.test.model.log") + def test_13_get_write_diff_values_x2many_command_unlink(self): + """Test ``_get_write_diff_values()`` on fields.Command.unlink() + + ``_get_write_diff_values()`` returns only the subset of IDs that should be + deleted/unlinked + """ + record = self._create_records() + # Create and assign 2 corecords to each X2M field + record.o2m_ids = o2m_corecords = self._create_records(2) + record.m2m_ids = m2m_corecords = self._create_records(2) + # Set vals to unlink 1 corecord in each X2M field + vals = { + "o2m_ids": [fields.Command.unlink(o2m_corecords[0].id)], + "m2m_ids": [fields.Command.unlink(m2m_corecords[1].id)], + } + # The diff should include only the IDs we want to unlink + self.assertEqual( + record._get_write_diff_values(vals), + # Odoo assigns command "delete" or "unlink" according to the field type + # and its definition (not important for our purposes here) + { + "o2m_ids": [fields.Command.delete(o2m_corecords[0].id)], + "m2m_ids": [fields.Command.unlink(m2m_corecords[1].id)], + }, + ) + record.write(vals) # Do the real update => the diff should be empty now + self.assertEqual(record._get_write_diff_values(vals), {}) + + @mute_logger("bwd.test.model.log") + def test_14_get_write_diff_values_x2many_command_link(self): + """Test ``_get_write_diff_values()`` on fields.Command.link() + + ``_get_write_diff_values()`` returns only the subset of IDs that should be + linked + """ + record = self._create_records() + # Create 2 corecords + o2m_corecords = self._create_records(2) + m2m_corecords = self._create_records(2) + # Assign 1 corecord to each X2M field + record.write( + { + "o2m_ids": [fields.Command.set(o2m_corecords[0].ids)], + "m2m_ids": [fields.Command.set(m2m_corecords[1].ids)], + } + ) + # Set vals to link all corecords on each X2M field + vals = { + "o2m_ids": [fields.Command.link(i) for i in o2m_corecords.ids], + "m2m_ids": [fields.Command.link(i) for i in m2m_corecords.ids], + } + # The diff should include only the IDs we want to link that are not already + # linked + self.assertEqual( + record._get_write_diff_values(vals), + # Odoo will update the commands to include the {"id": corecord.id} in them + { + "o2m_ids": [ + ( + fields.Command.LINK, + o2m_corecords[1].id, + {"id": o2m_corecords[1].id}, + ) + ], + "m2m_ids": [ + ( + fields.Command.LINK, + m2m_corecords[0].id, + {"id": m2m_corecords[0].id}, + ) + ], + }, + ) + record.write(vals) # Do the real update => the diff should be empty now + self.assertEqual(record._get_write_diff_values(vals), {}) + + @mute_logger("bwd.test.model.log") + def test_15_get_write_diff_values_x2many_command_clear(self): + """Test ``_get_write_diff_values()`` on fields.Command.clear() + + ``_get_write_diff_values()`` returns only the subset of IDs that should be + deleted/unlinked + """ + record = self._create_records() + # Create and assign 2 corecords to each X2M field + record.o2m_ids = o2m_corecords = self._create_records(2) + record.m2m_ids = m2m_corecords = self._create_records(2) + # Set vals to clear each X2M field + vals = { + "o2m_ids": [fields.Command.clear()], + "m2m_ids": [fields.Command.clear()], + } + self.assertEqual( + record._get_write_diff_values(vals), + # Odoo assigns command "delete" or "unlink" according to the field type + # and its definition (not important for our purposes here) + { + "o2m_ids": [fields.Command.delete(i) for i in o2m_corecords.ids], + "m2m_ids": [fields.Command.unlink(i) for i in m2m_corecords.ids], + }, + ) + record.write(vals) # Do the real update => the diff should be empty now + self.assertEqual(record._get_write_diff_values(vals), {}) + + @mute_logger("bwd.test.model.log") + def test_16_get_write_diff_values_x2many_command_set(self): + """Test ``_get_write_diff_values()`` on fields.Command.set() + + ``_get_write_diff_values()`` behavior depends on various cases + """ + record = self._create_records() + # Create 3 corecords for each X2M field + o2m_corecords = self._create_records(3) + m2m_corecords = self._create_records(3) + + # Case 1: + # - X2M fields contain no corecords + # - we want to assign them some corecords + # => ``_get_write_diff_values()`` should return a ``fields.Command.link()`` + # command for each corecord to add + self.assertEqual( + record._get_write_diff_values( + { + "o2m_ids": [fields.Command.set(o2m_corecords.ids)], + "m2m_ids": [fields.Command.set(m2m_corecords.ids)], + }, + ), + # Odoo will update the commands to "link", and it will add the + # {"id": corecord.id} in them + { + "o2m_ids": [ + (fields.Command.LINK, i, {"id": i}) for i in o2m_corecords.ids + ], + "m2m_ids": [ + (fields.Command.LINK, i, {"id": i}) for i in m2m_corecords.ids + ], + }, + ) + + # Case 2: + # - X2M fields contain some corecords + # - we want to replace them with different corecords + # => ``_get_write_diff_values()`` should return a + # ``fields.Command.[delete|unlink]()`` command for each corecord to remove, + # and a ``fields.Command.link()`` command for each corecord to add + record.o2m_ids = o2m_corecords[:1] + record.m2m_ids = m2m_corecords[:2] + self.assertEqual( + record._get_write_diff_values( + { + "o2m_ids": [fields.Command.set(o2m_corecords[1:].ids)], + "m2m_ids": [fields.Command.set(m2m_corecords[2:].ids)], + }, + ), + # Odoo will update the commands to "unlink", "delete" and "link" (with the + # {"id": corecord.id} in the "link" ones) + { + "o2m_ids": [ + (fields.Command.DELETE, i, 0) for i in o2m_corecords[:1].ids + ] + + [(fields.Command.LINK, i, {"id": i}) for i in o2m_corecords[1:].ids], + "m2m_ids": [ + (fields.Command.UNLINK, i, 0) for i in m2m_corecords[:2].ids + ] + + [(fields.Command.LINK, i, {"id": i}) for i in m2m_corecords[2:].ids], + }, + ) + + # Case 3: + # - X2M fields contain some corecords + # - we want to reassign the same corecords + # => ``_get_write_diff_values()`` should return nothing + record.o2m_ids = o2m_corecords + record.m2m_ids = m2m_corecords + self.assertEqual( + record._get_write_diff_values( + { + "o2m_ids": [fields.Command.set(o2m_corecords.ids)], + "m2m_ids": [fields.Command.set(m2m_corecords.ids)], + }, + ), + {}, + ) + + # Case 4: + # - X2M fields contain some corecords + # - we want to remove all corecords + # => ``_get_write_diff_values()`` should return a + # ``fields.Command.[delete|unlink]()`` command for each linked corecord + self.assertEqual( + record._get_write_diff_values( + { + "o2m_ids": [fields.Command.set([])], + "m2m_ids": [fields.Command.set([])], + }, + ), + # Odoo will update the commands to "unlink" and "delete" + { + "o2m_ids": [(fields.Command.DELETE, i, 0) for i in o2m_corecords.ids], + "m2m_ids": [(fields.Command.UNLINK, i, 0) for i in m2m_corecords.ids], + }, + ) + + # pylint: disable=W0104 + def test_20_write_diff_computed_fields(self): + """Checks cache behavior for computed fields when diff-writing their deps""" + # Prepare the record, its fields values and the cache + record = self._create_records() + vals = {"length": 5, "width": 3, "height": 2} + record.write(vals) + fnames = ("perimeter", "area", "volume") + for fname in fnames: + with mute_logger("bwd.test.model.log"): + record[fname] # Dummy read: set fields in cache + + # Use ``write`` w/ the same values: Odoo will need to recompute the computed + # fields values as soon as they're read + record.write(vals) + for fname in fnames: + with self.assertLogs("bwd.test.model.log", level="WARNING"): + record[fname] # Dummy read: check the compute method is triggered + + # Use ``write_diff`` w/ the same values: Odoo won't need to recompute the + # computed fields values + record.write_diff(vals) + for fname in fnames: + with self.assertNoLogs("bwd.test.model.log", level="WARNING"): + record[fname] # Dummy read: check the compute method is not triggered From 4abf3329afc33ba9f4152a8134c08d2ba3ecd4c5 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Sat, 6 Dec 2025 21:34:05 +0000 Subject: [PATCH 09/16] [UPD] Update sequence_python.pot --- sequence_python/i18n/sequence_python.pot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sequence_python/i18n/sequence_python.pot b/sequence_python/i18n/sequence_python.pot index 23379e65e50..76cab3e67da 100644 --- a/sequence_python/i18n/sequence_python.pot +++ b/sequence_python/i18n/sequence_python.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" +"Project-Id-Version: Odoo Server 17.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" From 47dfcd7e14dd0527972f5e8c47b06d4a369857e7 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sat, 6 Dec 2025 21:40:56 +0000 Subject: [PATCH 10/16] [BOT] post-merge updates --- README.md | 1 + sequence_python/README.rst | 8 ++++-- sequence_python/static/description/index.html | 28 +++++++++++-------- setup/_metapackage/pyproject.toml | 3 +- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 78b5c749dfb..20a580893fc 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ addon | version | maintainers | summary [rpc_helper](rpc_helper/) | 17.0.1.0.0 | simahawk | Helpers for disabling RPC calls [scheduler_error_mailer](scheduler_error_mailer/) | 17.0.1.0.0 | | Scheduler Error Mailer [sentry](sentry/) | 17.0.1.0.0 | barsi naglis versada moylop260 fernandahf | Report Odoo errors to Sentry +[sequence_python](sequence_python/) | 17.0.1.0.0 | | Calculate a sequence number from a Python expression [server_action_logging](server_action_logging/) | 17.0.1.0.0 | | Module that provides a logging mechanism for server actions [session_db](session_db/) | 17.0.1.0.1 | sbidoul | Store sessions in DB [slow_statement_logger](slow_statement_logger/) | 17.0.1.0.0 | | Log slow SQL statements diff --git a/sequence_python/README.rst b/sequence_python/README.rst index 90270f598f4..038be777be1 100644 --- a/sequence_python/README.rst +++ b/sequence_python/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + =============================== Sequence from Python expression =============================== @@ -7,13 +11,13 @@ Sequence from Python expression !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:2577b1699eaf32186615d1426d69a461c185cd70423a76700e336c09cf5c7056 + !! source digest: sha256:ec9931487f48ecf99466381c638107cc1cded6841dad03511386aad24ace10ee !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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 +.. |badge2| image:: https://img.shields.io/badge/license-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--tools-lightgray.png?logo=github diff --git a/sequence_python/static/description/index.html b/sequence_python/static/description/index.html index 6e4d81b6938..f4bd5711ad2 100644 --- a/sequence_python/static/description/index.html +++ b/sequence_python/static/description/index.html @@ -3,7 +3,7 @@ -Sequence from Python expression +README.rst -
-

Sequence from Python expression

+
+ + +Odoo Community Association + +
+

Sequence from Python expression

-

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

This module allows to generate a sequence by a Python formula expression.

Besides common Python functions and operators, it provides several @@ -399,7 +404,7 @@

Sequence from Python expression

-

Usage

+

Usage

To use this module, you need to:

  • Go to the form view of an ir.sequence record
  • @@ -421,7 +426,7 @@

    Usage

    And so on.

-

Bug Tracker

+

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 to smash it by providing a detailed and welcomed @@ -429,22 +434,22 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Sunflower IT
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -457,5 +462,6 @@

Maintainers

+
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index 5029ec7863b..f9b46b3de82 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "odoo-addons-oca-server-tools" -version = "17.0.20251023.0" +version = "17.0.20251206.0" dependencies = [ "odoo-addon-attachment_queue>=17.0dev,<17.1dev", "odoo-addon-auditlog>=17.0dev,<17.1dev", @@ -34,6 +34,7 @@ dependencies = [ "odoo-addon-rpc_helper>=17.0dev,<17.1dev", "odoo-addon-scheduler_error_mailer>=17.0dev,<17.1dev", "odoo-addon-sentry>=17.0dev,<17.1dev", + "odoo-addon-sequence_python>=17.0dev,<17.1dev", "odoo-addon-server_action_logging>=17.0dev,<17.1dev", "odoo-addon-session_db>=17.0dev,<17.1dev", "odoo-addon-slow_statement_logger>=17.0dev,<17.1dev", From d8092e7fc1613d6b7a8f86cfc4ce77bf751a902e Mon Sep 17 00:00:00 2001 From: oca-ci Date: Sat, 6 Dec 2025 21:52:30 +0000 Subject: [PATCH 11/16] [UPD] Update base_write_diff.pot --- base_write_diff/i18n/base_write_diff.pot | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 base_write_diff/i18n/base_write_diff.pot diff --git a/base_write_diff/i18n/base_write_diff.pot b/base_write_diff/i18n/base_write_diff.pot new file mode 100644 index 00000000000..55c54821814 --- /dev/null +++ b/base_write_diff/i18n/base_write_diff.pot @@ -0,0 +1,19 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_write_diff +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \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: base_write_diff +#: model:ir.model,name:base_write_diff.model_base +msgid "Base" +msgstr "" From c13b8fa67f7b6c56a2d1e9d677f167e073ad20c8 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sat, 6 Dec 2025 21:59:38 +0000 Subject: [PATCH 12/16] [BOT] post-merge updates --- README.md | 1 + base_write_diff/README.rst | 22 ++++--- base_write_diff/static/description/icon.png | Bin 0 -> 10254 bytes base_write_diff/static/description/index.html | 60 ++++++++++-------- setup/_metapackage/pyproject.toml | 3 +- 5 files changed, 49 insertions(+), 37 deletions(-) create mode 100644 base_write_diff/static/description/icon.png diff --git a/README.md b/README.md index 20a580893fc..662f1868aa0 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ addon | version | maintainers | summary [base_sparse_field_list_support](base_sparse_field_list_support/) | 17.0.1.0.0 | | add list support to convert_to_cache() [base_technical_user](base_technical_user/) | 17.0.1.0.0 | | Add a technical user parameter on the company [base_view_inheritance_extension](base_view_inheritance_extension/) | 17.0.1.1.0 | | Adds more operators for view inheritance +[base_write_diff](base_write_diff/) | 17.0.1.0.0 | | Prevents updates on fields whose values won't change anyway [database_cleanup](database_cleanup/) | 17.0.1.2.2 | | Database cleanup [dbfilter_from_header](dbfilter_from_header/) | 17.0.1.0.0 | | Filter databases with HTTP headers [fetchmail_attach_from_folder](fetchmail_attach_from_folder/) | 17.0.1.0.0 | NL66278 | Attach mails in an IMAP folder to existing objects diff --git a/base_write_diff/README.rst b/base_write_diff/README.rst index f2ca58edf28..b18633da22b 100644 --- a/base_write_diff/README.rst +++ b/base_write_diff/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ================= Base - Write Diff ================= @@ -7,13 +11,13 @@ Base - Write Diff !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:30b876ea608454c7036df28557f87027b05e4143e45985b192650237b1b16335 + !! source digest: sha256:71745a37619c24019b67f8558eb6c7be1b974ad0031b6ce40892e7eec305bcb3 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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 +.. |badge2| image:: https://img.shields.io/badge/license-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--tools-lightgray.png?logo=github @@ -110,12 +114,12 @@ This method accepts a write-like ``dict`` as param, and returns a new values. This allows for a more flexible and customizable behavior than the context key usage, because: -- you'll be able to filter out specific fields, instead of filtering - out all the fields whose values won't be changed after the update; -- you'll be able to execute the filtering on specific models, instead - of executing it on all the models involved in the stack of - ``write()`` calls from the first usage of the context key down to the - base method ``BaseModel.write()``. +- you'll be able to filter out specific fields, instead of filtering out + all the fields whose values won't be changed after the update; +- you'll be able to execute the filtering on specific models, instead of + executing it on all the models involved in the stack of ``write()`` + calls from the first usage of the context key down to the base method + ``BaseModel.write()``. Example: @@ -167,7 +171,7 @@ Authors Contributors ------------ -- Silvio Gregorini +- Silvio Gregorini Maintainers ----------- diff --git a/base_write_diff/static/description/icon.png b/base_write_diff/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcc49c24f364e9adf0afbc6fc0bac6dbecdeb11 GIT binary patch literal 10254 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 -Base - Write Diff +README.rst -
-

Base - Write Diff

+
+ + +Odoo Community Association + +
+

Base - Write Diff

-

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runboat

This module allows filtering values to update on records according to whether they are actually different from the records’ current values.

Table of contents

@@ -386,7 +391,7 @@

Base - Write Diff

-

Usage

+

Usage

Summary

This module allows you to update records by filtering out fields whose values are going to be left unchanged by BaseModel.write(); for @@ -412,22 +417,22 @@

Usage

⚠️ Beware: the context key is propagated down to other write() calls

Example:

-from odoo import models
+from odoo import models
 
 
-class ProductTemplate(models.Model):
+class ProductTemplate(models.Model):
     _inherit = "product.template"
 
-    def write(self, vals):
+    def write(self, vals):
         # Update only fields that are actually different
         self = self.with_context(write_use_diff_values=True)
         return super().write(vals)
 
 
-class ProductProduct(models.Model):
+class ProductProduct(models.Model):
     _inherit = "product.product"
 
-    def update_code_if_necessary(self, code: str):
+    def update_code_if_necessary(self, code: str):
         # Update ``default_code`` only if different from the current value
         self.with_context(write_use_diff_values=True).default_code = code
 
@@ -443,25 +448,25 @@

Usage

values. This allows for a more flexible and customizable behavior than the context key usage, because:

    -
  • you’ll be able to filter out specific fields, instead of filtering -out all the fields whose values won’t be changed after the update;
  • -
  • you’ll be able to execute the filtering on specific models, instead -of executing it on all the models involved in the stack of -write() calls from the first usage of the context key down to the -base method BaseModel.write().
  • +
  • you’ll be able to filter out specific fields, instead of filtering out +all the fields whose values won’t be changed after the update;
  • +
  • you’ll be able to execute the filtering on specific models, instead of +executing it on all the models involved in the stack of write() +calls from the first usage of the context key down to the base method +BaseModel.write().

Example:

-from collections import defaultdict
+from collections import defaultdict
 
-from odoo import api, models
-from odoo.tools.misc import frozendict
+from odoo import api, models
+from odoo.tools.misc import frozendict
 
 
-class ProductProduct(models.Model):
+class ProductProduct(models.Model):
     _inherit = "product.product"
 
-    def write(self, vals):
+    def write(self, vals):
         # OVERRIDE: ``odoo.addons.product.models.product_product.Product.write()``
         # override will clear the whole registry cache if either 'active' or
         # 'product_template_attribute_value_ids' are found in the ``vals`` dictionary:
@@ -479,7 +484,7 @@ 

Usage

-

Bug Tracker

+

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 to smash it by providing a detailed and welcomed @@ -487,21 +492,21 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

+
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index f9b46b3de82..b44c570a4e9 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "odoo-addons-oca-server-tools" -version = "17.0.20251206.0" +version = "17.0.20251206.1" dependencies = [ "odoo-addon-attachment_queue>=17.0dev,<17.1dev", "odoo-addon-auditlog>=17.0dev,<17.1dev", @@ -19,6 +19,7 @@ dependencies = [ "odoo-addon-base_sparse_field_list_support>=17.0dev,<17.1dev", "odoo-addon-base_technical_user>=17.0dev,<17.1dev", "odoo-addon-base_view_inheritance_extension>=17.0dev,<17.1dev", + "odoo-addon-base_write_diff>=17.0dev,<17.1dev", "odoo-addon-database_cleanup>=17.0dev,<17.1dev", "odoo-addon-dbfilter_from_header>=17.0dev,<17.1dev", "odoo-addon-fetchmail_attach_from_folder>=17.0dev,<17.1dev", From 0709245744fea7a5fca85ee97ea9bc42852d274b Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 9 Dec 2025 08:01:20 +0000 Subject: [PATCH 13/16] Added translation using Weblate (Italian) --- base_write_diff/i18n/it.po | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 base_write_diff/i18n/it.po diff --git a/base_write_diff/i18n/it.po b/base_write_diff/i18n/it.po new file mode 100644 index 00000000000..1e362f66210 --- /dev/null +++ b/base_write_diff/i18n/it.po @@ -0,0 +1,20 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_write_diff +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\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" + +#. module: base_write_diff +#: model:ir.model,name:base_write_diff.model_base +msgid "Base" +msgstr "" From 61e08d9bfced03b63a052c664d4acad397b3299b Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 9 Dec 2025 08:01:26 +0000 Subject: [PATCH 14/16] Added translation using Weblate (Italian) --- sequence_python/i18n/it.po | 114 +++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 sequence_python/i18n/it.po diff --git a/sequence_python/i18n/it.po b/sequence_python/i18n/it.po new file mode 100644 index 00000000000..0ef95313abd --- /dev/null +++ b/sequence_python/i18n/it.po @@ -0,0 +1,114 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sequence_python +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\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" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "number: The next number of the sequence (integer)" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"number_padded: Padded string of the next number of the sequence" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"random: The Python random module, eg. to use " +"random.randint(0, 9)" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "sequence: Odoo record of the sequence being used" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"string: The Python string module, eg. to use " +"random.choices(string.ascii_letters + string.digits, k=4)" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"uuid: The Python uuid module, eg. to use " +"uuid.uuid4()" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Aside from this, you may use several" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Help with Python expressions" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code_preview +msgid "Preview" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "Python Code" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code +msgid "Python expression" +msgstr "" + +#. module: sequence_python +#: model:ir.model,name:sequence_python.model_ir_sequence +msgid "Sequence" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__smart_search +msgid "Smart Search" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "" +"The expression you type here will be evaluated as the next number. The " +"following variables can be used:" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__use_python_code +msgid "Use Python" +msgstr "" + +#. module: sequence_python +#: model:ir.model.fields,help:sequence_python.field_ir_sequence__python_code +msgid "Write Python code that generates the sequence body." +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "builtin Python functions" +msgstr "" + +#. module: sequence_python +#: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view +msgid "number" +msgstr "" From 94a7f024f32778593b6b2eab03c10510512b18d0 Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 9 Dec 2025 10:39:31 +0000 Subject: [PATCH 15/16] Translated using Weblate (Italian) Currently translated at 100.0% (1 of 1 strings) Translation: server-tools-17.0/server-tools-17.0-base_write_diff Translate-URL: https://translation.odoo-community.org/projects/server-tools-17-0/server-tools-17-0-base_write_diff/it/ --- base_write_diff/i18n/it.po | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/base_write_diff/i18n/it.po b/base_write_diff/i18n/it.po index 1e362f66210..a47e0f9430d 100644 --- a/base_write_diff/i18n/it.po +++ b/base_write_diff/i18n/it.po @@ -6,15 +6,17 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 17.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2025-12-09 12:43+0000\n" +"Last-Translator: mymage \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 5.10.4\n" #. module: base_write_diff #: model:ir.model,name:base_write_diff.model_base msgid "Base" -msgstr "" +msgstr "Base" From e17a3399e54961e15feb76b1b28635f489b90f35 Mon Sep 17 00:00:00 2001 From: mymage Date: Tue, 9 Dec 2025 10:39:43 +0000 Subject: [PATCH 16/16] Translated using Weblate (Italian) Currently translated at 100.0% (18 of 18 strings) Translation: server-tools-17.0/server-tools-17.0-sequence_python Translate-URL: https://translation.odoo-community.org/projects/server-tools-17-0/server-tools-17-0-sequence_python/it/ --- sequence_python/i18n/it.po | 40 +++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/sequence_python/i18n/it.po b/sequence_python/i18n/it.po index 0ef95313abd..789fed27e50 100644 --- a/sequence_python/i18n/it.po +++ b/sequence_python/i18n/it.po @@ -6,24 +6,28 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 17.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2025-12-09 12:43+0000\n" +"Last-Translator: mymage \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 5.10.4\n" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "number: The next number of the sequence (integer)" -msgstr "" +msgstr "number: il numero successivo della sequenza (intero)" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "" "number_padded: Padded string of the next number of the sequence" msgstr "" +"number_padded: stringa riempita del numero successivo della " +"sequenza" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view @@ -31,11 +35,13 @@ msgid "" "random: The Python random module, eg. to use " "random.randint(0, 9)" msgstr "" +"random: il modulo random Python, es. da usare " +"random.randint(0, 9)" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "sequence: Odoo record of the sequence being used" -msgstr "" +msgstr "sequence: record Odoo della sequenza in uso" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view @@ -43,6 +49,8 @@ msgid "" "string: The Python string module, eg. to use " "random.choices(string.ascii_letters + string.digits, k=4)" msgstr "" +"string: il modulo stringa Python, es.da usare " +"random.choices(string.ascii_letters + string.digits, k=4)" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view @@ -50,41 +58,43 @@ msgid "" "uuid: The Python uuid module, eg. to use " "uuid.uuid4()" msgstr "" +"uuid: il modulo uuid Python da usare, es. da usare " +"uuid.uuid4()" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "Aside from this, you may use several" -msgstr "" +msgstr "Oltre a questo, si possono usare diversi" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "Help with Python expressions" -msgstr "" +msgstr "Aiuto con espressioni Python" #. module: sequence_python #: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code_preview msgid "Preview" -msgstr "" +msgstr "Anteprima" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "Python Code" -msgstr "" +msgstr "Codice Python" #. module: sequence_python #: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__python_code msgid "Python expression" -msgstr "" +msgstr "Espressione Python" #. module: sequence_python #: model:ir.model,name:sequence_python.model_ir_sequence msgid "Sequence" -msgstr "" +msgstr "Sequenza" #. module: sequence_python #: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__smart_search msgid "Smart Search" -msgstr "" +msgstr "Ricerca intelligente" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view @@ -92,23 +102,25 @@ msgid "" "The expression you type here will be evaluated as the next number. The " "following variables can be used:" msgstr "" +"L'espressione inserita qui verrà valutata come il numero successivo. Si " +"possono usare le seguenti variabili:" #. module: sequence_python #: model:ir.model.fields,field_description:sequence_python.field_ir_sequence__use_python_code msgid "Use Python" -msgstr "" +msgstr "Usa Python" #. module: sequence_python #: model:ir.model.fields,help:sequence_python.field_ir_sequence__python_code msgid "Write Python code that generates the sequence body." -msgstr "" +msgstr "Scrivere il codice Python che genera il corpo della sequenza." #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "builtin Python functions" -msgstr "" +msgstr "funzioni Python incorporate" #. module: sequence_python #: model_terms:ir.ui.view,arch_db:sequence_python.sequence_view msgid "number" -msgstr "" +msgstr "numero"