Skip to content

Commit

Permalink
[IMP] compute_field_after_install: Group computes by compute function
Browse files Browse the repository at this point in the history
  • Loading branch information
paradoxxxzero committed Jan 6, 2025
1 parent c49a4b5 commit b3f5bf3
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 19 deletions.
46 changes: 28 additions & 18 deletions compute_field_after_install/models/recompute_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@


import logging
from functools import reduce
from itertools import groupby

from odoo import api, fields, models
from odoo.exceptions import Warning as UserError
from odoo.tools.translate import _

from odoo.tools import config
from odoo.tools.translate import _

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -62,32 +63,41 @@ def _run_all(self):
return self.search([("state", "=", "todo")]).run()

def run(self):
for task in self:
cursor = self.env.cr
model = self.env[task.model]
# Group tasks by compute method to avoid computing multifields computes multiple times
model_tasks = groupby(
self,
lambda task: (task.model, self.env[task.model]._fields[task.field].compute),
)
for (model, _compute_fun), tasks in model_tasks:
tasks = reduce(lambda x, y: x | y, tasks)
fields = tasks.mapped("field")
last_id = max(tasks.mapped("last_id"), default=None)
step = min(tasks.mapped("step"))

while True:
_logger.info(
"Recompute field %s for model %s in background. Last id %d",
task.field,
task.model,
task.last_id,
"Recompute fields %s for model %s in background. Last id %d",
fields,
model,
last_id,
)
records = model.search(
[("id", "<", task.last_id)] if task.last_id else [],
limit=task.step,
records = self.env[model].search(
[("id", "<", last_id)] if last_id else [],
limit=step,
order="id desc",
)
if not records:
task.state = "done"
cursor.commit()
tasks.state = "done"
self.env.cr.commit() # pylint: disable=E8102
break
for field in fields:
field_ = records._fields[field]
self.env.add_to_compute(field_, records)

field = records._fields[task.field]
self.env.add_to_compute(field, records)
records.recompute()
task.last_id = records[-1].id
cursor.commit()
last_id = records[-1].id
tasks.last_id = last_id
self.env.cr.commit() # pylint: disable=E8102

return True

Expand Down
59 changes: 58 additions & 1 deletion compute_field_after_install/tests/test_recompute.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from unittest.mock import Mock, patch
from contextlib import contextmanager
from unittest.mock import Mock, patch

from odoo.tests import TransactionCase
from odoo.tools import config
Expand Down Expand Up @@ -56,6 +56,7 @@ def test_add_fields(self):
# Purge field commercial_company_name to simulate
# the installation of a new field
self.env.cr.execute("UPDATE res_partner SET commercial_company_name=null")
self.env.invalidate_all()

partner = self.env.ref("base.res_partner_address_7")
self.assertFalse(partner.commercial_company_name)
Expand Down Expand Up @@ -98,6 +99,7 @@ def test_add_fields_batch(self):
# Purge field commercial_company_name to simulate
# the installation of a new field
self.env.cr.execute("UPDATE res_partner SET commercial_company_name=null")
self.env.invalidate_all()

partner = self.env.ref("base.res_partner_address_7")
self.assertFalse(partner.commercial_company_name)
Expand Down Expand Up @@ -187,3 +189,58 @@ def test_default_step(self):
)
self.assertEqual(recompute_field.step, 4000)

def test_multifields(self):
if "sale.order" not in self.env:
self.skipTest("This test requires the sale module to be installed")

Check warning on line 194 in compute_field_after_install/tests/test_recompute.py

View check run for this annotation

Codecov / codecov/patch

compute_field_after_install/tests/test_recompute.py#L194

Added line #L194 was not covered by tests

records = self.env["sale.order"].search([])
original_id_amounts = {
record.id: (record.amount_untaxed, record.amount_tax, record.amount_total)
for record in records
}

with patch.dict(
config.options, {"computed_fields_defer_threshold": 1}, clear=True
):
for field in ("amount_untaxed", "amount_tax", "amount_total"):
self.env(context={"module": "fake_module"}).add_to_compute(
records._fields[field], records
)

# Check that jobs have been created
recompute_fields = self.env["recompute.field"].search(
[
("model", "=", "sale.order"),
]
)
self.assertEqual(len(recompute_fields), 3)
self.assertEqual(recompute_fields.mapped("state"), ["todo", "todo", "todo"])

# Purge field commercial_company_name to simulate
# the installation of a new field
self.env.cr.execute(
"UPDATE sale_order SET amount_untaxed=null, amount_tax=null, amount_total=null"
)
self.env.invalidate_all()

for record in records:
self.assertFalse(record.amount_untaxed)
self.assertFalse(record.amount_tax)
self.assertFalse(record.amount_total)

# Run the cron to process computed field

with self._count_computes(
self.env["sale.order"],
"_compute_amounts",
) as computed:
self.env["recompute.field"]._run_all()
self.assertEqual(computed["records"], len(records))
self.assertEqual(computed["calls"], 1)

self.assertEqual(recompute_fields.mapped("state"), ["done", "done", "done"])

for record in records:
self.assertEqual(record.amount_untaxed, original_id_amounts[record.id][0])
self.assertEqual(record.amount_tax, original_id_amounts[record.id][1])
self.assertEqual(record.amount_total, original_id_amounts[record.id][2])

0 comments on commit b3f5bf3

Please sign in to comment.