diff --git a/NEWS.rst b/NEWS.rst index f323fa8e06696c8bbf554a8a09f517334b86059f_TkVXUy5yc3Q=..21c89ac3dc80de3443c256e85de54000d91edc89_TkVXUy5yc3Q= 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,6 +1,20 @@ Changelog ========= +17.0.1.1.0 +---------- + +Features +~~~~~~~~ + +- In job log view, only display link to job definition to group system/configuration. + +Fixes +~~~~~ + +- fix readonly condition on job_definition_id in job log form view. +- fix duplicating jobs. + 17.0.1.0.1 ---------- diff --git a/__manifest__.py b/__manifest__.py index f323fa8e06696c8bbf554a8a09f517334b86059f_X19tYW5pZmVzdF9fLnB5..21c89ac3dc80de3443c256e85de54000d91edc89_X19tYW5pZmVzdF9fLnB5 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -20,7 +20,7 @@ ############################################################################## { "name": "External Jobs", - "version": "17.0.1.0.1", + "version": "17.0.1.1.0", "license": "AGPL-3", "author": "XCG Consulting", "category": "Tools", diff --git a/doc/autotodo.py b/doc/autotodo.py index f323fa8e06696c8bbf554a8a09f517334b86059f_ZG9jL2F1dG90b2RvLnB5..21c89ac3dc80de3443c256e85de54000d91edc89_ZG9jL2F1dG90b2RvLnB5 100755 --- a/doc/autotodo.py +++ b/doc/autotodo.py @@ -22,7 +22,7 @@ import os import os.path import sys -from collections.abc import Mapping +from collections.abc import MutableMapping def main(): @@ -31,13 +31,13 @@ sys.exit(1) folder = sys.argv[1] - exts = sys.argv[2].split(",") - tags = sys.argv[3].split(",") - todolist = {tag: [] for tag in tags} - path_file_length: Mapping[str, int] = {} + exts: list[str] = sys.argv[2].split(",") + tags: list[str] = sys.argv[3].split(",") + todolist: dict[str, list[tuple[str, int, str]]] = {tag: [] for tag in tags} + path_file_length: MutableMapping[str, int] = {} for root, _dirs, files in os.walk(folder): scan_folder((exts, tags, todolist, path_file_length), root, files) create_autotodo(folder, todolist, path_file_length) @@ -38,10 +38,10 @@ for root, _dirs, files in os.walk(folder): scan_folder((exts, tags, todolist, path_file_length), root, files) create_autotodo(folder, todolist, path_file_length) -def write_info(f, infos, folder, path_file_length: Mapping[str, int]): +def write_info(f, infos, folder, path_file_length: MutableMapping[str, int]): # Check sphinx version for lineno-start support import sphinx @@ -55,7 +55,7 @@ path = i[0] line = i[1] lines = (line - 3, min(line + 4, path_file_length[path])) - class_name = ":class:`%s`" % os.path.basename(os.path.splitext(path)[0]) + class_name = f":class:`{os.path.basename(os.path.splitext(path)[0])}`" f.write( "{}\n" "{}\n\n" @@ -74,7 +74,7 @@ ) ) if lineno_start: - f.write("\t\t:lineno-start: %s\n" % lines[0]) + f.write(f"\t\t:lineno-start: {lines[0]}\n") f.write("\n") @@ -78,10 +78,10 @@ f.write("\n") -def create_autotodo(folder, todolist, path_file_length: Mapping[str, int]): +def create_autotodo(folder, todolist, path_file_length: MutableMapping[str, int]): with open("autotodo", "w+") as f: for tag, info in list(todolist.items()): f.write("{}\n{}\n\n".format(tag, "=" * len(tag))) write_info(f, info, folder, path_file_length) @@ -82,10 +82,19 @@ with open("autotodo", "w+") as f: for tag, info in list(todolist.items()): f.write("{}\n{}\n\n".format(tag, "=" * len(tag))) write_info(f, info, folder, path_file_length) -def scan_folder(data_tuple, dirname, names): +def scan_folder( + data_tuple: tuple[ + list[str], + list[str], + dict[str, list[tuple[str, int, str]]], + MutableMapping[str, int], + ], + dirname: str, + names: list[str], +): (exts, tags, res, path_file_length) = data_tuple for name in names: (root, ext) = os.path.splitext(name) @@ -98,7 +107,9 @@ res[tag].extend(info) -def scan_file(filename, tags) -> tuple[dict[str, list[tuple[str, int, str]]], int]: +def scan_file( + filename: str, tags: list[str] +) -> tuple[dict[str, list[tuple[str, int, str]]], int]: res: dict[str, list[tuple[str, int, str]]] = {tag: [] for tag in tags} line_num: int = 0 with open(filename) as f: diff --git a/doc/conf.py b/doc/conf.py index f323fa8e06696c8bbf554a8a09f517334b86059f_ZG9jL2NvbmYucHk=..21c89ac3dc80de3443c256e85de54000d91edc89_ZG9jL2NvbmYucHk= 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -14,7 +14,7 @@ from importlib.metadata import PackageNotFoundError from importlib.metadata import version as import_version -import odoo +import odoo # type: ignore[import-untyped] from odoo_scripts.config import Configuration # If extensions (or modules to document with autodoc) are in another directory, @@ -106,7 +106,7 @@ html_static_path = ["_static"] # Output file base name for HTML help builder. -htmlhelp_basename = "%sdoc" % module_nospace +htmlhelp_basename = f"{module_nospace}doc" # -- Options for LaTeX output --------------------------------------------- @@ -110,11 +110,9 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements = {} - # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, @@ -115,11 +113,11 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, - "%s.tex" % module_nospace, - "%s Documentation" % project, + f"{module_nospace}.tex", + f"{project} Documentation", author, "manual", ) @@ -129,7 +127,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, module_lowercase, "%s Documentation" % project, [author], 1)] +man_pages = [(master_doc, module_lowercase, f"{project} Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -140,7 +138,7 @@ ( master_doc, module_nospace, - "%s Documentation" % project, + f"{project} Documentation", author, module_nospace, module_description, diff --git a/doc/index.rst b/doc/index.rst index f323fa8e06696c8bbf554a8a09f517334b86059f_ZG9jL2luZGV4LnJzdA==..21c89ac3dc80de3443c256e85de54000d91edc89_ZG9jL2luZGV4LnJzdA== 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,4 +1,4 @@ .. |coverage| image:: .badges/coverage.svg - :target: https://orus.io/xcg/odoo-modules/external_job/-/pipelines?ref=branch/15.0 + :target: https://orus.io/xcg/odoo-modules/external_job/-/pipelines?ref=branch/17.0 :alt: Coverage report .. |pylint| image:: .badges/pylint.svg @@ -3,6 +3,6 @@ :alt: Coverage report .. |pylint| image:: .badges/pylint.svg - :target: https://orus.io/xcg/odoo-modules/external_job/-/pipelines?ref=branch/15.0 + :target: https://orus.io/xcg/odoo-modules/external_job/-/pipelines?ref=branch/17.0 :alt: pylint score |coverage| |pylint| diff --git a/doc/modules.rst b/doc/modules.rst index f323fa8e06696c8bbf554a8a09f517334b86059f_ZG9jL21vZHVsZXMucnN0..21c89ac3dc80de3443c256e85de54000d91edc89_ZG9jL21vZHVsZXMucnN0 100644 --- a/doc/modules.rst +++ b/doc/modules.rst @@ -1,5 +1,5 @@ -external_job -============ +odoo.addons.external_job +======================== .. toctree:: :maxdepth: 4 diff --git a/models/default_value.py b/models/default_value.py index f323fa8e06696c8bbf554a8a09f517334b86059f_bW9kZWxzL2RlZmF1bHRfdmFsdWUucHk=..21c89ac3dc80de3443c256e85de54000d91edc89_bW9kZWxzL2RlZmF1bHRfdmFsdWUucHk= 100644 --- a/models/default_value.py +++ b/models/default_value.py @@ -1,7 +1,7 @@ ############################################################################### # # External Jobs, for Odoo -# Copyright © 2019, 2022 XCG Consulting (http://www.xcg-consulting.fr/) +# Copyright © 2019, 2022, 2024 XCG Consulting (http://www.xcg-consulting.fr/) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -18,7 +18,7 @@ # ############################################################################### -from odoo import fields, models +from odoo import fields, models # type: ignore[untyped-import] class DefaultValue(models.Model): diff --git a/models/env_var.py b/models/env_var.py index f323fa8e06696c8bbf554a8a09f517334b86059f_bW9kZWxzL2Vudl92YXIucHk=..21c89ac3dc80de3443c256e85de54000d91edc89_bW9kZWxzL2Vudl92YXIucHk= 100644 --- a/models/env_var.py +++ b/models/env_var.py @@ -1,7 +1,7 @@ ############################################################################## # # External Jobs, for Odoo -# Copyright © 2013, 2022 XCG Consulting (http://odoo.consulting) +# Copyright © 2013, 2022, 2024 XCG Consulting (http://odoo.consulting) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -18,7 +18,7 @@ # ############################################################################## -from odoo import fields, models +from odoo import fields, models # type: ignore[untyped-import] class EnvVar(models.Model): diff --git a/models/extrunner_server.py b/models/extrunner_server.py index f323fa8e06696c8bbf554a8a09f517334b86059f_bW9kZWxzL2V4dHJ1bm5lcl9zZXJ2ZXIucHk=..21c89ac3dc80de3443c256e85de54000d91edc89_bW9kZWxzL2V4dHJ1bm5lcl9zZXJ2ZXIucHk= 100644 --- a/models/extrunner_server.py +++ b/models/extrunner_server.py @@ -1,7 +1,7 @@ ############################################################################## # # External Jobs, for Odoo -# Copyright © 2019, 2023 XCG Consulting (https://xcg-consulting.fr/) +# Copyright © 2019, 2023, 2024 XCG Consulting (https://xcg-consulting.fr/) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -17,7 +17,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################## -from odoo import fields, models +from odoo import fields, models # type: ignore[untyped-import] class ExtrunnerServer(models.Model): diff --git a/models/job_definition.py b/models/job_definition.py index f323fa8e06696c8bbf554a8a09f517334b86059f_bW9kZWxzL2pvYl9kZWZpbml0aW9uLnB5..21c89ac3dc80de3443c256e85de54000d91edc89_bW9kZWxzL2pvYl9kZWZpbml0aW9uLnB5 100644 --- a/models/job_definition.py +++ b/models/job_definition.py @@ -25,5 +25,6 @@ import sys import tempfile from base64 import b64encode +from typing import Any import requests @@ -28,7 +29,7 @@ import requests -from odoo import _, api, exceptions, fields, models -from odoo.tools import config +from odoo import _, api, exceptions, fields, models # type: ignore[untyped-import] +from odoo.tools import config # type: ignore[untyped-import] from .job_log import State @@ -213,7 +214,7 @@ def run_job( self, job_args: dict | None = None, in_file_name: str | None = None - ) -> models.Model: + ) -> models.BaseModel: """Run jobs :param job_args: arguments for the job, added as a section of the dict @@ -223,6 +224,5 @@ :return: external_job.job_log record list, to get a view change, use return_joblog_act_window, or use action_run_job directly """ - joblogs = self.env["external_job.job_log"] # User do not have the rights to see most of the fields, or in the case of # extrunner, the models used, so use sudo to bypass the restrictions @@ -227,10 +227,10 @@ # User do not have the rights to see most of the fields, or in the case of # extrunner, the models used, so use sudo to bypass the restrictions - for job in self.sudo(): - # Pylint does not understand that this is ourselves - # pylint: disable=protected-access - if job.job_type == "extrunner": - joblogs += job._call_extrunner() - if job.job_type == "cmd": - joblogs += job._call_cmd(job_args, in_file_name) + joblogs = ( + self.sudo() + .env["external_job.job_log"] + .create(self.sudo()._vals_list(in_file_name)) + ) + self.sudo()._run(joblogs, job_args) + return joblogs.sudo(False) @@ -236,4 +236,28 @@ - return joblogs + def _vals_list(self, in_file_name: str | None = None) -> list[dict[str, Any]]: + vals_list = [] + extrunner_jobs = self.filtered(lambda job: job.job_type == "extrunner") + # Pylint does not understand that this is ourselves + # pylint: disable=protected-access + if extrunner_jobs: + vals_list.extend(extrunner_jobs._vals_list_extrunner()) + cmd_jobs = self.filtered(lambda job: job.job_type == "cmd") + if cmd_jobs: + vals_list.extend(cmd_jobs._vals_list_cmd(in_file_name)) + return vals_list + + def _run(self, joblogs: models.BaseModel, job_args: dict | None = None) -> None: + extrunner_job_logs = joblogs.filtered( + lambda joblog: joblog.job_definition_id.job_type == "extrunner" + ) + # Pylint does not understand that this is ourselves + # pylint: disable=protected-access + if extrunner_job_logs: + self._call_extrunner(joblogs) + cmd_job_logs = joblogs.filtered( + lambda joblog: joblog.job_definition_id.job_type == "cmd" + ) + if cmd_job_logs: + self._call_cmd(joblogs, job_args) @staticmethod @@ -238,8 +262,8 @@ @staticmethod - def return_joblog_act_window(joblogs: models.Model): + def return_joblog_act_window(joblogs: models.BaseModel): """Return a view change from a list of job log ids""" if len(joblogs) == 1: return JobDefinition._return_joblog_act_window(joblogs, default_view="form") return JobDefinition._return_joblog_act_window(joblogs, view_mode="tree") @@ -241,12 +265,8 @@ """Return a view change from a list of job log ids""" if len(joblogs) == 1: return JobDefinition._return_joblog_act_window(joblogs, default_view="form") return JobDefinition._return_joblog_act_window(joblogs, view_mode="tree") - def _call_cmd( - self, job_args: dict | None = None, in_file_name: str | None = None - ) -> models.Model: - cr, ctx = self.env.cr, self.env.context - - job_logs = self.env["external_job.job_log"] + def _vals_list_cmd(self, in_file_name: str | None = None) -> list[dict[str, Any]]: + vals_list = [] for job in self: @@ -252,5 +272,5 @@ for job in self: - command = job.command_line + # create job_log_vals = {"job_definition_id": job.id} if job.output_filename_base: @@ -270,8 +290,18 @@ if in_file_name_: job_log_vals["in_file_name"] = in_file_name_ - joblog = job_logs.create(job_log_vals) - job_logs += joblog + vals_list.append(job_log_vals) + return vals_list + + @api.model + def _call_cmd( + self, job_logs: models.BaseModel, job_args: dict | None = None + ) -> None: + cr, ctx = self.env.cr, self.env.context + + for joblog in job_logs: + job = joblog.job_definition_id + command = job.command_line arg_dict = ArgumentDict( job, @@ -285,9 +315,9 @@ if job.input_content: # TODO handle exceptions # in_file_name_ is set before to a value - with open(in_file_name_, "w+") as file: # types: arg-type + with open(joblog.in_file_name, "w+") as file: # types: arg-type data = arg_dict[job.input_content] if isinstance(data, str): file.write(data) else: for value in arg_dict[job.input_content]: @@ -289,9 +319,9 @@ data = arg_dict[job.input_content] if isinstance(data, str): file.write(data) else: for value in arg_dict[job.input_content]: - file.write("%s\n" % value) + file.write(f"{value}\n") # Make sure a commit of our transaction before giving the newly # created id to our external process, or it won't be able to find it @@ -306,7 +336,7 @@ run = sh.Command(command) except sh.CommandNotFound: JobDefinition._handle_error(joblog, _("Command %s not found", command)) - return job_logs + return if job.arguments_base: out = run((job.arguments_base % arg_dict).split()) @@ -314,8 +344,8 @@ out = run() # cleanup temporary files - if in_file_name: - os.remove(in_file_name) + if joblog.in_file_name: + os.remove(joblog.in_file_name) joblog_dict = { "end_date": fields.Datetime.now(), @@ -323,7 +353,7 @@ "state": State.DONE.value, } if job.output_filename_base: - with open(out_file_name, "rb") as outfile: + with open(joblog.out_file_name, "rb") as outfile: outfile_data = outfile.read() if not outfile_data: @@ -337,7 +367,7 @@ } ) - os.remove(out_file_name) + os.remove(joblog.out_file_name) joblog.write(joblog_dict) @@ -341,7 +371,5 @@ joblog.write(joblog_dict) - return job_logs - @staticmethod def _return_joblog_act_window( @@ -346,6 +374,6 @@ @staticmethod def _return_joblog_act_window( - job_logs: models.Model, view_mode="form", default_view="tree" + job_logs: models.BaseModel, view_mode="form", default_view="tree" ): """return a dictionary that represent external job log action Note: default_view : if default view is form, specifies the @@ -365,7 +393,6 @@ act["domain"] = [("id", "in", job_logs.ids)] return act - def _call_extrunner(self) -> models.Model: - """Call extrunner to launch an external job.""" - job_logs = self.env["external_job.job_log"] + def _vals_list_extrunner(self) -> list[dict[str, Any]]: + return [{"job_definition_id": rec.id} for rec in self] @@ -371,8 +398,9 @@ - for rec in self: - job_log_vals = {"job_definition_id": rec.id} - joblog = job_logs.create(job_log_vals) - job_logs += joblog + @api.model + def _call_extrunner(self, job_logs: models.BaseModel) -> None: + """Call extrunner to launch an external job.""" + for joblog in job_logs: + rec = joblog.job_definition_id # Dynamic arguments: Similar to what we do in the "run_job" method; # not worth factoring out. @@ -389,7 +417,7 @@ headers = {"Content-Type": "application/json"} server = rec.extrunner_server_id post = requests.post( - "%s/auth/login" % server.base_url, + f"{server.base_url}/auth/login", json={ "login": server.user_login, "password": server.user_password, @@ -407,5 +435,5 @@ text=post.text, ), ) - return job_logs + return @@ -411,7 +439,5 @@ - headers["Authorization"] = "Bearer %s" % post.json().get( - "id_token", "failed" - ) + headers["Authorization"] = f"Bearer {post.json().get('id_token', 'failed')}" # Make sure a commit of our transaction before giving the newly # created id to our external process, or it won't be able to find it @@ -479,6 +505,4 @@ joblog.write(values) - return job_logs - @staticmethod @@ -484,5 +508,5 @@ @staticmethod - def _handle_error(joblog, msg: str): + def _handle_error(joblog: models.BaseModel, msg: str): joblog.write( { "end_date": fields.Datetime.now(), diff --git a/models/job_log.py b/models/job_log.py index f323fa8e06696c8bbf554a8a09f517334b86059f_bW9kZWxzL2pvYl9sb2cucHk=..21c89ac3dc80de3443c256e85de54000d91edc89_bW9kZWxzL2pvYl9sb2cucHk= 100644 --- a/models/job_log.py +++ b/models/job_log.py @@ -21,7 +21,7 @@ import logging from enum import Enum -from odoo import _, api, fields, models +from odoo import _, api, fields, models # type: ignore[untyped-import] _logger = logging.getLogger(__name__) @@ -56,7 +56,7 @@ ) start_date = fields.Datetime( - string="Start Date", readonly=True, default=fields.Datetime.now + string="Start Date", readonly=True, default=fields.Datetime.now, copy=False ) # TODO rename to end_datetime? @@ -60,5 +60,5 @@ ) # TODO rename to end_datetime? - end_date = fields.Datetime(string="End Date", readonly=True) + end_date = fields.Datetime(string="End Date", readonly=True, copy=False) @@ -64,7 +64,5 @@ - in_file_name = fields.Char(string="Temporary In File Name", size=512, readonly=True) - - out_file_name = fields.Char( - string="Temporary Out File Name", size=512, readonly=True + in_file_name = fields.Char( + string="Temporary In File Name", size=512, readonly=True, copy=False ) @@ -69,4 +67,6 @@ ) - out_file = fields.Binary(string="Output File", readonly=True) + out_file_name = fields.Char( + string="Temporary Out File Name", size=512, readonly=True, copy=False + ) @@ -72,3 +72,3 @@ - filename = fields.Char(string="Filename") + out_file = fields.Binary(string="Output File", readonly=True, copy=False) @@ -74,3 +74,3 @@ - stdout = fields.Text(string="StdOut", readonly=True) + filename = fields.Char(string="Filename", copy=False) @@ -76,5 +76,7 @@ - stderr = fields.Text(string="StdErr", readonly=True) + stdout = fields.Text(string="StdOut", readonly=True, copy=False) + + stderr = fields.Text(string="StdErr", readonly=True, copy=False) state = fields.Selection( selection=[(state.value, state.translate()) for state in State], @@ -105,3 +107,13 @@ job_dtime = datetime.datetime.strftime(job_dtime, lang_format) jlog.display_name = _("%s’s Job", job_dtime) + + @api.returns("self", lambda value: value.id) + def copy(self, default=None): + vals = {} + if default is not None: + vals.update(default) + vals.update(self.job_definition_id._vals_list()[0]) + created = super().copy(vals) + created.job_definition_id._run(created) + return created diff --git a/tests/test_job_definition.py b/tests/test_job_definition.py index f323fa8e06696c8bbf554a8a09f517334b86059f_dGVzdHMvdGVzdF9qb2JfZGVmaW5pdGlvbi5weQ==..21c89ac3dc80de3443c256e85de54000d91edc89_dGVzdHMvdGVzdF9qb2JfZGVmaW5pdGlvbi5weQ== 100644 --- a/tests/test_job_definition.py +++ b/tests/test_job_definition.py @@ -1,7 +1,7 @@ ############################################################################### # # External Job, for Odoo -# Copyright © 2018, 2022, 2023 XCG Consulting (https://www.xcg-consulting.fr/) +# Copyright © 2018, 2022, 2023, 2024 XCG Consulting (https://www.xcg-consulting.fr/) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -23,8 +23,8 @@ import requests # Odoo req from freezegun import freeze_time -from odoo.exceptions import AccessError -from odoo.tests import TransactionCase +from odoo.exceptions import AccessError # type: ignore[untyped-import] +from odoo.tests import TransactionCase # type: ignore[untyped-import] class Test(TransactionCase): @@ -63,6 +63,13 @@ self.assertEqual(job_def.job_count, 1) self.assertEqual(job_log.state, "done") + # copy and do the same checks + with freeze_time("2023-02-01 10:31:00"): + copy = job_log.copy() + self.assertEqual("%d\n" % copy[0].id, copy.stdout) + self.assertEqual(job_def.job_count, 2) + self.assertEqual(copy.state, "done") + # gc tests job_def.unlink_delay = 1 self.env["external_job.job_definition"]._gc_job_log() self.assertEqual( @@ -174,6 +181,22 @@ self.assertEqual(job.out_file, base64.b64encode(b"ATTACHMENT-CONTENTS")) self.assertTrue(job.filename) + # copy tests + copy = job.copy() + requests_post_mock.assert_any_call( + "http://BASE-URL-1/auth/login", + json=mock.ANY, + headers=mock.ANY, + timeout=mock.ANY, + ) + requests_post_mock.assert_any_call( + "http://BASE-URL-1/run/0", json=mock.ANY, headers=mock.ANY, timeout=mock.ANY + ) + self.assertTrue(copy.end_date) + self.assertEqual(copy.state, "done") + self.assertEqual(copy.out_file, base64.b64encode(b"ATTACHMENT-CONTENTS")) + self.assertTrue(copy.filename) + def test_run_job_create_empty_file(self): """Test for the creation of an empty file""" job_def = self.browse_ref("external_job.demo_job_definition_touch") diff --git a/tests/test_job_log.py b/tests/test_job_log.py index f323fa8e06696c8bbf554a8a09f517334b86059f_dGVzdHMvdGVzdF9qb2JfbG9nLnB5..21c89ac3dc80de3443c256e85de54000d91edc89_dGVzdHMvdGVzdF9qb2JfbG9nLnB5 100644 --- a/tests/test_job_log.py +++ b/tests/test_job_log.py @@ -1,7 +1,7 @@ ############################################################################### # # External Jobs, for Odoo -# Copyright © 2022, 2023 XCG Consulting (https://www.xcg-consulting.fr/) +# Copyright © 2022, 2023, 2024 XCG Consulting (https://www.xcg-consulting.fr/) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -17,8 +17,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################### -from odoo.exceptions import AccessError -from odoo.tests import TransactionCase +from odoo.exceptions import AccessError # type: ignore[untyped-import] +from odoo.tests import TransactionCase # type: ignore[untyped-import] class Test(TransactionCase): diff --git a/tests/test_jobrunner.py b/tests/test_jobrunner.py index f323fa8e06696c8bbf554a8a09f517334b86059f_dGVzdHMvdGVzdF9qb2JydW5uZXIucHk=..21c89ac3dc80de3443c256e85de54000d91edc89_dGVzdHMvdGVzdF9qb2JydW5uZXIucHk= 100644 --- a/tests/test_jobrunner.py +++ b/tests/test_jobrunner.py @@ -1,7 +1,7 @@ ############################################################################### # # External Jobs, for Odoo -# Copyright © 2022 XCG Consulting (https://www.xcg-consulting.fr/) +# Copyright © 2022, 2024 XCG Consulting (https://www.xcg-consulting.fr/) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -17,7 +17,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################### -from odoo.tests import TransactionCase +from odoo.tests import TransactionCase # type: ignore[untyped-import] class Test(TransactionCase): diff --git a/views/job_definition.xml b/views/job_definition.xml index f323fa8e06696c8bbf554a8a09f517334b86059f_dmlld3Mvam9iX2RlZmluaXRpb24ueG1s..21c89ac3dc80de3443c256e85de54000d91edc89_dmlld3Mvam9iX2RlZmluaXRpb24ueG1s 100644 --- a/views/job_definition.xml +++ b/views/job_definition.xml @@ -69,71 +69,63 @@ <field name="raise_error" /> <field name="output_filename_base" /> </group> - <group - col="4" - colspan="4" - name="help" - string="Legend for arguments and environment variable’s value" - > - <dl> - <dt> - <code>%%(job_definition.<field>)s</code> - </dt> - <dd>job definition field.</dd> - <dt> - <code>%%(job_log.id)s</code> - </dt> - <dd>job ID.</dd> - <dt invisible="job_type != 'cmd'"> - <code>%%(job_log.in_file_name)s</code> - </dt> - <dd invisible="job_type != 'cmd'"> - job in temporary file. - </dd> - <dt invisible="job_type != 'cmd'"> - <code>%%(job_log.out_file_name)s</code> - </dt> - <dd invisible="job_type != 'cmd'"> - job out temporary file name (where the program is - supposed to write). - </dd> - <dt> - <code>%%(config.options.???)s</code> - </dt> - <dd> - Arguments to extract from the current configuration, - placed after the static arguments. - </dd> - <dd> - Please note that the db_name configuration key does NOT - correspond to the active database, which is determined - from the cursor and always passed to the job as first - argument. - </dd> - <dt> - <code>%%(cr.dbname)s</code> - </dt> - <dd>database in use.</dd> - <dt> - <code>%%(context.<value>)s</code> - </dt> - <dd>context values (from run)</dd> - <dt invisible="job_type != 'cmd'"> - <code>%%(run.<value>)s</code> - </dt> - <dd invisible="job_type != 'cmd'"> - values from run args argument - </dd> - <dt> - <code>%%(datetime.now)s</code> - </dt> - <dd>iso formated now date</dd> - <dt> - <code>%%(datetime.today)s</code> - </dt> - <dd>iso formated today date</dd> - </dl> - </group> + <h3>Legend for arguments and environment variable’s value</h3> + <dl> + <dt> + <code>%%(job_definition.<field>)s</code> + </dt> + <dd>job definition field.</dd> + <dt> + <code>%%(job_log.id)s</code> + </dt> + <dd>job ID.</dd> + <dt invisible="job_type != 'cmd'"> + <code>%%(job_log.in_file_name)s</code> + </dt> + <dd invisible="job_type != 'cmd'">job in temporary file.</dd> + <dt invisible="job_type != 'cmd'"> + <code>%%(job_log.out_file_name)s</code> + </dt> + <dd invisible="job_type != 'cmd'"> + job out temporary file name (where the program is + supposed to write). + </dd> + <dt> + <code>%%(config.options.???)s</code> + </dt> + <dd> + Arguments to extract from the current configuration, + placed after the static arguments. + </dd> + <dd> + Please note that the db_name configuration key does NOT + correspond to the active database, which is determined + from the cursor and always passed to the job as first + argument. + </dd> + <dt> + <code>%%(cr.dbname)s</code> + </dt> + <dd>database in use.</dd> + <dt> + <code>%%(context.<value>)s</code> + </dt> + <dd>context values (from run)</dd> + <dt invisible="job_type != 'cmd'"> + <code>%%(run.<value>)s</code> + </dt> + <dd invisible="job_type != 'cmd'"> + values from run args argument + </dd> + <dt> + <code>%%(datetime.now)s</code> + </dt> + <dd>iso formated now date</dd> + <dt> + <code>%%(datetime.today)s</code> + </dt> + <dd>iso formated today date</dd> + </dl> </sheet> </form> </field> diff --git a/views/job_log.xml b/views/job_log.xml index f323fa8e06696c8bbf554a8a09f517334b86059f_dmlld3Mvam9iX2xvZy54bWw=..21c89ac3dc80de3443c256e85de54000d91edc89_dmlld3Mvam9iX2xvZy54bWw= 100644 --- a/views/job_log.xml +++ b/views/job_log.xml @@ -71,7 +71,14 @@ <group> <field name="job_definition_id" - readonly="[('id', '!=', False)]" + readonly="id" + widget="selection" + groups="!base.group_system" + /> + <field + name="job_definition_id" + readonly="id" + groups="base.group_system" /> </group>