Maintainers
+Maintainers
This module is maintained by the OCA.
@@ -475,5 +480,6 @@ This addon adds an integrated Job Queue to Odoo.
diff --git a/queue_job/tests/common.py b/queue_job/tests/common.py index ca88c31b47..8773d51cc6 100644 --- a/queue_job/tests/common.py +++ b/queue_job/tests/common.py @@ -1,21 +1,24 @@ # Copyright 2019 Camptocamp # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import doctest -import logging import typing from contextlib import contextmanager from itertools import groupby from operator import attrgetter from unittest import TestCase, mock -from odoo.tests.case import TestCase as _TestCase -from odoo.tests.common import MetaCase +from odoo.tests.common import SETATTR_SOURCES from odoo.addons.queue_job.delay import Graph # pylint: disable=odoo-addons-relative-import from odoo.addons.queue_job.job import Job +# Register queue_job patcher as a known path to prevent verbose warning +# in tests when methods are patched. +SETATTR_SOURCES["_patch_method"] = tuple( + list(SETATTR_SOURCES.get("_patch_method", [])) + ["/queue_job/models/base.py"], +) + @contextmanager def trap_jobs(): @@ -211,7 +214,7 @@ def assert_enqueued_job(self, method, args=None, kwargs=None, properties=None): if expected_call not in actual_calls: raise AssertionError( - "Job {} was not enqueued.\n" "Actual enqueued jobs:\n{}".format( + "Job {} was not enqueued.\nActual enqueued jobs:\n{}".format( self._format_job_call(expected_call), "\n".join( f" * {self._format_job_call(call)}" for call in actual_calls @@ -411,51 +414,3 @@ def test_export(self): delayable = mock.MagicMock(name="DelayableBinding") delayable_cls.return_value = delayable yield delayable_cls, delayable - - -class OdooDocTestCase(doctest.DocTestCase, _TestCase, MetaCase("DummyCase", (), {})): - """ - We need a custom DocTestCase class in order to: - - define test_tags to run as part of standard tests - - output a more meaningful test name than default "DocTestCase.runTest" - """ - - def __init__( - self, doctest, optionflags=0, setUp=None, tearDown=None, checker=None, seq=0 - ): - super().__init__( - doctest._dt_test, - optionflags=optionflags, - setUp=setUp, - tearDown=tearDown, - checker=checker, - ) - self.test_sequence = seq - - def setUp(self): - """Log an extra statement which test is started.""" - super().setUp() - logging.getLogger(__name__).info("Running tests for %s", self._dt_test.name) - - -def load_doctests(module): - """ - Generates a tests loading method for the doctests of the given module - https://docs.python.org/3/library/unittest.html#load-tests-protocol - """ - - def load_tests(loader, tests, ignore): - """ - Apply the 'test_tags' attribute to each DocTestCase found by the DocTestSuite. - Also extend the DocTestCase class trivially to fit the class teardown - that Odoo backported for its own test classes from Python 3.8. - """ - - for idx, test in enumerate(doctest.DocTestSuite(module)): - odoo_test = OdooDocTestCase(test, seq=idx) - odoo_test.test_tags = {"standard", "at_install", "queue_job", "doctest"} - tests.addTest(odoo_test) - - return tests - - return load_tests diff --git a/queue_job/tests/test_runner_channels.py b/queue_job/tests/test_runner_channels.py index d323d00683..b00dd26261 100644 --- a/queue_job/tests/test_runner_channels.py +++ b/queue_job/tests/test_runner_channels.py @@ -1,10 +1,17 @@ # Copyright 2015-2016 Camptocamp SA # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) +import doctest + +from odoo.tests import BaseCase, tagged # pylint: disable=odoo-addons-relative-import # we are testing, we want to test as we were an external consumer of the API from odoo.addons.queue_job.jobrunner import channels -from .common import load_doctests -load_tests = load_doctests(channels) +@tagged("doctest") +class TestDoctest(BaseCase): + def test_doctest(self): + doctest.testmod( + channels, exclude_empty=True, optionflags=doctest.REPORT_ONLY_FIRST_FAILURE + ) diff --git a/queue_job/tests/test_runner_runner.py b/queue_job/tests/test_runner_runner.py index 131ce6322d..1adfa790dd 100644 --- a/queue_job/tests/test_runner_runner.py +++ b/queue_job/tests/test_runner_runner.py @@ -1,17 +1,21 @@ # Copyright 2015-2016 Camptocamp SA # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) - -# pylint: disable=odoo-addons-relative-import -# we are testing, we want to test as we were an external consumer of the API +import doctest import os from odoo.tests import BaseCase, tagged +# pylint: disable=odoo-addons-relative-import +# we are testing, we want to test as we were an external consumer of the API from odoo.addons.queue_job.jobrunner import runner -from .common import load_doctests -load_tests = load_doctests(runner) +@tagged("doctest") +class TestDoctest(BaseCase): + def test_doctest(self): + doctest.testmod( + runner, exclude_empty=True, optionflags=doctest.REPORT_ONLY_FIRST_FAILURE + ) @tagged("-at_install", "post_install") diff --git a/queue_job_batch/README.rst b/queue_job_batch/README.rst index d0b9d22c38..57dc08f67b 100644 --- a/queue_job_batch/README.rst +++ b/queue_job_batch/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 + =============== Job Queue Batch =============== @@ -7,13 +11,13 @@ Job Queue Batch !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:b6e605728ea05be0bf98dbe6e94c197bab21882e965286ef214f719e252b444e + !! source digest: sha256:ca835258ad181cc54a5ba0eea86590c59e71b3aa70582a7ce115b83caa952991 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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%2Fqueue-lightgray.png?logo=github diff --git a/queue_job_batch/__manifest__.py b/queue_job_batch/__manifest__.py index 669ba10927..7b9a0abc54 100644 --- a/queue_job_batch/__manifest__.py +++ b/queue_job_batch/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Job Queue Batch", - "version": "18.0.1.0.0", + "version": "18.0.1.1.1", "author": "Creu Blanca,Odoo Community Association (OCA)", "website": "https://github.com/OCA/queue", "license": "AGPL-3", diff --git a/queue_job_batch/models/queue_job.py b/queue_job_batch/models/queue_job.py index ddc3efe879..524da23ef7 100644 --- a/queue_job_batch/models/queue_job.py +++ b/queue_job_batch/models/queue_job.py @@ -9,7 +9,7 @@ class QueueJob(models.Model): _inherit = "queue.job" - job_batch_id = fields.Many2one("queue.job.batch") + job_batch_id = fields.Many2one("queue.job.batch", index="btree_not_null") @api.model_create_multi def create(self, vals_list): diff --git a/queue_job_batch/models/queue_job_batch.py b/queue_job_batch/models/queue_job_batch.py index 998f7a5ac2..a0d084040d 100644 --- a/queue_job_batch/models/queue_job_batch.py +++ b/queue_job_batch/models/queue_job_batch.py @@ -62,9 +62,11 @@ class QueueJobBatch(models.Model): compute="_compute_job_count", ) - def _get_state(self): - self.ensure_one() - job_states = set(self.job_ids.grouped("state").keys()) + def _get_state(self, job_states): + """Determine the batch state from a set of job states. + + :param job_states: set of state strings for all jobs in this batch + """ if all(state in ("done", "cancelled", "failed") for state in job_states): return "finished" elif {"done", "started"} & job_states: @@ -74,8 +76,20 @@ def _get_state(self): return "pending" def check_state(self): + grouped = self.env["queue.job"].read_group( + [("job_batch_id", "in", self.ids)], + ["job_batch_id", "state"], + ["job_batch_id", "state"], + lazy=False, + ) + states_by_batch = {} + for g in grouped: + batch_id = g["job_batch_id"][0] + states_by_batch.setdefault(batch_id, set()).add(g["state"]) + for rec in self: - if (state := rec._get_state()) != rec.state: + job_states = states_by_batch.get(rec.id, set()) + if (state := rec._get_state(job_states)) != rec.state: rec.state = state def set_read(self): @@ -101,13 +115,29 @@ def get_new_batch(self, name, **kwargs): @api.depends("job_ids.state") def _compute_job_count(self): + grouped = self.env["queue.job"].read_group( + [("job_batch_id", "in", self.ids)], + ["job_batch_id", "state"], + ["job_batch_id", "state"], + lazy=False, + ) + counts = {} + for g in grouped: + batch_id = g["job_batch_id"][0] + counts.setdefault(batch_id, {}) + counts[batch_id][g["state"]] = g["__count"] + for rec in self: - jobs_by_state = rec.job_ids.grouped("state") - rec.job_count = len(rec.job_ids) - rec.failed_job_count = len(jobs_by_state.get("failed", [])) - rec.finished_job_count = len(jobs_by_state.get("done", [])) - rec.completeness = rec.finished_job_count / max(1, rec.job_count) - rec.failed_percentage = rec.failed_job_count / max(1, rec.job_count) + by_state = counts.get(rec.id, {}) + total = sum(by_state.values()) + done = by_state.get("done", 0) + failed = by_state.get("failed", 0) + + rec.job_count = total + rec.failed_job_count = failed + rec.finished_job_count = done + rec.completeness = done / max(1, total) + rec.failed_percentage = failed / max(1, total) @api.model def _to_store_fnames(self): diff --git a/queue_job_batch/security/security.xml b/queue_job_batch/security/security.xml index 9443708ae0..a033a049c7 100644 --- a/queue_job_batch/security/security.xml +++ b/queue_job_batch/security/security.xml @@ -20,7 +20,7 @@This addon adds an a grouper for queue jobs.
It allows to show your jobs in a batched form in order to know better the results.
Example:
-from odoo import models, fields, api +from odoo import models, fields, api -class MyModel(models.Model): +class MyModel(models.Model): _name = 'my.model' - def my_method(self, a, k=None): + def my_method(self, a, k=None): _logger.info('executed with a: %s and k: %s', a, k) -class MyOtherModel(models.Model): +class MyOtherModel(models.Model): _name = 'my.other.model' @api.multi - def button_do_stuff(self): + def button_do_stuff(self): batch = self.env['queue.job.batch'].get_new_batch('Group') model = self.env['my.model'].with_context(job_batch=batch) for i in range(1, 100): @@ -415,13 +420,13 @@Job Queue Batch
You can manage your batch jobs from the Systray. A new button will be shown with your currently executing job batches and the recently finished job groups.
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,15 +434,15 @@
Do not contact contributors directly about support or help with technical issues.
The migration of this module from 12.0 to 14.0 was financially supported by Camptocamp