From e477bd2957abe29a4d25cbd4d5480855a35a37cf Mon Sep 17 00:00:00 2001 From: SilvioC2C Date: Tue, 25 Jun 2024 15:52:03 +0200 Subject: [PATCH] [MIG] connector_jira: Migration to 17.0 --- connector_jira/__manifest__.py | 15 ++++--- connector_jira/components/binder.py | 6 +-- .../migrations/15.0.1.0.0/pre-migrate.py | 24 ----------- .../migrations/15.0.2.0.0/pre-migrate.py | 32 -------------- .../models/account_analytic_line/common.py | 20 ++++----- .../models/account_analytic_line/importer.py | 22 ++++++---- connector_jira/models/jira_backend/common.py | 15 ++----- connector_jira/models/jira_binding/common.py | 2 +- .../models/jira_issue_type/common.py | 4 +- .../models/project_project/common.py | 42 ++++++++----------- .../project_project/project_link_jira.py | 1 + connector_jira/models/project_task/common.py | 33 ++++++--------- .../models/project_task/importer.py | 18 ++++---- .../models/project_task/task_link_jira.py | 1 + connector_jira/views/jira_backend_views.xml | 36 ++++++++++------ .../views/jira_issue_type_views.xml | 9 ++-- .../views/project_link_jira_views.xml | 24 ++++------- .../views/project_project_views.xml | 16 +++---- connector_jira/views/project_task_views.xml | 27 ++++-------- connector_jira/views/task_link_jira_views.xml | 6 +-- .../views/timesheet_account_analytic_line.xml | 12 +++--- .../jira_account_analytic_line_import.py | 1 + requirements.txt | 2 +- 23 files changed, 143 insertions(+), 225 deletions(-) delete mode 100644 connector_jira/migrations/15.0.1.0.0/pre-migrate.py delete mode 100644 connector_jira/migrations/15.0.2.0.0/pre-migrate.py diff --git a/connector_jira/__manifest__.py b/connector_jira/__manifest__.py index d5f279104..321194fc2 100644 --- a/connector_jira/__manifest__.py +++ b/connector_jira/__manifest__.py @@ -2,18 +2,23 @@ { "name": "JIRA Connector", - "version": "15.0.2.0.0", + "version": "17.0.1.0.0", "author": "Camptocamp,Odoo Community Association (OCA)", "license": "AGPL-3", "category": "Connector", "depends": [ - "connector", + # Odoo community "project", "hr_timesheet", - "queue_job", "web", - "web_widget_url_advanced", + # OCA/connector + "connector", + # OCA/queue + "queue_job", + # OCA/server-ux "multi_step_wizard", + # OCA/web + "web_widget_url_advanced", ], "external_dependencies": { "python": [ @@ -24,7 +29,7 @@ "requests-toolbelt>=0.9.1", "requests-jwt>=0.6.0", "PyJWT>=1.7.1,<2.9.0", - "cryptography<37", + "cryptography>=38,<39", # Compatibility w/ Odoo 17.0 requirements "atlassian_jwt>=3.0.0", ], }, diff --git a/connector_jira/components/binder.py b/connector_jira/components/binder.py index 85e13518f..c5cd522f0 100644 --- a/connector_jira/components/binder.py +++ b/connector_jira/components/binder.py @@ -44,11 +44,7 @@ class JiraModelBinder(Component): _name = "jira.model.binder" _inherit = ["base.binder", "jira.base"] - - _apply_on = [ - "jira.issue.type", - ] - + _apply_on = ["jira.issue.type"] _odoo_field = "id" def to_internal(self, external_id, unwrap=False): diff --git a/connector_jira/migrations/15.0.1.0.0/pre-migrate.py b/connector_jira/migrations/15.0.1.0.0/pre-migrate.py deleted file mode 100644 index cd1d6b5a3..000000000 --- a/connector_jira/migrations/15.0.1.0.0/pre-migrate.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2022 Camptocamp SA -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from openupgradelib import openupgrade - - -def migrate(cr, version): - add_missing_xmlid_on_channel(cr) - - -def add_missing_xmlid_on_channel(cr): - query = """ - SELECT id FROM queue_job_channel - WHERE complete_name='root.connector_jira.import'; - """ - cr.execute(query) - channel = cr.fetchall() - if channel: - openupgrade.add_xmlid( - cr, - "connector_jira", - "import_root", - "queue.job.channel", - channel[0][0], - ) diff --git a/connector_jira/migrations/15.0.2.0.0/pre-migrate.py b/connector_jira/migrations/15.0.2.0.0/pre-migrate.py deleted file mode 100644 index b64af6379..000000000 --- a/connector_jira/migrations/15.0.2.0.0/pre-migrate.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2024 Camptocamp SA -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) - - -def migrate(cr, version): - remove_field_selection(cr) - - -def remove_field_selection(cr): - queries = [ - # delete xml ids of ir.model.fields.selections - "DELETE FROM ir_model_data imd " - "USING ir_model_fields_selection fs, ir_model_fields f, ir_model m " - "WHERE imd.module='connector_jira' " - "AND imd.model='ir.model.fields.selection' " - "AND res_id=fs.id " - "AND f.model_id = m.id " - "AND m.name='jira.backend.auth' " - "AND fs.field_id=f.id;", - # delete ir_model_fields_selection - "DELETE FROM ir_model_fields_selection " - "USING ir_model_fields f, ir_model m " - "WHERE f.model_id = m.id " - "AND m.name='jira.backend.auth' " - "AND field_id=f.id;", - # delete ir.model - "DELETE from ir_model WHERE model='jira.backend.auth';", - "DROP TABLE jira_backend_auth", - ] - - for query in queries: - cr.execute(query) diff --git a/connector_jira/models/account_analytic_line/common.py b/connector_jira/models/account_analytic_line/common.py index b9819c5a8..e4d0b669c 100644 --- a/connector_jira/models/account_analytic_line/common.py +++ b/connector_jira/models/account_analytic_line/common.py @@ -58,23 +58,16 @@ class JiraAccountAnalyticLine(models.Model): # for instance, we do not import "Tasks" but we import "Epics", # the analytic line for a "Task" will be linked to an "Epic" on # Odoo, but we still want to know the original task here - jira_issue_key = fields.Char( - string="Original Task Key", - readonly=True, - ) + jira_issue_key = fields.Char(string="Original Task Key") jira_issue_type_id = fields.Many2one( comodel_name="jira.issue.type", string="Original Issue Type", - readonly=True, ) jira_issue_url = fields.Char( string="Original JIRA issue Link", compute="_compute_jira_issue_url", ) - jira_epic_issue_key = fields.Char( - string="Original Epic Key", - readonly=True, - ) + jira_epic_issue_key = fields.Char(string="Original Epic Key") jira_epic_issue_url = fields.Char( string="Original JIRA Epic Link", compute="_compute_jira_issue_url", @@ -241,10 +234,11 @@ def _connector_jira_unlink_validate(self): _("Timesheet linked to JIRA Worklog can not be deleted!") ) - @api.model - def create(self, vals): - self._connector_jira_create_validate(vals) - return super().create(vals) + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + self._connector_jira_create_validate(vals) + return super().create(vals_list) def write(self, vals): self._connector_jira_write_validate(vals) diff --git a/connector_jira/models/account_analytic_line/importer.py b/connector_jira/models/account_analytic_line/importer.py index a994351e3..910fbe84c 100644 --- a/connector_jira/models/account_analytic_line/importer.py +++ b/connector_jira/models/account_analytic_line/importer.py @@ -288,19 +288,25 @@ def _recurse_import_task(self): def _create_data(self, map_record, **kwargs): return super()._create_data( map_record, - task_binding=self.task_binding, - project_binding=self.project_binding, - fallback_project=self.fallback_project, - linked_issue=self.external_issue, + **dict( + kwargs or [], + task_binding=self.task_binding, + project_binding=self.project_binding, + fallback_project=self.fallback_project, + linked_issue=self.external_issue, + ), ) def _update_data(self, map_record, **kwargs): return super()._update_data( map_record, - task_binding=self.task_binding, - project_binding=self.project_binding, - fallback_project=self.fallback_project, - linked_issue=self.external_issue, + **dict( + kwargs or [], + task_binding=self.task_binding, + project_binding=self.project_binding, + fallback_project=self.fallback_project, + linked_issue=self.external_issue, + ), ) def run(self, external_id, force=False, record=None, **kwargs): diff --git a/connector_jira/models/jira_backend/common.py b/connector_jira/models/jira_backend/common.py index 6bf2dd3d2..58324002e 100644 --- a/connector_jira/models/jira_backend/common.py +++ b/connector_jira/models/jira_backend/common.py @@ -67,7 +67,6 @@ class JiraBackend(models.Model): uri = fields.Char( string="Jira URI", - readonly=True, help="the value is provided when the app is installed on Jira Cloud.", ) name = fields.Char( @@ -79,9 +78,7 @@ class JiraBackend(models.Model): help="URL to use when registering the backend as an app on the marketplace", compute="_compute_app_descriptor_url", ) - display_url = fields.Char( - help="Url used for the Jira app in messages", readonly=True - ) + display_url = fields.Char(help="Url used for the Jira app in messages") application_key = fields.Char( compute="_compute_application_key", store=True, @@ -130,7 +127,6 @@ class JiraBackend(models.Model): ], default="setup", required=True, - readonly=True, help="State of the Backend.\n" "Setup: in this state you can register the backend on " "https://marketplace.atlassian.com/ as an app, using the app descriptor url.\n" @@ -138,12 +134,11 @@ class JiraBackend(models.Model): "(transition is automatic).", ) private_key = fields.Char( - readonly=True, groups="connector.group_connector_manager", help="The shared secret for JWT, provided at app installation", ) public_key = fields.Text( - readonly=True, help="The Client Key for JWT, provided at app installation" + help="The Client Key for JWT, provided at app installation" ) verify_ssl = fields.Boolean(default=True, string="Verify SSL?") @@ -182,7 +177,6 @@ class JiraBackend(models.Model): comodel_name="jira.issue.type", inverse_name="backend_id", string="Issue Types", - readonly=True, ) epic_link_field_name = fields.Char( @@ -201,11 +195,10 @@ class JiraBackend(models.Model): ) # TODO: use something better to show this info - # For instance, we could use web_notify to simply show a system msg. - report_user_sync = fields.Html(readonly=True) + # For instance, we could use web_notify to simply show a system msg. + report_user_sync = fields.Html() @api.model_create_multi - @api.returns("self", lambda value: value.id) def create(self, vals_list): records = super().create(vals_list) records._compute_application_key() diff --git a/connector_jira/models/jira_binding/common.py b/connector_jira/models/jira_binding/common.py index 31cc238b4..daac47235 100644 --- a/connector_jira/models/jira_binding/common.py +++ b/connector_jira/models/jira_binding/common.py @@ -26,7 +26,7 @@ class JiraBinding(models.AbstractModel): ondelete="restrict", ) jira_updated_at = MilliDatetime() - external_id = fields.Char(string="ID on Jira", index=True) + external_id = fields.Char(string="ID on Jira", index="trigram") _sql_constraints = [ ( diff --git a/connector_jira/models/jira_issue_type/common.py b/connector_jira/models/jira_issue_type/common.py index 6690e9f5d..50dc2fb02 100644 --- a/connector_jira/models/jira_issue_type/common.py +++ b/connector_jira/models/jira_issue_type/common.py @@ -11,8 +11,8 @@ class JiraIssueType(models.Model): _inherit = "jira.binding" _description = "Jira Issue Type" - name = fields.Char(required=True, readonly=True) - description = fields.Char(readonly=True) + name = fields.Char(required=True) + description = fields.Char() backend_id = fields.Many2one(ondelete="cascade") def is_sync_for_project(self, project_binding): diff --git a/connector_jira/models/project_project/common.py b/connector_jira/models/project_project/common.py index f79c5a906..da317b1a5 100644 --- a/connector_jira/models/project_project/common.py +++ b/connector_jira/models/project_project/common.py @@ -34,8 +34,8 @@ class JiraProjectBaseFields(models.AbstractModel): jira_key = fields.Char( string="JIRA Key", required=True, - size=10, - ) # limit on JIRA + size=10, # limit on JIRA + ) sync_issue_type_ids = fields.Many2many( comodel_name="jira.issue.type", string="Issue Levels to Synchronize", @@ -99,13 +99,10 @@ def _selection_project_type(self): def _add_sql_constraints(self): # we replace the sql constraint by a python one # to include the organizations - for key, definition, _msg in self._sql_constraints: + for key, definition, __ in self._sql_constraints: conname = f"{self._table}_{key}" if key == "jira_binding_uniq": - has_definition = tools.constraint_definition( - self.env.cr, self._table, conname - ) - if has_definition: + if tools.constraint_definition(self.env.cr, self._table, conname): tools.drop_constraint(self.env.cr, self._table, conname) else: tools.add_constraint(self.env.cr, self._table, conname, definition) @@ -201,11 +198,11 @@ def _is_linked(self): return True return False - @api.model - def create(self, values): - record = super().create(values) - record._ensure_jira_key() - return record + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + records._ensure_jira_key() + return records def write(self, values): if "project_template" in values: @@ -251,14 +248,12 @@ def _compute_jira_key(self): keys = project.mapped("jira_bind_ids.jira_key") project.jira_key = ", ".join(keys) - def name_get(self): - names = [] - for project in self: - project_id, name = super(ProjectProject, project).name_get()[0] - if project.jira_key: - name = f"[{project.jira_key}] {name}" - names.append((project_id, name)) - return names + # pylint: disable=W8110 + @api.depends("jira_key") + def _compute_display_name(self): + super()._compute_display_name() + for project in self.filtered("jira_key"): + project.display_name = f"[{project.jira_key}] {project.display_name}" @api.model def name_search(self, name="", args=None, operator="ilike", limit=100): @@ -272,14 +267,13 @@ def name_search(self, name="", args=None, operator="ilike", limit=100): ] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = ["&", "!"] + domain[1:] - return self.search( - domain + (args or []), - limit=limit, - ).name_get() + projects = self.search(domain + (args or []), limit=limit) + return [(p.id, p.display_name) for p in projects.sudo()] def create_and_link_jira(self): action_link = self.env.ref("connector_jira.open_project_link_jira") action = action_link.read()[0] + # TODO: remove dependency on ``active_id[s]/model`` action["context"] = dict( self.env.context, active_id=self.id, diff --git a/connector_jira/models/project_project/project_link_jira.py b/connector_jira/models/project_project/project_link_jira.py index b8686f907..c7da2ffb7 100644 --- a/connector_jira/models/project_project/project_link_jira.py +++ b/connector_jira/models/project_project/project_link_jira.py @@ -37,6 +37,7 @@ class ProjectLinkJira(models.TransientModel): @api.model def _default_project_id(self): + # TODO: remove dependency on ``active_id[s]/model`` return self.env.context.get("active_id") @api.model diff --git a/connector_jira/models/project_task/common.py b/connector_jira/models/project_task/common.py index 83b8ce11a..d6fc79b5b 100644 --- a/connector_jira/models/project_task/common.py +++ b/connector_jira/models/project_task/common.py @@ -29,22 +29,18 @@ class JiraProjectTask(models.Model): ) jira_key = fields.Char( string="Key", - readonly=True, ) jira_issue_type_id = fields.Many2one( comodel_name="jira.issue.type", string="Issue Type", - readonly=True, ) jira_epic_link_id = fields.Many2one( comodel_name="jira.project.task", string="Epic", - readonly=True, ) jira_parent_id = fields.Many2one( comodel_name="jira.project.task", string="Parent Issue", - readonly=True, help="Parent issue when the issue is a subtask. " "Empty if the type of parent is filtered out " "of the synchronizations.", @@ -153,14 +149,12 @@ def _compute_jira_issue_url(self): main_binding = record.jira_bind_ids[0] record.jira_issue_url = main_binding.jira_issue_url - def name_get(self): - names = [] - for task in self: - task_id, name = super(ProjectTask, task).name_get()[0] - if task.jira_compound_key: - name = f"[{task.jira_compound_key}] {name}" - names.append((task_id, name)) - return names + # pylint: disable=W8110 + @api.depends("jira_compound_key") + def _compute_display_name(self): + super()._compute_display_name() + for task in self.filtered("jira_compound_key"): + task.display_name = f"[{task.jira_compound_key}] {task.display_name}" @api.model def name_search(self, name="", args=None, operator="ilike", limit=100): @@ -174,10 +168,8 @@ def name_search(self, name="", args=None, operator="ilike", limit=100): ] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = ["&", "!"] + domain[1:] - return self.search( - domain + (args or []), - limit=limit, - ).name_get() + tasks = self.search(domain + (args or []), limit=limit) + return [(t.id, t.display_name) for t in tasks.sudo()] @api.model def _get_connector_jira_fields(self): @@ -239,10 +231,11 @@ def _connector_jira_unlink_validate(self): _("Task linked to JIRA Issue can not be deleted!") ) - @api.model - def create(self, vals): - self._connector_jira_create_validate(vals) - return super().create(vals) + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + self._connector_jira_create_validate(vals) + return super().create(vals_list) def write(self, vals): self._connector_jira_write_validate(vals) diff --git a/connector_jira/models/project_task/importer.py b/connector_jira/models/project_task/importer.py index 0896aae64..50653ee0d 100644 --- a/connector_jira/models/project_task/importer.py +++ b/connector_jira/models/project_task/importer.py @@ -181,7 +181,7 @@ def __init__(self, work_context): self.project_binding = None def _get_external_data(self): - """Return the raw Jira data for ``self.external_id``""" + # OVERRIDE: return the raw Jira data for ``self.external_id`` result = super()._get_external_data() epic_field_name = self.backend_record.epic_link_field_name if epic_field_name: @@ -209,17 +209,21 @@ def _is_issue_type_sync(self): def _create_data(self, map_record, **kwargs): return super()._create_data( map_record, - jira_epic=self.jira_epic, - project_binding=self.project_binding, - **kwargs, + **dict( + kwargs or [], + jira_epic=self.jira_epic, + project_binding=self.project_binding, + ), ) def _update_data(self, map_record, **kwargs): return super()._update_data( map_record, - jira_epic=self.jira_epic, - project_binding=self.project_binding, - **kwargs, + **dict( + kwargs or [], + jira_epic=self.jira_epic, + project_binding=self.project_binding, + ), ) def _import(self, binding, **kwargs): diff --git a/connector_jira/models/project_task/task_link_jira.py b/connector_jira/models/project_task/task_link_jira.py index dde32f028..903ccfb11 100644 --- a/connector_jira/models/project_task/task_link_jira.py +++ b/connector_jira/models/project_task/task_link_jira.py @@ -49,6 +49,7 @@ def _selection_state(self): def default_get(self, fields): values = super().default_get(fields) context = self.env.context + # TODO: remove dependency on ``active_id[s]/model`` if context.get("active_model") == "project.task" and context.get("active_id"): task = self.env["project.task"].browse(context["active_id"]) project_linked_backends = task.mapped("project_id.jira_bind_ids.backend_id") diff --git a/connector_jira/views/jira_backend_views.xml b/connector_jira/views/jira_backend_views.xml index 8d48f8cb6..1ee805da2 100644 --- a/connector_jira/views/jira_backend_views.xml +++ b/connector_jira/views/jira_backend_views.xml @@ -16,9 +16,9 @@ name="check_connection" type="object" string="Check Connection" - states="setup,running" + invisible="state not in ('setup', 'running')" /> - +