Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,46 @@
# Queue Job Cron Jobrunner
Odoo addon: queue_job_cron_jobrunner
## Installation
```bash
pip install odoo-bringout-oca-queue-queue_job_cron_jobrunner
```
## Dependencies
This addon depends on:
- queue_job
## Manifest Information
- **Name**: Queue Job Cron Jobrunner
- **Version**: 16.0.1.1.0
- **Category**: Others
- **License**: AGPL-3
- **Installable**: True
## Source
Based on [OCA/queue](https://github.com/OCA/queue) branch 16.0, addon `queue_job_cron_jobrunner`.
## License
This package maintains the original AGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Queue_job_cron_jobrunner Module - queue_job_cron_jobrunner
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for queue_job_cron_jobrunner. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,5 @@
# Dependencies
This addon depends on:
- [queue_job](../../odoo-bringout-oca-queue-queue_job)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon queue_job_cron_jobrunner or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-queue-queue_job_cron_jobrunner"
# or
uv pip install odoo-bringout-oca-queue-queue_job_cron_jobrunner"
```

View file

@ -0,0 +1,13 @@
# Models
Detected core models and extensions in queue_job_cron_jobrunner.
```mermaid
classDiagram
class ir_cron
class queue_job
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: queue_job_cron_jobrunner. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon queue_job_cron_jobrunner
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,8 @@
# Security
This module does not define custom security rules or access controls beyond Odoo defaults.
Default Odoo security applies:
- Base user access through standard groups
- Model access inherited from dependencies
- No custom row-level security rules

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon queue_job_cron_jobrunner
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,42 @@
[project]
name = "odoo-bringout-oca-queue-queue_job_cron_jobrunner"
version = "16.0.0"
description = "Queue Job Cron Jobrunner - Run jobs without a dedicated JobRunner"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-queue-queue_job>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["queue_job_cron_jobrunner"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,136 @@
========================
Queue Job Cron Jobrunner
========================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:1dd710838363f7266378ba0b5d01df2c27d29a3c3275ca5aebe37a851cc07f49
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |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%2Fqueue-lightgray.png?logo=github
:target: https://github.com/OCA/queue/tree/16.0/queue_job_cron_jobrunner
:alt: OCA/queue
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/queue-16-0/queue-16-0-queue_job_cron_jobrunner
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/queue&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module implements a simple ``queue.job`` runner using ``ir.cron`` triggers.
It's meant to be used on environments where the regular job runner can't be run, like
on Odoo.sh.
Unlike the regular job runner, where jobs are dispatched to the HttpWorkers, jobs are
processed on the CronWorker threads by the job runner crons. This is a design decision
because:
* Odoo.sh puts HttpWorkers to sleep when there's no network activity
* HttpWorkers are meant for traffic. Users shouldn't pay the price of background tasks.
For now, it only implements the most basic features of the ``queue_job`` runner, notably
no channel capacity nor priorities. Please check the ROADMAP for further details.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_
**Table of contents**
.. contents::
:local:
Configuration
=============
.. warning::
Don't use this module if you're already running the regular ``queue_job`` runner.
For the easiest case, no configuration is required besides installing the module.
To avoid CronWorker CPU timeout from abruptly stopping the job processing cron, it's
recommended to launch Odoo with ``--limit-time-real-cron=0``, to disable the CronWorker
timeout altogether.
.. note::
In Odoo.sh, this is done by default.
Parallel execution of jobs can be achieved by leveraging multiple ``ir.cron`` records:
* Make sure you have enough CronWorkers available (Odoo CLI ``--max-cron-threads``)
* Duplicate the ``queue_job_cron`` cron record as many times as needed, until you have
as much records as cron workers.
Known issues / Roadmap
======================
* Support channel capacity and priority. (See ``_acquire_one_job``)
* Gracefully handle CronWorker CPU timeouts. (See ``_job_runner``)
* Commit transaction after job state updated to started. (See ``_process``)
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/queue/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 <https://github.com/OCA/queue/issues/new?body=module:%20queue_job_cron_jobrunner%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Camptocamp SA
Contributors
~~~~~~~~~~~~
* `Camptocamp <https://www.camptocamp.com>`_
* Iván Todorovich <ivan.todorovich@camptocamp.com>
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.
.. |maintainer-ivantodorovich| image:: https://github.com/ivantodorovich.png?size=40px
:target: https://github.com/ivantodorovich
:alt: ivantodorovich
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-ivantodorovich|
This module is part of the `OCA/queue <https://github.com/OCA/queue/tree/16.0/queue_job_cron_jobrunner>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1 @@
from . import models

View file

@ -0,0 +1,17 @@
{
"name": "Queue Job Cron Jobrunner",
"summary": "Run jobs without a dedicated JobRunner",
"version": "16.0.1.1.0",
"development_status": "Alpha",
"author": "Camptocamp SA, Odoo Community Association (OCA)",
"maintainers": ["ivantodorovich"],
"website": "https://github.com/OCA/queue",
"license": "AGPL-3",
"category": "Others",
"depends": ["queue_job"],
"data": [
"data/ir_cron.xml",
"views/ir_cron.xml",
],
"installable": True,
}

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="queue_job_cron" model="ir.cron">
<field name="name">Queue Job Runner</field>
<field name="model_id" ref="queue_job.model_queue_job" />
<field name="state">code</field>
<field name="code">model._job_runner()</field>
<field name="queue_job_runner" eval="True" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
</record>
</odoo>

View file

@ -0,0 +1,43 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * queue_job_cron_jobrunner
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.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: queue_job_cron_jobrunner
#: model:ir.model.fields,help:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "If checked, the cron is considered to be a queue.job runner."
msgstr "Ako je označeno, cron se smatra pokretačem queue.job-a."
#. module: queue_job_cron_jobrunner
#. odoo-python
#: code:addons/queue_job_cron_jobrunner/models/queue_job.py:0
#, python-format
msgid "Job interrupted and set to Done: nothing to do."
msgstr "Posao prekinut i postavljen na završeno: nema šta da se radi."
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_queue_job
msgid "Queue Job"
msgstr "Red poslova"
#. module: queue_job_cron_jobrunner
#: model:ir.actions.server,name:queue_job_cron_jobrunner.queue_job_cron_ir_actions_server
#: model:ir.cron,cron_name:queue_job_cron_jobrunner.queue_job_cron
#: model:ir.model.fields,field_description:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "Queue Job Runner"
msgstr "Pokretač reda poslova"
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_ir_cron
msgid "Scheduled Actions"
msgstr "Vremenski plan akcija"

View file

@ -0,0 +1,46 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * queue_job_cron_jobrunner
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-12-01 19:35+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\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.17\n"
#. module: queue_job_cron_jobrunner
#: model:ir.model.fields,help:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "If checked, the cron is considered to be a queue.job runner."
msgstr "Si está marcada, el cron se considera un ejecutor de queue.job runner."
#. module: queue_job_cron_jobrunner
#. odoo-python
#: code:addons/queue_job_cron_jobrunner/models/queue_job.py:0
#, python-format
msgid "Job interrupted and set to Done: nothing to do."
msgstr "Trabajo interrumpido y marcado como hecho: nada que hacer."
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_queue_job
msgid "Queue Job"
msgstr "Cola de Trabajo"
#. module: queue_job_cron_jobrunner
#: model:ir.actions.server,name:queue_job_cron_jobrunner.queue_job_cron_ir_actions_server
#: model:ir.cron,cron_name:queue_job_cron_jobrunner.queue_job_cron
#: model:ir.model.fields,field_description:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "Queue Job Runner"
msgstr "Cola de Ejecución de Trabajos"
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_ir_cron
msgid "Scheduled Actions"
msgstr "Acciones Planificadas"

View file

@ -0,0 +1,46 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * queue_job_cron_jobrunner
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-01-11 09:34+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\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 4.17\n"
#. module: queue_job_cron_jobrunner
#: model:ir.model.fields,help:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "If checked, the cron is considered to be a queue.job runner."
msgstr "Se selezionata, il cron è considerato un esecutore del queue.job."
#. module: queue_job_cron_jobrunner
#. odoo-python
#: code:addons/queue_job_cron_jobrunner/models/queue_job.py:0
#, python-format
msgid "Job interrupted and set to Done: nothing to do."
msgstr "Lavoro interrotto e impostato a completato: nulla da fare."
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_queue_job
msgid "Queue Job"
msgstr "Lavoro in coda"
#. module: queue_job_cron_jobrunner
#: model:ir.actions.server,name:queue_job_cron_jobrunner.queue_job_cron_ir_actions_server
#: model:ir.cron,cron_name:queue_job_cron_jobrunner.queue_job_cron
#: model:ir.model.fields,field_description:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "Queue Job Runner"
msgstr "Esecutore lavoro in coda"
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_ir_cron
msgid "Scheduled Actions"
msgstr "Azioni pianificate"

View file

@ -0,0 +1,43 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * queue_job_cron_jobrunner
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.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: queue_job_cron_jobrunner
#: model:ir.model.fields,help:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "If checked, the cron is considered to be a queue.job runner."
msgstr ""
#. module: queue_job_cron_jobrunner
#. odoo-python
#: code:addons/queue_job_cron_jobrunner/models/queue_job.py:0
#, python-format
msgid "Job interrupted and set to Done: nothing to do."
msgstr ""
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_queue_job
msgid "Queue Job"
msgstr ""
#. module: queue_job_cron_jobrunner
#: model:ir.actions.server,name:queue_job_cron_jobrunner.queue_job_cron_ir_actions_server
#: model:ir.cron,cron_name:queue_job_cron_jobrunner.queue_job_cron
#: model:ir.model.fields,field_description:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "Queue Job Runner"
msgstr ""
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_ir_cron
msgid "Scheduled Actions"
msgstr ""

View file

@ -0,0 +1,47 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * queue_job_cron_jobrunner
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2022-11-23 09:45+0000\n"
"Last-Translator: Dorin Hongu <dhongu@gmail.com>\n"
"Language-Team: none\n"
"Language: ro\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < "
"20)) ? 1 : 2;\n"
"X-Generator: Weblate 4.14.1\n"
#. module: queue_job_cron_jobrunner
#: model:ir.model.fields,help:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "If checked, the cron is considered to be a queue.job runner."
msgstr "Dacă este bifat, cron-ul este considerat a fi un runner queue.job."
#. module: queue_job_cron_jobrunner
#. odoo-python
#: code:addons/queue_job_cron_jobrunner/models/queue_job.py:0
#, python-format
msgid "Job interrupted and set to Done: nothing to do."
msgstr "Job întrerupt și setat la Terminat: nimic de făcut."
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_queue_job
msgid "Queue Job"
msgstr "Coadă sarcini"
#. module: queue_job_cron_jobrunner
#: model:ir.actions.server,name:queue_job_cron_jobrunner.queue_job_cron_ir_actions_server
#: model:ir.cron,cron_name:queue_job_cron_jobrunner.queue_job_cron
#: model:ir.model.fields,field_description:queue_job_cron_jobrunner.field_ir_cron__queue_job_runner
msgid "Queue Job Runner"
msgstr ""
#. module: queue_job_cron_jobrunner
#: model:ir.model,name:queue_job_cron_jobrunner.model_ir_cron
msgid "Scheduled Actions"
msgstr ""

View file

@ -0,0 +1,2 @@
from . import ir_cron
from . import queue_job

View file

@ -0,0 +1,13 @@
# Copyright 2022 Camptocamp SA (https://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class IrCron(models.Model):
_inherit = "ir.cron"
queue_job_runner = fields.Boolean(
help="If checked, the cron is considered to be a queue.job runner.",
)

View file

@ -0,0 +1,168 @@
# Copyright 2022 Camptocamp SA (https://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
import traceback
from io import StringIO
from psycopg2 import OperationalError
from odoo import _, api, models, tools
from odoo.service.model import PG_CONCURRENCY_ERRORS_TO_RETRY
from odoo.addons.queue_job.controllers.main import PG_RETRY
from odoo.addons.queue_job.exception import (
FailedJobError,
NothingToDoJob,
RetryableJobError,
)
from odoo.addons.queue_job.job import Job
_logger = logging.getLogger(__name__)
class QueueJob(models.Model):
_inherit = "queue.job"
@api.model
def _acquire_one_job(self):
"""Acquire the next job to be run.
:returns: queue.job record (locked for update)
"""
# TODO: This method should respect channel priority and capacity,
# rather than just fetching them by creation date.
self.env.flush_all()
self.env.cr.execute(
"""
SELECT id
FROM queue_job
WHERE state = 'pending'
AND (eta IS NULL OR eta <= (now() AT TIME ZONE 'UTC'))
ORDER BY priority, date_created
LIMIT 1 FOR NO KEY UPDATE SKIP LOCKED
"""
)
row = self.env.cr.fetchone()
return self.browse(row and row[0])
def _process(self, commit=False):
"""Process the job"""
self.ensure_one()
job = Job._load_from_db_record(self)
# Set it as started
job.set_started()
job.store()
_logger.debug("%s started", job.uuid)
# TODO: Commit the state change so that the state can be read from the UI
# while the job is processing. However, doing this will release the
# lock on the db, so we need to find another way.
# if commit:
# self.flush()
# self.env.cr.commit()
# Actual processing
try:
try:
with self.env.cr.savepoint():
job.perform()
job.set_done()
job.store()
except OperationalError as err:
# Automatically retry the typical transaction serialization errors
if err.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY:
raise
message = tools.ustr(err.pgerror, errors="replace")
job.postpone(result=message, seconds=PG_RETRY)
job.set_pending(reset_retry=False)
job.store()
_logger.debug("%s OperationalError, postponed", job)
except NothingToDoJob as err:
if str(err):
msg = str(err)
else:
msg = _("Job interrupted and set to Done: nothing to do.")
job.set_done(msg)
job.store()
except RetryableJobError as err:
# delay the job later, requeue
job.postpone(result=str(err), seconds=5)
job.set_pending(reset_retry=False)
job.store()
_logger.debug("%s postponed", job)
except (FailedJobError, Exception):
with StringIO() as buff:
traceback.print_exc(file=buff)
_logger.error(buff.getvalue())
job.set_failed(exc_info=buff.getvalue())
job.store()
if commit: # pragma: no cover
self.env.flush_all()
self.env.cr.commit() # pylint: disable=invalid-commit
_logger.debug("%s enqueue depends started", job)
job.enqueue_waiting()
_logger.debug("%s enqueue depends done", job)
@api.model
def _job_runner(self, commit=True):
"""Short-lived job runner, triggered by async crons"""
job = self._acquire_one_job()
while job:
job._process(commit=commit)
job = self._acquire_one_job()
# TODO: If limit_time_real_cron is reached before all the jobs are done,
# the worker will be killed abruptly.
# Ideally, find a way to know if we're close to reaching this limit,
# stop processing, and trigger a new execution to continue.
#
# if job and limit_time_real_cron_reached_or_about_to_reach:
# self._cron_trigger()
# break
@api.model
def _cron_trigger(self, at=None):
"""Trigger the cron job runners
Odoo will prevent concurrent cron jobs from running.
So, to support parallel execution, we'd need to have (at least) the
same number of ir.crons records as cron workers.
All crons should be triggered at the same time.
"""
crons = self.env["ir.cron"].sudo().search([("queue_job_runner", "=", True)])
for cron in crons:
cron._trigger(at=at)
def _ensure_cron_trigger(self):
"""Create cron triggers for these jobs"""
records = self.filtered(lambda r: r.state == "pending")
if not records:
return
# Trigger immediate runs
immediate = any(not rec.eta for rec in records)
if immediate:
self._cron_trigger()
# Trigger delayed eta runs
delayed_etas = {rec.eta for rec in records if rec.eta}
if delayed_etas:
self._cron_trigger(at=list(delayed_etas))
@api.model_create_multi
def create(self, vals_list):
# When jobs are created, also create the cron trigger
records = super().create(vals_list)
records._ensure_cron_trigger()
return records
def write(self, vals):
# When a job state or eta changes, make sure a cron trigger is created
res = super().write(vals)
if "state" in vals or "eta" in vals:
self._ensure_cron_trigger()
return res

View file

@ -0,0 +1,21 @@
.. warning::
Don't use this module if you're already running the regular ``queue_job`` runner.
For the easiest case, no configuration is required besides installing the module.
To avoid CronWorker CPU timeout from abruptly stopping the job processing cron, it's
recommended to launch Odoo with ``--limit-time-real-cron=0``, to disable the CronWorker
timeout altogether.
.. note::
In Odoo.sh, this is done by default.
Parallel execution of jobs can be achieved by leveraging multiple ``ir.cron`` records:
* Make sure you have enough CronWorkers available (Odoo CLI ``--max-cron-threads``)
* Duplicate the ``queue_job_cron`` cron record as many times as needed, until you have
as much records as cron workers.

View file

@ -0,0 +1,3 @@
* `Camptocamp <https://www.camptocamp.com>`_
* Iván Todorovich <ivan.todorovich@camptocamp.com>

View file

@ -0,0 +1,14 @@
This module implements a simple ``queue.job`` runner using ``ir.cron`` triggers.
It's meant to be used on environments where the regular job runner can't be run, like
on Odoo.sh.
Unlike the regular job runner, where jobs are dispatched to the HttpWorkers, jobs are
processed on the CronWorker threads by the job runner crons. This is a design decision
because:
* Odoo.sh puts HttpWorkers to sleep when there's no network activity
* HttpWorkers are meant for traffic. Users shouldn't pay the price of background tasks.
For now, it only implements the most basic features of the ``queue_job`` runner, notably
no channel capacity nor priorities. Please check the ROADMAP for further details.

View file

@ -0,0 +1,3 @@
* Support channel capacity and priority. (See ``_acquire_one_job``)
* Gracefully handle CronWorker CPU timeouts. (See ``_job_runner``)
* Commit transaction after job state updated to started. (See ``_process``)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,479 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Queue Job Cron Jobrunner</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="queue-job-cron-jobrunner">
<h1 class="title">Queue Job Cron Jobrunner</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:1dd710838363f7266378ba0b5d01df2c27d29a3c3275ca5aebe37a851cc07f49
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/queue/tree/16.0/queue_job_cron_jobrunner"><img alt="OCA/queue" src="https://img.shields.io/badge/github-OCA%2Fqueue-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/queue-16-0/queue-16-0-queue_job_cron_jobrunner"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/queue&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module implements a simple <tt class="docutils literal">queue.job</tt> runner using <tt class="docutils literal">ir.cron</tt> triggers.</p>
<p>Its meant to be used on environments where the regular job runner cant be run, like
on Odoo.sh.</p>
<p>Unlike the regular job runner, where jobs are dispatched to the HttpWorkers, jobs are
processed on the CronWorker threads by the job runner crons. This is a design decision
because:</p>
<ul class="simple">
<li>Odoo.sh puts HttpWorkers to sleep when theres no network activity</li>
<li>HttpWorkers are meant for traffic. Users shouldnt pay the price of background tasks.</li>
</ul>
<p>For now, it only implements the most basic features of the <tt class="docutils literal">queue_job</tt> runner, notably
no channel capacity nor priorities. Please check the ROADMAP for further details.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
<a class="reference external" href="https://odoo-community.org/page/development-status">More details on development status</a></p>
</div>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<div class="admonition warning">
<p class="first admonition-title">Warning</p>
<p class="last">Dont use this module if youre already running the regular <tt class="docutils literal">queue_job</tt> runner.</p>
</div>
<p>For the easiest case, no configuration is required besides installing the module.</p>
<p>To avoid CronWorker CPU timeout from abruptly stopping the job processing cron, its
recommended to launch Odoo with <tt class="docutils literal"><span class="pre">--limit-time-real-cron=0</span></tt>, to disable the CronWorker
timeout altogether.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">In Odoo.sh, this is done by default.</p>
</div>
<p>Parallel execution of jobs can be achieved by leveraging multiple <tt class="docutils literal">ir.cron</tt> records:</p>
<ul class="simple">
<li>Make sure you have enough CronWorkers available (Odoo CLI <tt class="docutils literal"><span class="pre">--max-cron-threads</span></tt>)</li>
<li>Duplicate the <tt class="docutils literal">queue_job_cron</tt> cron record as many times as needed, until you have
as much records as cron workers.</li>
</ul>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Support channel capacity and priority. (See <tt class="docutils literal">_acquire_one_job</tt>)</li>
<li>Gracefully handle CronWorker CPU timeouts. (See <tt class="docutils literal">_job_runner</tt>)</li>
<li>Commit transaction after job state updated to started. (See <tt class="docutils literal">_process</tt>)</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/queue/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/queue/issues/new?body=module:%20queue_job_cron_jobrunner%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
<ul class="simple">
<li>Camptocamp SA</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
<ul>
<li><p class="first"><a class="reference external" href="https://www.camptocamp.com">Camptocamp</a></p>
<blockquote>
<ul class="simple">
<li>Iván Todorovich &lt;<a class="reference external" href="mailto:ivan.todorovich&#64;camptocamp.com">ivan.todorovich&#64;camptocamp.com</a>&gt;</li>
</ul>
</blockquote>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>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.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/ivantodorovich"><img alt="ivantodorovich" src="https://github.com/ivantodorovich.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/queue/tree/16.0/queue_job_cron_jobrunner">OCA/queue</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1 @@
from . import test_queue_job

View file

@ -0,0 +1,99 @@
# Copyright 2022 Camptocamp SA (https://www.camptocamp.com).
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from datetime import timedelta
from freezegun import freeze_time
from odoo import fields
from odoo.tests.common import TransactionCase
from odoo.tools import mute_logger
class TestQueueJob(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.cron = cls.env.ref("queue_job_cron_jobrunner.queue_job_cron")
# Cleanup triggers just in case
cls.env["ir.cron.trigger"].search([]).unlink()
def assertTriggerAt(self, at, message=None):
"""Ensures a cron trigger is created at the given time"""
return self.assertTrue(
self.env["ir.cron.trigger"].search([("call_at", "=", at)]),
message,
)
@freeze_time("2022-02-22 22:22:22")
def test_queue_job_cron_trigger(self):
"""Test that ir.cron triggers are created for every queue.job"""
job = self.env["res.partner"].with_delay().create({"name": "test"})
job_record = job.db_record()
self.assertTriggerAt(fields.Datetime.now(), "Trigger should've been created")
job_record.eta = fields.Datetime.now() + timedelta(hours=1)
self.assertTriggerAt(job_record.eta, "A new trigger should've been created")
@mute_logger("odoo.addons.queue_job_cron_jobrunner.models.queue_job")
def test_queue_job_process(self):
"""Test that jobs are processed by the queue job cron"""
# Create some jobs
job1 = self.env["res.partner"].with_delay().create({"name": "test"})
job1_record = job1.db_record()
job2 = self.env["res.partner"].with_delay().create(False)
job2_record = job2.db_record()
job3 = self.env["res.partner"].with_delay(eta=3600).create({"name": "Test"})
job3_record = job3.db_record()
# Run the job processing cron
self.env["queue.job"]._job_runner(commit=False)
# Check that the jobs were processed
self.assertEqual(job1_record.state, "done", "Processed OK")
self.assertEqual(job2_record.state, "failed", "Has errors")
self.assertEqual(job3_record.state, "pending", "Still pending, because of eta")
@freeze_time("2022-02-22 22:22:22")
def test_queue_job_cron_trigger_enqueue_dependencies(self):
"""Test that ir.cron execution enqueue waiting dependencies"""
delayable = self.env["res.partner"].delayable().create({"name": "test"})
delayable2 = self.env["res.partner"].delayable().create({"name": "test2"})
delayable.on_done(delayable2)
delayable.delay()
job_record = delayable._generated_job.db_record()
job_record_depends = delayable2._generated_job.db_record()
self.env["queue.job"]._job_runner(commit=False)
self.assertEqual(job_record.state, "done", "Processed OK")
# if the state is "waiting_dependencies", it means the "enqueue_waiting()"
# step has not been done when the parent job has been done
self.assertEqual(job_record_depends.state, "done", "Processed OK")
def test_acquire_one_job_use_priority(self):
with freeze_time("2024-01-01 10:01:01"):
self.env["res.partner"].with_delay(priority=3).create({"name": "test"})
with freeze_time("2024-01-01 10:02:01"):
job = (
self.env["res.partner"].with_delay(priority=1).create({"name": "test"})
)
with freeze_time("2024-01-01 10:03:01"):
self.env["res.partner"].with_delay(priority=2).create({"name": "test"})
self.assertEqual(self.env["queue.job"]._acquire_one_job(), job.db_record())
def test_acquire_one_job_consume_the_oldest_first(self):
with freeze_time("2024-01-01 10:01:01"):
job = (
self.env["res.partner"].with_delay(priority=30).create({"name": "test"})
)
with freeze_time("2024-01-01 10:02:01"):
self.env["res.partner"].with_delay(priority=30).create({"name": "test"})
with freeze_time("2024-01-01 10:03:01"):
self.env["res.partner"].with_delay(priority=30).create({"name": "test"})
self.assertEqual(self.env["queue.job"]._acquire_one_job(), job.db_record())

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2022 Camptocamp SA (https://www.camptocamp.com).
@author Iván Todorovich <ivan.todorovich@camptocamp.com>
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="ir_cron_view_form" model="ir.ui.view">
<field name="model">ir.cron</field>
<field name="inherit_id" ref="base.ir_cron_view_form" />
<field name="arch" type="xml">
<field name="doall" position="after">
<field
name="queue_job_runner"
attrs="{'invisible': [('model_name', '!=', 'queue.job')]}"
/>
</field>
</field>
</record>
</odoo>