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.
@@ -0,0 +1,423 @@
1
+ # Copyright 2025 Camptocamp SA
2
+ # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
3
+
4
+ from logging import getLogger
5
+
6
+ from odoo_test_helper import FakeModelLoader
7
+
8
+ from odoo import api, fields, models
9
+ from odoo.tests import TransactionCase
10
+ from odoo.tools.misc import mute_logger
11
+
12
+ from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT
13
+
14
+
15
+ class TestRecordDiffCommon(TransactionCase):
16
+ @classmethod
17
+ def setUpClass(cls):
18
+ super().setUpClass()
19
+ # Setup env
20
+ cls.env = cls.env["base"].with_context(**DISABLED_MAIL_CONTEXT).env
21
+
22
+ # ``_register_hook()`` is usually called at the end of the test process, but
23
+ # we need to be able to test it here
24
+ cls.env["base"]._register_hook()
25
+
26
+ # Load test model
27
+ cls.loader = FakeModelLoader(cls.env, cls.__module__)
28
+ cls.loader.backup_registry()
29
+
30
+ class BWDTestModel(models.Model):
31
+ _name = "bwd.test.model"
32
+ _description = "Base Write Diff - Test Model"
33
+ _test_logger = getLogger("bwd.test.model.log")
34
+
35
+ # To test a non-relational field
36
+ name = fields.Char()
37
+ # To test single-relational fields
38
+ m2o_id = fields.Many2one("bwd.test.model")
39
+ # To test multi-relational fields
40
+ o2m_ids = fields.One2many("bwd.test.model", inverse_name="m2o_id")
41
+ m2m_ids = fields.Many2many("bwd.test.model", "test_rel", "id_1", "id_2")
42
+ # To test computed fields
43
+ # ``perimeter``: computed, stored field that depends on stored fields
44
+ # ``area``: computed, non-stored field that depends on stored fields
45
+ # ``volume``: computed, non-stored field that depends on non-stored fields
46
+ length = fields.Integer() # pylint: disable=W8105 (Pylint complains this?)
47
+ width = fields.Integer()
48
+ height = fields.Integer()
49
+ perimeter = fields.Integer(compute="_compute_perimeter", store=True)
50
+ area = fields.Integer(compute="_compute_area", store=False)
51
+ volume = fields.Integer(compute="_compute_volume", store=False)
52
+
53
+ @api.depends("length", "width")
54
+ def _compute_perimeter(self):
55
+ self._test_logger.warning("Computing perimeter")
56
+ for rec in self:
57
+ rec.perimeter = 2 * (rec.length + rec.width)
58
+
59
+ @api.depends("length", "width")
60
+ def _compute_area(self):
61
+ self._test_logger.warning("Computing area")
62
+ for rec in self:
63
+ rec.area = rec.length * rec.width
64
+
65
+ @api.depends("area", "height")
66
+ def _compute_volume(self):
67
+ self._test_logger.warning("Computing volume")
68
+ for rec in self:
69
+ rec.volume = rec.area * rec.height
70
+
71
+ cls.loader.update_registry([BWDTestModel])
72
+
73
+ @classmethod
74
+ def tearDownClass(cls):
75
+ cls.loader.restore_registry()
76
+ super().tearDownClass()
77
+
78
+ def _create_records(self, count=1):
79
+ records = self.env["bwd.test.model"].create([{} for _ in range(1, count + 1)])
80
+ for rec in records:
81
+ rec.name = f"Record {rec.id}"
82
+ return records
83
+
84
+
85
+ class TestRecordDiff(TestRecordDiffCommon):
86
+ @mute_logger("bwd.test.model.log")
87
+ def test_00_get_write_diff_values_simple(self):
88
+ """Test ``_get_write_diff_values()`` on fields that are not multi-relational"""
89
+ record = self._create_records()
90
+ # Try to write the same value on non-relational field
91
+ # => ``_get_write_diff_values()`` returns an empty dict
92
+ vals = {"name": record.name}
93
+ self.assertEqual(record._get_write_diff_values(vals), {})
94
+ # Try to write another value on non-relational field
95
+ # => ``_get_write_diff_values()`` returns the same dict
96
+ vals = {"name": record.name + " something else"}
97
+ self.assertEqual(record._get_write_diff_values(vals), vals)
98
+ # Try to write the same value on M2O field
99
+ # => ``_get_write_diff_values()`` returns an empty dict
100
+ vals = {"m2o_id": record.m2o_id.id}
101
+ self.assertEqual(record._get_write_diff_values(vals), {})
102
+ # Try to write another value on M2O field
103
+ # => ``_get_write_diff_values()`` returns the same dict
104
+ vals = {"m2o_id": self._create_records().id}
105
+ self.assertEqual(record._get_write_diff_values(vals), vals)
106
+
107
+ @mute_logger("bwd.test.model.log")
108
+ def test_10_get_write_diff_values_x2many_command_create(self):
109
+ """Test ``_get_write_diff_values()`` on fields.Command.create()
110
+
111
+ ``_get_write_diff_values()`` always returns the original dict, even after the
112
+ corecords are actually created (because the values will create a new, different
113
+ corecord if used on ``write()`` again)
114
+ """
115
+ record = self._create_records()
116
+ vals = {
117
+ "o2m_ids": [fields.Command.create({"name": "O2M Co-record"})],
118
+ "m2m_ids": [fields.Command.create({"name": "M2M Co-record"})],
119
+ }
120
+ self.assertEqual(record._get_write_diff_values(vals), vals)
121
+ record.write(vals) # Do the real update => the diff is not empty anyway
122
+ self.assertEqual(record._get_write_diff_values(vals), vals)
123
+
124
+ @mute_logger("bwd.test.model.log")
125
+ def test_11_get_write_diff_values_x2many_command_update(self):
126
+ """Test ``_get_write_diff_values()`` on fields.Command.update()
127
+
128
+ ``_get_write_diff_values()`` returns only the subset of IDs/values that should
129
+ be updated
130
+ """
131
+ record = self._create_records()
132
+ # Create and assign 2 corecords to each X2M field
133
+ record.o2m_ids = o2m_corecords = self._create_records(2)
134
+ record.m2m_ids = m2m_corecords = self._create_records(2)
135
+ # Set vals to update 1 corecord on each X2M field
136
+ vals = {
137
+ "o2m_ids": [
138
+ fields.Command.update(o2m_corecords[0].id, {"name": "O2M Corec"}),
139
+ fields.Command.update(
140
+ o2m_corecords[1].id, {"name": o2m_corecords[1].name}
141
+ ),
142
+ ],
143
+ "m2m_ids": [
144
+ fields.Command.update(
145
+ m2m_corecords[0].id, {"name": m2m_corecords[0].name}
146
+ ),
147
+ fields.Command.update(m2m_corecords[1].id, {"name": "M2M Corec"}),
148
+ ],
149
+ }
150
+ # The diff should include only the IDs we want to update, and the fields we are
151
+ # actually different on them
152
+ self.assertEqual(
153
+ record._get_write_diff_values(vals),
154
+ {
155
+ "o2m_ids": [
156
+ fields.Command.update(o2m_corecords[0].id, {"name": "O2M Corec"})
157
+ ],
158
+ "m2m_ids": [
159
+ fields.Command.update(m2m_corecords[1].id, {"name": "M2M Corec"})
160
+ ],
161
+ },
162
+ )
163
+ record.write(vals) # Do the real update => the diff should be empty now
164
+ self.assertEqual(record._get_write_diff_values(vals), {})
165
+
166
+ @mute_logger("bwd.test.model.log")
167
+ def test_12_get_write_diff_values_x2many_command_delete(self):
168
+ """Test ``_get_write_diff_values()`` on fields.Command.delete()
169
+
170
+ ``_get_write_diff_values()`` returns only the subset of IDs that should be
171
+ deleted/unlinked
172
+ """
173
+ record = self._create_records()
174
+ # Create and assign 2 corecords to each X2M field
175
+ record.o2m_ids = o2m_corecords = self._create_records(2)
176
+ record.m2m_ids = m2m_corecords = self._create_records(2)
177
+ # Set vals to delete 1 corecord in each X2M field
178
+ vals = {
179
+ "o2m_ids": [fields.Command.delete(o2m_corecords[0].id)],
180
+ "m2m_ids": [fields.Command.delete(m2m_corecords[1].id)],
181
+ }
182
+ # The diff should include only the IDs we want to delete
183
+ self.assertEqual(
184
+ record._get_write_diff_values(vals),
185
+ # Odoo assigns command "delete" or "unlink" according to the field type
186
+ # and its definition (not important for our purposes here)
187
+ {
188
+ "o2m_ids": [fields.Command.delete(o2m_corecords[0].id)],
189
+ "m2m_ids": [fields.Command.unlink(m2m_corecords[1].id)],
190
+ },
191
+ )
192
+ record.write(vals) # Do the real update => the diff should be empty now
193
+ self.assertEqual(record._get_write_diff_values(vals), {})
194
+
195
+ @mute_logger("bwd.test.model.log")
196
+ def test_13_get_write_diff_values_x2many_command_unlink(self):
197
+ """Test ``_get_write_diff_values()`` on fields.Command.unlink()
198
+
199
+ ``_get_write_diff_values()`` returns only the subset of IDs that should be
200
+ deleted/unlinked
201
+ """
202
+ record = self._create_records()
203
+ # Create and assign 2 corecords to each X2M field
204
+ record.o2m_ids = o2m_corecords = self._create_records(2)
205
+ record.m2m_ids = m2m_corecords = self._create_records(2)
206
+ # Set vals to unlink 1 corecord in each X2M field
207
+ vals = {
208
+ "o2m_ids": [fields.Command.unlink(o2m_corecords[0].id)],
209
+ "m2m_ids": [fields.Command.unlink(m2m_corecords[1].id)],
210
+ }
211
+ # The diff should include only the IDs we want to unlink
212
+ self.assertEqual(
213
+ record._get_write_diff_values(vals),
214
+ # Odoo assigns command "delete" or "unlink" according to the field type
215
+ # and its definition (not important for our purposes here)
216
+ {
217
+ "o2m_ids": [fields.Command.delete(o2m_corecords[0].id)],
218
+ "m2m_ids": [fields.Command.unlink(m2m_corecords[1].id)],
219
+ },
220
+ )
221
+ record.write(vals) # Do the real update => the diff should be empty now
222
+ self.assertEqual(record._get_write_diff_values(vals), {})
223
+
224
+ @mute_logger("bwd.test.model.log")
225
+ def test_14_get_write_diff_values_x2many_command_link(self):
226
+ """Test ``_get_write_diff_values()`` on fields.Command.link()
227
+
228
+ ``_get_write_diff_values()`` returns only the subset of IDs that should be
229
+ linked
230
+ """
231
+ record = self._create_records()
232
+ # Create 2 corecords
233
+ o2m_corecords = self._create_records(2)
234
+ m2m_corecords = self._create_records(2)
235
+ # Assign 1 corecord to each X2M field
236
+ record.write(
237
+ {
238
+ "o2m_ids": [fields.Command.set(o2m_corecords[0].ids)],
239
+ "m2m_ids": [fields.Command.set(m2m_corecords[1].ids)],
240
+ }
241
+ )
242
+ # Set vals to link all corecords on each X2M field
243
+ vals = {
244
+ "o2m_ids": [fields.Command.link(i) for i in o2m_corecords.ids],
245
+ "m2m_ids": [fields.Command.link(i) for i in m2m_corecords.ids],
246
+ }
247
+ # The diff should include only the IDs we want to link that are not already
248
+ # linked
249
+ self.assertEqual(
250
+ record._get_write_diff_values(vals),
251
+ # Odoo will update the commands to include the {"id": corecord.id} in them
252
+ {
253
+ "o2m_ids": [
254
+ (
255
+ fields.Command.LINK,
256
+ o2m_corecords[1].id,
257
+ {"id": o2m_corecords[1].id},
258
+ )
259
+ ],
260
+ "m2m_ids": [
261
+ (
262
+ fields.Command.LINK,
263
+ m2m_corecords[0].id,
264
+ {"id": m2m_corecords[0].id},
265
+ )
266
+ ],
267
+ },
268
+ )
269
+ record.write(vals) # Do the real update => the diff should be empty now
270
+ self.assertEqual(record._get_write_diff_values(vals), {})
271
+
272
+ @mute_logger("bwd.test.model.log")
273
+ def test_15_get_write_diff_values_x2many_command_clear(self):
274
+ """Test ``_get_write_diff_values()`` on fields.Command.clear()
275
+
276
+ ``_get_write_diff_values()`` returns only the subset of IDs that should be
277
+ deleted/unlinked
278
+ """
279
+ record = self._create_records()
280
+ # Create and assign 2 corecords to each X2M field
281
+ record.o2m_ids = o2m_corecords = self._create_records(2)
282
+ record.m2m_ids = m2m_corecords = self._create_records(2)
283
+ # Set vals to clear each X2M field
284
+ vals = {
285
+ "o2m_ids": [fields.Command.clear()],
286
+ "m2m_ids": [fields.Command.clear()],
287
+ }
288
+ self.assertEqual(
289
+ record._get_write_diff_values(vals),
290
+ # Odoo assigns command "delete" or "unlink" according to the field type
291
+ # and its definition (not important for our purposes here)
292
+ {
293
+ "o2m_ids": [fields.Command.delete(i) for i in o2m_corecords.ids],
294
+ "m2m_ids": [fields.Command.unlink(i) for i in m2m_corecords.ids],
295
+ },
296
+ )
297
+ record.write(vals) # Do the real update => the diff should be empty now
298
+ self.assertEqual(record._get_write_diff_values(vals), {})
299
+
300
+ @mute_logger("bwd.test.model.log")
301
+ def test_16_get_write_diff_values_x2many_command_set(self):
302
+ """Test ``_get_write_diff_values()`` on fields.Command.set()
303
+
304
+ ``_get_write_diff_values()`` behavior depends on various cases
305
+ """
306
+ record = self._create_records()
307
+ # Create 3 corecords for each X2M field
308
+ o2m_corecords = self._create_records(3)
309
+ m2m_corecords = self._create_records(3)
310
+
311
+ # Case 1:
312
+ # - X2M fields contain no corecords
313
+ # - we want to assign them some corecords
314
+ # => ``_get_write_diff_values()`` should return a ``fields.Command.link()``
315
+ # command for each corecord to add
316
+ self.assertEqual(
317
+ record._get_write_diff_values(
318
+ {
319
+ "o2m_ids": [fields.Command.set(o2m_corecords.ids)],
320
+ "m2m_ids": [fields.Command.set(m2m_corecords.ids)],
321
+ },
322
+ ),
323
+ # Odoo will update the commands to "link", and it will add the
324
+ # {"id": corecord.id} in them
325
+ {
326
+ "o2m_ids": [
327
+ (fields.Command.LINK, i, {"id": i}) for i in o2m_corecords.ids
328
+ ],
329
+ "m2m_ids": [
330
+ (fields.Command.LINK, i, {"id": i}) for i in m2m_corecords.ids
331
+ ],
332
+ },
333
+ )
334
+
335
+ # Case 2:
336
+ # - X2M fields contain some corecords
337
+ # - we want to replace them with different corecords
338
+ # => ``_get_write_diff_values()`` should return a
339
+ # ``fields.Command.[delete|unlink]()`` command for each corecord to remove,
340
+ # and a ``fields.Command.link()`` command for each corecord to add
341
+ record.o2m_ids = o2m_corecords[:1]
342
+ record.m2m_ids = m2m_corecords[:2]
343
+ self.assertEqual(
344
+ record._get_write_diff_values(
345
+ {
346
+ "o2m_ids": [fields.Command.set(o2m_corecords[1:].ids)],
347
+ "m2m_ids": [fields.Command.set(m2m_corecords[2:].ids)],
348
+ },
349
+ ),
350
+ # Odoo will update the commands to "unlink", "delete" and "link" (with the
351
+ # {"id": corecord.id} in the "link" ones)
352
+ {
353
+ "o2m_ids": [
354
+ (fields.Command.DELETE, i, 0) for i in o2m_corecords[:1].ids
355
+ ]
356
+ + [(fields.Command.LINK, i, {"id": i}) for i in o2m_corecords[1:].ids],
357
+ "m2m_ids": [
358
+ (fields.Command.UNLINK, i, 0) for i in m2m_corecords[:2].ids
359
+ ]
360
+ + [(fields.Command.LINK, i, {"id": i}) for i in m2m_corecords[2:].ids],
361
+ },
362
+ )
363
+
364
+ # Case 3:
365
+ # - X2M fields contain some corecords
366
+ # - we want to reassign the same corecords
367
+ # => ``_get_write_diff_values()`` should return nothing
368
+ record.o2m_ids = o2m_corecords
369
+ record.m2m_ids = m2m_corecords
370
+ self.assertEqual(
371
+ record._get_write_diff_values(
372
+ {
373
+ "o2m_ids": [fields.Command.set(o2m_corecords.ids)],
374
+ "m2m_ids": [fields.Command.set(m2m_corecords.ids)],
375
+ },
376
+ ),
377
+ {},
378
+ )
379
+
380
+ # Case 4:
381
+ # - X2M fields contain some corecords
382
+ # - we want to remove all corecords
383
+ # => ``_get_write_diff_values()`` should return a
384
+ # ``fields.Command.[delete|unlink]()`` command for each linked corecord
385
+ self.assertEqual(
386
+ record._get_write_diff_values(
387
+ {
388
+ "o2m_ids": [fields.Command.set([])],
389
+ "m2m_ids": [fields.Command.set([])],
390
+ },
391
+ ),
392
+ # Odoo will update the commands to "unlink" and "delete"
393
+ {
394
+ "o2m_ids": [(fields.Command.DELETE, i, 0) for i in o2m_corecords.ids],
395
+ "m2m_ids": [(fields.Command.UNLINK, i, 0) for i in m2m_corecords.ids],
396
+ },
397
+ )
398
+
399
+ # pylint: disable=W0104
400
+ def test_20_write_diff_computed_fields(self):
401
+ """Checks cache behavior for computed fields when diff-writing their deps"""
402
+ # Prepare the record, its fields values and the cache
403
+ record = self._create_records()
404
+ vals = {"length": 5, "width": 3, "height": 2}
405
+ record.write(vals)
406
+ fnames = ("perimeter", "area", "volume")
407
+ for fname in fnames:
408
+ with mute_logger("bwd.test.model.log"):
409
+ record[fname] # Dummy read: set fields in cache
410
+
411
+ # Use ``write`` w/ the same values: Odoo will need to recompute the computed
412
+ # fields values as soon as they're read
413
+ record.write(vals)
414
+ for fname in fnames:
415
+ with self.assertLogs("bwd.test.model.log", level="WARNING"):
416
+ record[fname] # Dummy read: check the compute method is triggered
417
+
418
+ # Use ``write_diff`` w/ the same values: Odoo won't need to recompute the
419
+ # computed fields values
420
+ record.write_diff(vals)
421
+ for fname in fnames:
422
+ with self.assertNoLogs("bwd.test.model.log", level="WARNING"):
423
+ record[fname] # Dummy read: check the compute method is not triggered
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.1
2
+ Name: odoo-addon-base_write_diff
3
+ Version: 17.0.1.0.0.2
4
+ Requires-Python: >=3.10
5
+ Requires-Dist: odoo>=17.0a,<17.1dev
6
+ Summary: Prevents updates on fields whose values won't change anyway
7
+ Home-page: https://github.com/OCA/server-tools
8
+ License: AGPL-3
9
+ Author: Camptocamp, Odoo Community Association (OCA)
10
+ Author-email: support@odoo-community.org
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Framework :: Odoo
13
+ Classifier: Framework :: Odoo :: 17.0
14
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
15
+ Description-Content-Type: text/x-rst
16
+
17
+ .. image:: https://odoo-community.org/readme-banner-image
18
+ :target: https://odoo-community.org/get-involved?utm_source=readme
19
+ :alt: Odoo Community Association
20
+
21
+ =================
22
+ Base - Write Diff
23
+ =================
24
+
25
+ ..
26
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
27
+ !! This file is generated by oca-gen-addon-readme !!
28
+ !! changes will be overwritten. !!
29
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
30
+ !! source digest: sha256:71745a37619c24019b67f8558eb6c7be1b974ad0031b6ce40892e7eec305bcb3
31
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
32
+
33
+ .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
34
+ :target: https://odoo-community.org/page/development-status
35
+ :alt: Beta
36
+ .. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
37
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
38
+ :alt: License: AGPL-3
39
+ .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
40
+ :target: https://github.com/OCA/server-tools/tree/17.0/base_write_diff
41
+ :alt: OCA/server-tools
42
+ .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
43
+ :target: https://translation.odoo-community.org/projects/server-tools-17-0/server-tools-17-0-base_write_diff
44
+ :alt: Translate me on Weblate
45
+ .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
46
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/server-tools&target_branch=17.0
47
+ :alt: Try me on Runboat
48
+
49
+ |badge1| |badge2| |badge3| |badge4| |badge5|
50
+
51
+ This module allows filtering values to update on records according to
52
+ whether they are actually different from the records' current values.
53
+
54
+ **Table of contents**
55
+
56
+ .. contents::
57
+ :local:
58
+
59
+ Usage
60
+ =====
61
+
62
+ **Summary**
63
+
64
+ This module allows you to update records by filtering out fields whose
65
+ values are going to be left unchanged by ``BaseModel.write()``; for
66
+ example, let's assume you have:
67
+
68
+ .. code:: python
69
+
70
+ >>> self
71
+ sale.order.line(1,)
72
+ >>> self.price_unit
73
+ 10.00
74
+
75
+ If you use ``self.write({"price_unit": 10.00})`` or
76
+ ``self.price_unit = 10.00``, Odoo may end up executing unnecessary
77
+ operations, like triggering the update on the field, recompute computed
78
+ fields that depend on ``price_unit``, and so on, even if the value is
79
+ actually unchanged.
80
+
81
+ By using this module, you can prevent all of that.
82
+
83
+ You can use this module in 3 different ways. All of them require you to
84
+ add this module as a dependency of your module.
85
+
86
+ **1 - Context key ``"write_use_diff_values"``**
87
+
88
+ By adding ``write_use_diff_values=True`` to the context when updating a
89
+ field value, the ``BaseModel.write()`` patch will take care of filtering
90
+ out the fields' values that are the same as the record's current ones.
91
+
92
+ ⚠️ Beware: the context key is propagated down to other ``write()`` calls
93
+
94
+ Example:
95
+
96
+ .. code:: python
97
+
98
+ from odoo import models
99
+
100
+
101
+ class ProductTemplate(models.Model):
102
+ _inherit = "product.template"
103
+
104
+ def write(self, vals):
105
+ # Update only fields that are actually different
106
+ self = self.with_context(write_use_diff_values=True)
107
+ return super().write(vals)
108
+
109
+
110
+ class ProductProduct(models.Model):
111
+ _inherit = "product.product"
112
+
113
+ def update_code_if_necessary(self, code: str):
114
+ # Update ``default_code`` only if different from the current value
115
+ self.with_context(write_use_diff_values=True).default_code = code
116
+
117
+ **2 - Method ``BaseModel.write_diff()``**
118
+
119
+ It is the same as calling ``write()``, but it automatically enables the
120
+ ``"write_use_diff_values"`` context flag: ``self.write_diff(vals)`` is a
121
+ shortcut for
122
+ ``self.with_context(write_use_diff_values=True).write(vals)``
123
+
124
+ ⚠️ Beware: the context key is propagated down to other ``write()`` calls
125
+
126
+ **3 - Method ``BaseModel._get_write_diff_values(vals)``**
127
+
128
+ This method accepts a write-like ``dict`` as param, and returns a new
129
+ ``dict`` made of the fields who will actually update the record's
130
+ values. This allows for a more flexible and customizable behavior than
131
+ the context key usage, because:
132
+
133
+ - you'll be able to filter out specific fields, instead of filtering out
134
+ all the fields whose values won't be changed after the update;
135
+ - you'll be able to execute the filtering on specific models, instead of
136
+ executing it on all the models involved in the stack of ``write()``
137
+ calls from the first usage of the context key down to the base method
138
+ ``BaseModel.write()``.
139
+
140
+ Example:
141
+
142
+ .. code:: python
143
+
144
+ from collections import defaultdict
145
+
146
+ from odoo import api, models
147
+ from odoo.tools.misc import frozendict
148
+
149
+
150
+ class ProductProduct(models.Model):
151
+ _inherit = "product.product"
152
+
153
+ def write(self, vals):
154
+ # OVERRIDE: ``odoo.addons.product.models.product_product.Product.write()``
155
+ # override will clear the whole registry cache if either 'active' or
156
+ # 'product_template_attribute_value_ids' are found in the ``vals`` dictionary:
157
+ # remove them unless it's necessary to update them
158
+ fnames = {"active", "product_template_attribute_value_ids"}
159
+ if vals_to_check := {f: vals.pop(f) for f in fnames.intersection(vals)}:
160
+ groups = defaultdict(lambda: self.browse())
161
+ for prod in self:
162
+ groups[frozendict(prod._get_write_diff_values(vals_to_check))] += prod
163
+ for diff_vals, prods in groups.items():
164
+ if res_vals := (vals | dict(diff_vals)):
165
+ super(ProductProduct, prods).write(res_vals)
166
+ return True
167
+ return super().write(vals)
168
+
169
+ Bug Tracker
170
+ ===========
171
+
172
+ Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
173
+ In case of trouble, please check there if your issue has already been reported.
174
+ If you spotted it first, help us to smash it by providing a detailed and welcomed
175
+ `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**>`_.
176
+
177
+ Do not contact contributors directly about support or help with technical issues.
178
+
179
+ Credits
180
+ =======
181
+
182
+ Authors
183
+ -------
184
+
185
+ * Camptocamp
186
+
187
+ Contributors
188
+ ------------
189
+
190
+ - Silvio Gregorini <silvio.gregorini@camptocamp.com>
191
+
192
+ Maintainers
193
+ -----------
194
+
195
+ This module is maintained by the OCA.
196
+
197
+ .. image:: https://odoo-community.org/logo.png
198
+ :alt: Odoo Community Association
199
+ :target: https://odoo-community.org
200
+
201
+ OCA, or the Odoo Community Association, is a nonprofit organization whose
202
+ mission is to support the collaborative development of Odoo features and
203
+ promote its widespread use.
204
+
205
+ This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/17.0/base_write_diff>`_ project on GitHub.
206
+
207
+ You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
@@ -0,0 +1,17 @@
1
+ odoo/addons/base_write_diff/README.rst,sha256=sZW-2soYGCfprm9XKjIgeeWjI2-9O6uOLgVcGZiZokk,6949
2
+ odoo/addons/base_write_diff/__init__.py,sha256=X9EJGOE2GtZbS0G82PtSXmWSZ_R8jEM0rlJTDliQjp4,21
3
+ odoo/addons/base_write_diff/__manifest__.py,sha256=wlJDjQpcjcz5Rbue3P_BXHS8s39-H-VLgkrSWxy4mYs,464
4
+ odoo/addons/base_write_diff/i18n/base_write_diff.pot,sha256=G8ybp7izelJrFwRs4Dq3QeOjXqNSQD0WYhZeX8e86OI,463
5
+ odoo/addons/base_write_diff/models/__init__.py,sha256=9pIQnc7H3xgVNpi7n3I18hg04S4IkX5mnAfl9cv7RBE,19
6
+ odoo/addons/base_write_diff/models/base.py,sha256=m_BvhzFy-Z1hFMe4mtI0g5za-WVXhRiy_CBRmG-DIVY,6022
7
+ odoo/addons/base_write_diff/readme/CONTRIBUTORS.md,sha256=I2EZz-IBsF_bZyhR1U8QoNZmB-m5qd7CGXRRRT0tkQE,57
8
+ odoo/addons/base_write_diff/readme/DESCRIPTION.md,sha256=KGpIKiOVAfAVofJx1WzWT69_Bge7AifVh4LvMG5n1GI,140
9
+ odoo/addons/base_write_diff/readme/USAGE.md,sha256=nRas9M95YRiVXh6FVUvTJ60YMBB6Fh2jhwBqvhM-2q8,3742
10
+ odoo/addons/base_write_diff/static/description/icon.png,sha256=CgnOEZCwoe6f1vlLwkqFVfc2q_uwBMU0UnXN8j6X5ag,10254
11
+ odoo/addons/base_write_diff/static/description/index.html,sha256=BPyYUioBiOJvIMe358jde59Xj4p-9jwJrQrrRoT3Eho,23403
12
+ odoo/addons/base_write_diff/tests/__init__.py,sha256=54LbOvo7gqDhCdt6fz1qGm7h946smek_vCcKGC1s-UI,35
13
+ odoo/addons/base_write_diff/tests/test_base_write_diff.py,sha256=n94okgQBw0-HGR-F8jWD0hKVZTN69605U-lhCucI7iY,18176
14
+ odoo_addon_base_write_diff-17.0.1.0.0.2.dist-info/METADATA,sha256=WfkSpbaPsvQqbqemPY5zi5mAVobMxr4OyYOpXkJlgJ4,7537
15
+ odoo_addon_base_write_diff-17.0.1.0.0.2.dist-info/WHEEL,sha256=ZhOvUsYhy81Dx67gN3TV0RchQWBIIzutDZaJODDg2Vo,81
16
+ odoo_addon_base_write_diff-17.0.1.0.0.2.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
17
+ odoo_addon_base_write_diff-17.0.1.0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: Whool 1.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+