odoo-addon-base-write-diff 17.0.1.0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/base_write_diff/README.rst +191 -0
- odoo/addons/base_write_diff/__init__.py +1 -0
- odoo/addons/base_write_diff/__manifest__.py +14 -0
- odoo/addons/base_write_diff/i18n/base_write_diff.pot +19 -0
- odoo/addons/base_write_diff/models/__init__.py +1 -0
- odoo/addons/base_write_diff/models/base.py +117 -0
- odoo/addons/base_write_diff/readme/CONTRIBUTORS.md +1 -0
- odoo/addons/base_write_diff/readme/DESCRIPTION.md +2 -0
- odoo/addons/base_write_diff/readme/USAGE.md +101 -0
- odoo/addons/base_write_diff/static/description/icon.png +0 -0
- odoo/addons/base_write_diff/static/description/index.html +524 -0
- odoo/addons/base_write_diff/tests/__init__.py +1 -0
- odoo/addons/base_write_diff/tests/test_base_write_diff.py +423 -0
- odoo_addon_base_write_diff-17.0.1.0.0.2.dist-info/METADATA +207 -0
- odoo_addon_base_write_diff-17.0.1.0.0.2.dist-info/RECORD +17 -0
- odoo_addon_base_write_diff-17.0.1.0.0.2.dist-info/WHEEL +5 -0
- odoo_addon_base_write_diff-17.0.1.0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
.. image:: https://odoo-community.org/readme-banner-image
|
|
2
|
+
:target: https://odoo-community.org/get-involved?utm_source=readme
|
|
3
|
+
:alt: Odoo Community Association
|
|
4
|
+
|
|
5
|
+
=================
|
|
6
|
+
Base - Write Diff
|
|
7
|
+
=================
|
|
8
|
+
|
|
9
|
+
..
|
|
10
|
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
11
|
+
!! This file is generated by oca-gen-addon-readme !!
|
|
12
|
+
!! changes will be overwritten. !!
|
|
13
|
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
14
|
+
!! source digest: sha256:71745a37619c24019b67f8558eb6c7be1b974ad0031b6ce40892e7eec305bcb3
|
|
15
|
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
16
|
+
|
|
17
|
+
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
|
18
|
+
:target: https://odoo-community.org/page/development-status
|
|
19
|
+
:alt: Beta
|
|
20
|
+
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
|
|
21
|
+
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
|
22
|
+
:alt: License: AGPL-3
|
|
23
|
+
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
|
|
24
|
+
:target: https://github.com/OCA/server-tools/tree/17.0/base_write_diff
|
|
25
|
+
:alt: OCA/server-tools
|
|
26
|
+
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
|
27
|
+
:target: https://translation.odoo-community.org/projects/server-tools-17-0/server-tools-17-0-base_write_diff
|
|
28
|
+
:alt: Translate me on Weblate
|
|
29
|
+
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
|
|
30
|
+
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=17.0
|
|
31
|
+
:alt: Try me on Runboat
|
|
32
|
+
|
|
33
|
+
|badge1| |badge2| |badge3| |badge4| |badge5|
|
|
34
|
+
|
|
35
|
+
This module allows filtering values to update on records according to
|
|
36
|
+
whether they are actually different from the records' current values.
|
|
37
|
+
|
|
38
|
+
**Table of contents**
|
|
39
|
+
|
|
40
|
+
.. contents::
|
|
41
|
+
:local:
|
|
42
|
+
|
|
43
|
+
Usage
|
|
44
|
+
=====
|
|
45
|
+
|
|
46
|
+
**Summary**
|
|
47
|
+
|
|
48
|
+
This module allows you to update records by filtering out fields whose
|
|
49
|
+
values are going to be left unchanged by ``BaseModel.write()``; for
|
|
50
|
+
example, let's assume you have:
|
|
51
|
+
|
|
52
|
+
.. code:: python
|
|
53
|
+
|
|
54
|
+
>>> self
|
|
55
|
+
sale.order.line(1,)
|
|
56
|
+
>>> self.price_unit
|
|
57
|
+
10.00
|
|
58
|
+
|
|
59
|
+
If you use ``self.write({"price_unit": 10.00})`` or
|
|
60
|
+
``self.price_unit = 10.00``, Odoo may end up executing unnecessary
|
|
61
|
+
operations, like triggering the update on the field, recompute computed
|
|
62
|
+
fields that depend on ``price_unit``, and so on, even if the value is
|
|
63
|
+
actually unchanged.
|
|
64
|
+
|
|
65
|
+
By using this module, you can prevent all of that.
|
|
66
|
+
|
|
67
|
+
You can use this module in 3 different ways. All of them require you to
|
|
68
|
+
add this module as a dependency of your module.
|
|
69
|
+
|
|
70
|
+
**1 - Context key ``"write_use_diff_values"``**
|
|
71
|
+
|
|
72
|
+
By adding ``write_use_diff_values=True`` to the context when updating a
|
|
73
|
+
field value, the ``BaseModel.write()`` patch will take care of filtering
|
|
74
|
+
out the fields' values that are the same as the record's current ones.
|
|
75
|
+
|
|
76
|
+
⚠️ Beware: the context key is propagated down to other ``write()`` calls
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
|
|
80
|
+
.. code:: python
|
|
81
|
+
|
|
82
|
+
from odoo import models
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ProductTemplate(models.Model):
|
|
86
|
+
_inherit = "product.template"
|
|
87
|
+
|
|
88
|
+
def write(self, vals):
|
|
89
|
+
# Update only fields that are actually different
|
|
90
|
+
self = self.with_context(write_use_diff_values=True)
|
|
91
|
+
return super().write(vals)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ProductProduct(models.Model):
|
|
95
|
+
_inherit = "product.product"
|
|
96
|
+
|
|
97
|
+
def update_code_if_necessary(self, code: str):
|
|
98
|
+
# Update ``default_code`` only if different from the current value
|
|
99
|
+
self.with_context(write_use_diff_values=True).default_code = code
|
|
100
|
+
|
|
101
|
+
**2 - Method ``BaseModel.write_diff()``**
|
|
102
|
+
|
|
103
|
+
It is the same as calling ``write()``, but it automatically enables the
|
|
104
|
+
``"write_use_diff_values"`` context flag: ``self.write_diff(vals)`` is a
|
|
105
|
+
shortcut for
|
|
106
|
+
``self.with_context(write_use_diff_values=True).write(vals)``
|
|
107
|
+
|
|
108
|
+
⚠️ Beware: the context key is propagated down to other ``write()`` calls
|
|
109
|
+
|
|
110
|
+
**3 - Method ``BaseModel._get_write_diff_values(vals)``**
|
|
111
|
+
|
|
112
|
+
This method accepts a write-like ``dict`` as param, and returns a new
|
|
113
|
+
``dict`` made of the fields who will actually update the record's
|
|
114
|
+
values. This allows for a more flexible and customizable behavior than
|
|
115
|
+
the context key usage, because:
|
|
116
|
+
|
|
117
|
+
- you'll be able to filter out specific fields, instead of filtering out
|
|
118
|
+
all the fields whose values won't be changed after the update;
|
|
119
|
+
- you'll be able to execute the filtering on specific models, instead of
|
|
120
|
+
executing it on all the models involved in the stack of ``write()``
|
|
121
|
+
calls from the first usage of the context key down to the base method
|
|
122
|
+
``BaseModel.write()``.
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
|
|
126
|
+
.. code:: python
|
|
127
|
+
|
|
128
|
+
from collections import defaultdict
|
|
129
|
+
|
|
130
|
+
from odoo import api, models
|
|
131
|
+
from odoo.tools.misc import frozendict
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ProductProduct(models.Model):
|
|
135
|
+
_inherit = "product.product"
|
|
136
|
+
|
|
137
|
+
def write(self, vals):
|
|
138
|
+
# OVERRIDE: ``odoo.addons.product.models.product_product.Product.write()``
|
|
139
|
+
# override will clear the whole registry cache if either 'active' or
|
|
140
|
+
# 'product_template_attribute_value_ids' are found in the ``vals`` dictionary:
|
|
141
|
+
# remove them unless it's necessary to update them
|
|
142
|
+
fnames = {"active", "product_template_attribute_value_ids"}
|
|
143
|
+
if vals_to_check := {f: vals.pop(f) for f in fnames.intersection(vals)}:
|
|
144
|
+
groups = defaultdict(lambda: self.browse())
|
|
145
|
+
for prod in self:
|
|
146
|
+
groups[frozendict(prod._get_write_diff_values(vals_to_check))] += prod
|
|
147
|
+
for diff_vals, prods in groups.items():
|
|
148
|
+
if res_vals := (vals | dict(diff_vals)):
|
|
149
|
+
super(ProductProduct, prods).write(res_vals)
|
|
150
|
+
return True
|
|
151
|
+
return super().write(vals)
|
|
152
|
+
|
|
153
|
+
Bug Tracker
|
|
154
|
+
===========
|
|
155
|
+
|
|
156
|
+
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
|
|
157
|
+
In case of trouble, please check there if your issue has already been reported.
|
|
158
|
+
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
|
159
|
+
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20base_write_diff%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
|
160
|
+
|
|
161
|
+
Do not contact contributors directly about support or help with technical issues.
|
|
162
|
+
|
|
163
|
+
Credits
|
|
164
|
+
=======
|
|
165
|
+
|
|
166
|
+
Authors
|
|
167
|
+
-------
|
|
168
|
+
|
|
169
|
+
* Camptocamp
|
|
170
|
+
|
|
171
|
+
Contributors
|
|
172
|
+
------------
|
|
173
|
+
|
|
174
|
+
- Silvio Gregorini <silvio.gregorini@camptocamp.com>
|
|
175
|
+
|
|
176
|
+
Maintainers
|
|
177
|
+
-----------
|
|
178
|
+
|
|
179
|
+
This module is maintained by the OCA.
|
|
180
|
+
|
|
181
|
+
.. image:: https://odoo-community.org/logo.png
|
|
182
|
+
:alt: Odoo Community Association
|
|
183
|
+
:target: https://odoo-community.org
|
|
184
|
+
|
|
185
|
+
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
|
186
|
+
mission is to support the collaborative development of Odoo features and
|
|
187
|
+
promote its widespread use.
|
|
188
|
+
|
|
189
|
+
This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/17.0/base_write_diff>`_ project on GitHub.
|
|
190
|
+
|
|
191
|
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import models
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Copyright 2025 Camptocamp SA
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
3
|
+
|
|
4
|
+
{
|
|
5
|
+
"name": "Base - Write Diff",
|
|
6
|
+
"summary": "Prevents updates on fields whose values won't change anyway",
|
|
7
|
+
"version": "17.0.1.0.0",
|
|
8
|
+
"author": "Camptocamp, Odoo Community Association (OCA)",
|
|
9
|
+
"license": "AGPL-3",
|
|
10
|
+
"category": "Hidden",
|
|
11
|
+
"website": "https://github.com/OCA/server-tools",
|
|
12
|
+
"installable": True,
|
|
13
|
+
"depends": ["base", "web"],
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Translation of Odoo Server.
|
|
2
|
+
# This file contains the translation of the following modules:
|
|
3
|
+
# * base_write_diff
|
|
4
|
+
#
|
|
5
|
+
msgid ""
|
|
6
|
+
msgstr ""
|
|
7
|
+
"Project-Id-Version: Odoo Server 17.0\n"
|
|
8
|
+
"Report-Msgid-Bugs-To: \n"
|
|
9
|
+
"Last-Translator: \n"
|
|
10
|
+
"Language-Team: \n"
|
|
11
|
+
"MIME-Version: 1.0\n"
|
|
12
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
13
|
+
"Content-Transfer-Encoding: \n"
|
|
14
|
+
"Plural-Forms: \n"
|
|
15
|
+
|
|
16
|
+
#. module: base_write_diff
|
|
17
|
+
#: model:ir.model,name:base_write_diff.model_base
|
|
18
|
+
msgid "Base"
|
|
19
|
+
msgstr ""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import base
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Copyright 2025 Camptocamp SA (https://www.camptocamp.com).
|
|
2
|
+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
3
|
+
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
|
|
6
|
+
from odoo import Command, models
|
|
7
|
+
from odoo.tools.misc import frozendict
|
|
8
|
+
|
|
9
|
+
from odoo.addons.web.models.models import RecordSnapshot
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseModel(models.BaseModel):
|
|
13
|
+
_inherit = "base"
|
|
14
|
+
|
|
15
|
+
def write(self, vals):
|
|
16
|
+
# OVERRIDE: when using the ``write_use_diff_values`` context key, remove values
|
|
17
|
+
# that won't be changed before/after the ``write()`` itself.
|
|
18
|
+
# If ``write()`` is called on an empty recordset or with no value, ignore
|
|
19
|
+
# everything and shortcut to ``super()``.
|
|
20
|
+
if not (self and vals and self.env.context.get("write_use_diff_values")):
|
|
21
|
+
return super().write(vals)
|
|
22
|
+
recs_by_vals = defaultdict(lambda: self.browse())
|
|
23
|
+
for rec in self:
|
|
24
|
+
recs_by_vals[frozendict(rec._get_write_diff_values(vals))] += rec
|
|
25
|
+
for rec_vals, recs in recs_by_vals.items():
|
|
26
|
+
if rec_vals: # Don't trigger ``write()`` if there is nothing to update
|
|
27
|
+
super(BaseModel, recs).write(dict(rec_vals))
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
def write_diff(self, vals: dict) -> bool:
|
|
31
|
+
"""Executes a ``write()`` only on fields that actually need to be updated"""
|
|
32
|
+
return self.with_context(write_use_diff_values=True).write(vals)
|
|
33
|
+
|
|
34
|
+
def _get_write_diff_values(self, vals: dict) -> dict:
|
|
35
|
+
"""Compares record values with the values to write
|
|
36
|
+
|
|
37
|
+
Returns a dictionary containing only the fields that actually needs to be
|
|
38
|
+
updated on ``self``, filtering out those which contain a value that is the same
|
|
39
|
+
as the current record's field value.
|
|
40
|
+
For example:
|
|
41
|
+
>>> self.name = "A"
|
|
42
|
+
>>> self.code = "a"
|
|
43
|
+
>>> self._get_write_diff_values({"name": "A", "code": "a"})
|
|
44
|
+
{}
|
|
45
|
+
>>> self._get_write_diff_values({"name": "B", "code": "a"})
|
|
46
|
+
{"name": "B"}
|
|
47
|
+
>>> self._get_write_diff_values({"name": "B", "code": "b"})
|
|
48
|
+
{"name": "B", "code": "b"}
|
|
49
|
+
"""
|
|
50
|
+
self.ensure_one()
|
|
51
|
+
diff_values = {}
|
|
52
|
+
|
|
53
|
+
# Step 1: group fields according to whether they're multi-relational or not
|
|
54
|
+
x2many_fields_values, simple_fields_values = {}, {}
|
|
55
|
+
for fname, fvalue in vals.items():
|
|
56
|
+
if self._fields[fname].type in ("one2many", "many2many"):
|
|
57
|
+
x2many_fields_values[fname] = fvalue
|
|
58
|
+
else:
|
|
59
|
+
simple_fields_values[fname] = fvalue
|
|
60
|
+
|
|
61
|
+
# Step 2: prepare fields to update by checking simple fields first
|
|
62
|
+
if simple_fields_values:
|
|
63
|
+
simple_fields_specs = {f: {} for f in simple_fields_values}
|
|
64
|
+
snapshot0 = self._do_snapshot({}, simple_fields_specs)
|
|
65
|
+
snapshot1 = self._do_snapshot(simple_fields_values, simple_fields_specs)
|
|
66
|
+
diff_values.update(snapshot1.diff(snapshot0))
|
|
67
|
+
|
|
68
|
+
# Step 3: prepare fields to update by checking multi-relational fields
|
|
69
|
+
# For each multi-relational field, prepare a new list of values by checking
|
|
70
|
+
# the original commands:
|
|
71
|
+
# - if it's an update command, check whether something actually changes on
|
|
72
|
+
# the corecord by calling ``_get_write_diff_values()`` recursively
|
|
73
|
+
# - else, add the original command to the new list: all commands except "update"
|
|
74
|
+
# will modify the record-corecords relation by creating/[un]linking/deleting
|
|
75
|
+
# corecords
|
|
76
|
+
# Then, check the new list of values to decide if the field needs updating:
|
|
77
|
+
# - at least 1 creation/update => add the full list of commands for simplicity
|
|
78
|
+
# - else => check whether the new values will effectively change the
|
|
79
|
+
# record-corecords relationship
|
|
80
|
+
for fname, fvalues in x2many_fields_values.items():
|
|
81
|
+
# Prepare the new list of commands/values according to the original command
|
|
82
|
+
new_fvalues = []
|
|
83
|
+
for fvalue in fvalues:
|
|
84
|
+
if fvalue[0] == Command.UPDATE:
|
|
85
|
+
cmd, corec_id, corec_vals = fvalue
|
|
86
|
+
corec = self.env[self._fields[fname].comodel_name].browse(corec_id)
|
|
87
|
+
if corec_diff_vals := corec._get_write_diff_values(corec_vals):
|
|
88
|
+
new_fvalues.append((cmd, corec_id, corec_diff_vals))
|
|
89
|
+
else:
|
|
90
|
+
new_fvalues.append(fvalue)
|
|
91
|
+
# Check whether we actually need to include the new list in the diff values
|
|
92
|
+
if any(v[0] in (Command.CREATE, Command.UPDATE) for v in new_fvalues):
|
|
93
|
+
diff_values[fname] = new_fvalues
|
|
94
|
+
else:
|
|
95
|
+
x2many_snapshot0 = self._do_snapshot({}, {fname: {}})
|
|
96
|
+
x2many_snapshot1 = self._do_snapshot({fname: new_fvalues}, {fname: {}})
|
|
97
|
+
if x2many_diff_values := x2many_snapshot1.diff(x2many_snapshot0):
|
|
98
|
+
diff_values.update(x2many_diff_values)
|
|
99
|
+
|
|
100
|
+
return diff_values
|
|
101
|
+
|
|
102
|
+
def _do_snapshot(self, vals: dict, specs: dict) -> "RecordSnapshot":
|
|
103
|
+
"""Prepares a ``RecordSnapshot`` object with the specified params"""
|
|
104
|
+
self.ensure_one()
|
|
105
|
+
# Align ``vals`` and ``specs`` to make sure they both contain the same fields:
|
|
106
|
+
# - if a field in ``specs`` is missing from ``vals``, we read its current value
|
|
107
|
+
# from the record and convert it to a ``write()``-able format to prevent cache
|
|
108
|
+
# issues and inconsistencies
|
|
109
|
+
# - if a field in ``vals`` is missing from ``specs``, we add it with the default
|
|
110
|
+
# value of ``{}`` to allow ``RecordSnapshot`` to handle it properly
|
|
111
|
+
vals_fnames_not_in_specs = set(vals) - set(specs)
|
|
112
|
+
specs_fnames_not_in_vals = set(specs) - set(vals)
|
|
113
|
+
for fname in vals_fnames_not_in_specs:
|
|
114
|
+
specs[fname] = {}
|
|
115
|
+
for fname in specs_fnames_not_in_vals:
|
|
116
|
+
vals[fname] = self._fields[fname].convert_to_write(self[fname], self)
|
|
117
|
+
return RecordSnapshot(self.new(values=vals, origin=self), fields_spec=specs)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- Silvio Gregorini \<<silvio.gregorini@camptocamp.com>\>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
**Summary**
|
|
2
|
+
|
|
3
|
+
This module allows you to update records by filtering out fields whose values are going
|
|
4
|
+
to be left unchanged by ``BaseModel.write()``; for example, let's assume you have:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
>>> self
|
|
8
|
+
sale.order.line(1,)
|
|
9
|
+
>>> self.price_unit
|
|
10
|
+
10.00
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If you use ``self.write({"price_unit": 10.00})`` or ``self.price_unit = 10.00``, Odoo
|
|
14
|
+
may end up executing unnecessary operations, like triggering the update on the field,
|
|
15
|
+
recompute computed fields that depend on ``price_unit``, and so on, even if the value
|
|
16
|
+
is actually unchanged.
|
|
17
|
+
|
|
18
|
+
By using this module, you can prevent all of that.
|
|
19
|
+
|
|
20
|
+
You can use this module in 3 different ways. All of them require you to add this module
|
|
21
|
+
as a dependency of your module.
|
|
22
|
+
|
|
23
|
+
**1 - Context key ``"write_use_diff_values"``**
|
|
24
|
+
|
|
25
|
+
By adding ``write_use_diff_values=True`` to the context when updating a field value,
|
|
26
|
+
the ``BaseModel.write()`` patch will take care of filtering out the fields' values
|
|
27
|
+
that are the same as the record's current ones.
|
|
28
|
+
|
|
29
|
+
⚠️ Beware: the context key is propagated down to other ``write()`` calls
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from odoo import models
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ProductTemplate(models.Model):
|
|
38
|
+
_inherit = "product.template"
|
|
39
|
+
|
|
40
|
+
def write(self, vals):
|
|
41
|
+
# Update only fields that are actually different
|
|
42
|
+
self = self.with_context(write_use_diff_values=True)
|
|
43
|
+
return super().write(vals)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ProductProduct(models.Model):
|
|
47
|
+
_inherit = "product.product"
|
|
48
|
+
|
|
49
|
+
def update_code_if_necessary(self, code: str):
|
|
50
|
+
# Update ``default_code`` only if different from the current value
|
|
51
|
+
self.with_context(write_use_diff_values=True).default_code = code
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**2 - Method ``BaseModel.write_diff()``**
|
|
55
|
+
|
|
56
|
+
It is the same as calling ``write()``, but it automatically enables the
|
|
57
|
+
``"write_use_diff_values"`` context flag: ``self.write_diff(vals)`` is a shortcut for
|
|
58
|
+
``self.with_context(write_use_diff_values=True).write(vals)``
|
|
59
|
+
|
|
60
|
+
⚠️ Beware: the context key is propagated down to other ``write()`` calls
|
|
61
|
+
|
|
62
|
+
**3 - Method ``BaseModel._get_write_diff_values(vals)``**
|
|
63
|
+
|
|
64
|
+
This method accepts a write-like ``dict`` as param, and returns a new ``dict`` made of
|
|
65
|
+
the fields who will actually update the record's values. This allows for a more
|
|
66
|
+
flexible and customizable behavior than the context key usage, because:
|
|
67
|
+
|
|
68
|
+
- you'll be able to filter out specific fields, instead of filtering out all the fields
|
|
69
|
+
whose values won't be changed after the update;
|
|
70
|
+
- you'll be able to execute the filtering on specific models, instead of executing it
|
|
71
|
+
on all the models involved in the stack of ``write()`` calls from the first usage of
|
|
72
|
+
the context key down to the base method ``BaseModel.write()``.
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from collections import defaultdict
|
|
78
|
+
|
|
79
|
+
from odoo import api, models
|
|
80
|
+
from odoo.tools.misc import frozendict
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ProductProduct(models.Model):
|
|
84
|
+
_inherit = "product.product"
|
|
85
|
+
|
|
86
|
+
def write(self, vals):
|
|
87
|
+
# OVERRIDE: ``odoo.addons.product.models.product_product.Product.write()``
|
|
88
|
+
# override will clear the whole registry cache if either 'active' or
|
|
89
|
+
# 'product_template_attribute_value_ids' are found in the ``vals`` dictionary:
|
|
90
|
+
# remove them unless it's necessary to update them
|
|
91
|
+
fnames = {"active", "product_template_attribute_value_ids"}
|
|
92
|
+
if vals_to_check := {f: vals.pop(f) for f in fnames.intersection(vals)}:
|
|
93
|
+
groups = defaultdict(lambda: self.browse())
|
|
94
|
+
for prod in self:
|
|
95
|
+
groups[frozendict(prod._get_write_diff_values(vals_to_check))] += prod
|
|
96
|
+
for diff_vals, prods in groups.items():
|
|
97
|
+
if res_vals := (vals | dict(diff_vals)):
|
|
98
|
+
super(ProductProduct, prods).write(res_vals)
|
|
99
|
+
return True
|
|
100
|
+
return super().write(vals)
|
|
101
|
+
```
|
|
Binary file
|