odoo-addon-odoo-repository 16.0.1.3.0.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/odoo_repository/README.rst +81 -0
- odoo/addons/odoo_repository/__init__.py +2 -0
- odoo/addons/odoo_repository/__manifest__.py +58 -0
- odoo/addons/odoo_repository/controllers/__init__.py +1 -0
- odoo/addons/odoo_repository/controllers/main.py +32 -0
- odoo/addons/odoo_repository/data/ir_cron.xml +38 -0
- odoo/addons/odoo_repository/data/odoo.repository.csv +216 -0
- odoo/addons/odoo_repository/data/odoo_branch.xml +82 -0
- odoo/addons/odoo_repository/data/odoo_module.xml +16 -0
- odoo/addons/odoo_repository/data/odoo_repository.xml +71 -0
- odoo/addons/odoo_repository/data/odoo_repository_addons_path.xml +59 -0
- odoo/addons/odoo_repository/data/odoo_repository_org.xml +14 -0
- odoo/addons/odoo_repository/data/queue_job.xml +56 -0
- odoo/addons/odoo_repository/lib/__init__.py +0 -0
- odoo/addons/odoo_repository/lib/scanner.py +1302 -0
- odoo/addons/odoo_repository/migrations/16.0.1.1.0/post-migration.py +26 -0
- odoo/addons/odoo_repository/migrations/16.0.1.2.0/pre-migration.py +43 -0
- odoo/addons/odoo_repository/migrations/16.0.1.3.0/post-migration.py +19 -0
- odoo/addons/odoo_repository/models/__init__.py +18 -0
- odoo/addons/odoo_repository/models/authentication_token.py +12 -0
- odoo/addons/odoo_repository/models/odoo_author.py +16 -0
- odoo/addons/odoo_repository/models/odoo_branch.py +111 -0
- odoo/addons/odoo_repository/models/odoo_license.py +16 -0
- odoo/addons/odoo_repository/models/odoo_maintainer.py +31 -0
- odoo/addons/odoo_repository/models/odoo_module.py +24 -0
- odoo/addons/odoo_repository/models/odoo_module_branch.py +873 -0
- odoo/addons/odoo_repository/models/odoo_module_branch_version.py +123 -0
- odoo/addons/odoo_repository/models/odoo_module_category.py +15 -0
- odoo/addons/odoo_repository/models/odoo_module_dev_status.py +15 -0
- odoo/addons/odoo_repository/models/odoo_python_dependency.py +16 -0
- odoo/addons/odoo_repository/models/odoo_repository.py +664 -0
- odoo/addons/odoo_repository/models/odoo_repository_addons_path.py +40 -0
- odoo/addons/odoo_repository/models/odoo_repository_branch.py +98 -0
- odoo/addons/odoo_repository/models/odoo_repository_org.py +23 -0
- odoo/addons/odoo_repository/models/res_company.py +23 -0
- odoo/addons/odoo_repository/models/res_config_settings.py +23 -0
- odoo/addons/odoo_repository/models/ssh_key.py +12 -0
- odoo/addons/odoo_repository/readme/CONTRIBUTORS.rst +2 -0
- odoo/addons/odoo_repository/readme/DESCRIPTION.rst +1 -0
- odoo/addons/odoo_repository/security/ir.model.access.csv +27 -0
- odoo/addons/odoo_repository/security/res_groups.xml +25 -0
- odoo/addons/odoo_repository/static/description/README +4 -0
- odoo/addons/odoo_repository/static/description/icon.png +0 -0
- odoo/addons/odoo_repository/static/description/index.html +430 -0
- odoo/addons/odoo_repository/tests/__init__.py +6 -0
- odoo/addons/odoo_repository/tests/common.py +162 -0
- odoo/addons/odoo_repository/tests/test_base_scanner.py +214 -0
- odoo/addons/odoo_repository/tests/test_odoo_module_branch.py +97 -0
- odoo/addons/odoo_repository/tests/test_odoo_repository_scan.py +242 -0
- odoo/addons/odoo_repository/tests/test_repository_scanner.py +215 -0
- odoo/addons/odoo_repository/tests/test_sync_node.py +55 -0
- odoo/addons/odoo_repository/tests/test_utils.py +25 -0
- odoo/addons/odoo_repository/utils/__init__.py +0 -0
- odoo/addons/odoo_repository/utils/github.py +30 -0
- odoo/addons/odoo_repository/utils/module.py +25 -0
- odoo/addons/odoo_repository/utils/scanner.py +90 -0
- odoo/addons/odoo_repository/views/authentication_token.xml +63 -0
- odoo/addons/odoo_repository/views/menu.xml +38 -0
- odoo/addons/odoo_repository/views/odoo_author.xml +54 -0
- odoo/addons/odoo_repository/views/odoo_branch.xml +84 -0
- odoo/addons/odoo_repository/views/odoo_license.xml +40 -0
- odoo/addons/odoo_repository/views/odoo_maintainer.xml +69 -0
- odoo/addons/odoo_repository/views/odoo_module.xml +90 -0
- odoo/addons/odoo_repository/views/odoo_module_branch.xml +353 -0
- odoo/addons/odoo_repository/views/odoo_module_category.xml +40 -0
- odoo/addons/odoo_repository/views/odoo_module_dev_status.xml +40 -0
- odoo/addons/odoo_repository/views/odoo_python_dependency.xml +40 -0
- odoo/addons/odoo_repository/views/odoo_repository.xml +165 -0
- odoo/addons/odoo_repository/views/odoo_repository_addons_path.xml +49 -0
- odoo/addons/odoo_repository/views/odoo_repository_branch.xml +60 -0
- odoo/addons/odoo_repository/views/odoo_repository_org.xml +54 -0
- odoo/addons/odoo_repository/views/res_config_settings.xml +123 -0
- odoo/addons/odoo_repository/views/ssh_key.xml +63 -0
- odoo_addon_odoo_repository-16.0.1.3.0.13.dist-info/METADATA +100 -0
- odoo_addon_odoo_repository-16.0.1.3.0.13.dist-info/RECORD +77 -0
- odoo_addon_odoo_repository-16.0.1.3.0.13.dist-info/WHEEL +5 -0
- odoo_addon_odoo_repository-16.0.1.3.0.13.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
# Copyright 2023 Camptocamp SA
|
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
3
|
+
|
|
4
|
+
import pathlib
|
|
5
|
+
import random
|
|
6
|
+
import time
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
from odoo import _, api, fields, models, tools
|
|
10
|
+
from odoo.exceptions import ValidationError
|
|
11
|
+
from odoo.osv import expression
|
|
12
|
+
|
|
13
|
+
from odoo.addons.queue_job.exception import RetryableJobError
|
|
14
|
+
|
|
15
|
+
from ..utils import github
|
|
16
|
+
from ..utils.module import adapt_version
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OdooModuleBranch(models.Model):
|
|
20
|
+
_name = "odoo.module.branch"
|
|
21
|
+
_description = "Odoo Module Branch"
|
|
22
|
+
_order = "repository_sequence, module_name, branch_name"
|
|
23
|
+
|
|
24
|
+
module_id = fields.Many2one(
|
|
25
|
+
comodel_name="odoo.module",
|
|
26
|
+
ondelete="restrict",
|
|
27
|
+
string="Technical name",
|
|
28
|
+
required=True,
|
|
29
|
+
index=True,
|
|
30
|
+
)
|
|
31
|
+
module_name = fields.Char(
|
|
32
|
+
string="Module Technical Name", related="module_id.name", store=True, index=True
|
|
33
|
+
)
|
|
34
|
+
repository_branch_id = fields.Many2one(
|
|
35
|
+
comodel_name="odoo.repository.branch",
|
|
36
|
+
ondelete="set null",
|
|
37
|
+
string="Repository Branch",
|
|
38
|
+
index=True,
|
|
39
|
+
)
|
|
40
|
+
repository_id = fields.Many2one(
|
|
41
|
+
related="repository_branch_id.repository_id",
|
|
42
|
+
store=True,
|
|
43
|
+
precompute=True,
|
|
44
|
+
string="Repository",
|
|
45
|
+
)
|
|
46
|
+
repository_sequence = fields.Integer(
|
|
47
|
+
related="repository_id.sequence",
|
|
48
|
+
store=True,
|
|
49
|
+
index=True,
|
|
50
|
+
)
|
|
51
|
+
org_id = fields.Many2one(
|
|
52
|
+
related="repository_branch_id.repository_id.org_id",
|
|
53
|
+
store=True,
|
|
54
|
+
string="Organization",
|
|
55
|
+
)
|
|
56
|
+
branch_id = fields.Many2one(
|
|
57
|
+
# NOTE: not a related on 'repository_branch_id' as we need to create
|
|
58
|
+
# modules without knowing in advance what is their repo (orphaned modules).
|
|
59
|
+
comodel_name="odoo.branch",
|
|
60
|
+
ondelete="cascade",
|
|
61
|
+
string="Odoo Version",
|
|
62
|
+
required=True,
|
|
63
|
+
index=True,
|
|
64
|
+
)
|
|
65
|
+
branch_name = fields.Char(
|
|
66
|
+
string="Branch Name", related="branch_id.name", store=True, index=True
|
|
67
|
+
)
|
|
68
|
+
branch_sequence = fields.Integer(
|
|
69
|
+
string="Branch Sequence", related="branch_id.sequence", store=True, index=True
|
|
70
|
+
)
|
|
71
|
+
pr_url = fields.Char(string="PR URL")
|
|
72
|
+
is_standard = fields.Boolean(
|
|
73
|
+
string="Standard?",
|
|
74
|
+
help="Is this module part of Odoo standard?",
|
|
75
|
+
default=False,
|
|
76
|
+
)
|
|
77
|
+
is_enterprise = fields.Boolean(
|
|
78
|
+
string="Enterprise?",
|
|
79
|
+
help="Is this module designed for Odoo Enterprise only?",
|
|
80
|
+
default=False,
|
|
81
|
+
)
|
|
82
|
+
is_community = fields.Boolean(
|
|
83
|
+
string="Community?",
|
|
84
|
+
help="Is this module a contribution of the community?",
|
|
85
|
+
default=False,
|
|
86
|
+
)
|
|
87
|
+
title = fields.Char(index=True, help="Descriptive name")
|
|
88
|
+
name = fields.Char(
|
|
89
|
+
string="Techname",
|
|
90
|
+
compute="_compute_name",
|
|
91
|
+
store=True,
|
|
92
|
+
index=True,
|
|
93
|
+
)
|
|
94
|
+
summary = fields.Char(index=True)
|
|
95
|
+
category_id = fields.Many2one(
|
|
96
|
+
comodel_name="odoo.module.category",
|
|
97
|
+
ondelete="restrict",
|
|
98
|
+
string="Category",
|
|
99
|
+
index=True,
|
|
100
|
+
)
|
|
101
|
+
author_ids = fields.Many2many(
|
|
102
|
+
comodel_name="odoo.author",
|
|
103
|
+
string="Authors",
|
|
104
|
+
)
|
|
105
|
+
maintainer_ids = fields.Many2many(
|
|
106
|
+
comodel_name="odoo.maintainer",
|
|
107
|
+
relation="module_branch_maintainer_rel",
|
|
108
|
+
column1="module_branch_id",
|
|
109
|
+
column2="maintainer_id",
|
|
110
|
+
string="Maintainers",
|
|
111
|
+
)
|
|
112
|
+
dependency_ids = fields.Many2many(
|
|
113
|
+
comodel_name="odoo.module.branch",
|
|
114
|
+
relation="module_branch_dependency_rel",
|
|
115
|
+
column1="module_branch_id",
|
|
116
|
+
column2="dependency_id",
|
|
117
|
+
string="Dependencies",
|
|
118
|
+
)
|
|
119
|
+
reverse_dependency_ids = fields.Many2many(
|
|
120
|
+
comodel_name="odoo.module.branch",
|
|
121
|
+
relation="module_branch_dependency_rel",
|
|
122
|
+
column1="dependency_id",
|
|
123
|
+
column2="module_branch_id",
|
|
124
|
+
string="Reverse Dependencies",
|
|
125
|
+
)
|
|
126
|
+
global_dependency_level = fields.Integer(
|
|
127
|
+
compute="_compute_dependency_level",
|
|
128
|
+
recursive=True,
|
|
129
|
+
store=True,
|
|
130
|
+
string="Global Dep. Level",
|
|
131
|
+
help="Dependency level including all standard Odoo modules.",
|
|
132
|
+
)
|
|
133
|
+
non_std_dependency_level = fields.Integer(
|
|
134
|
+
compute="_compute_dependency_level",
|
|
135
|
+
recursive=True,
|
|
136
|
+
store=True,
|
|
137
|
+
string="Non-Std Dep. Level",
|
|
138
|
+
help="Dependency level excluding all standard Odoo modules.",
|
|
139
|
+
)
|
|
140
|
+
license_id = fields.Many2one(
|
|
141
|
+
comodel_name="odoo.license",
|
|
142
|
+
ondelete="restrict",
|
|
143
|
+
string="License",
|
|
144
|
+
index=True,
|
|
145
|
+
)
|
|
146
|
+
version = fields.Char("Last version")
|
|
147
|
+
version_ids = fields.One2many(
|
|
148
|
+
comodel_name="odoo.module.branch.version",
|
|
149
|
+
inverse_name="module_branch_id",
|
|
150
|
+
string="Versions",
|
|
151
|
+
)
|
|
152
|
+
development_status_id = fields.Many2one(
|
|
153
|
+
comodel_name="odoo.module.dev.status",
|
|
154
|
+
ondelete="restrict",
|
|
155
|
+
string="Develoment Status",
|
|
156
|
+
index=True,
|
|
157
|
+
)
|
|
158
|
+
external_dependencies = fields.Serialized()
|
|
159
|
+
python_dependency_ids = fields.Many2many(
|
|
160
|
+
comodel_name="odoo.python.dependency",
|
|
161
|
+
string="Python Dependencies",
|
|
162
|
+
)
|
|
163
|
+
application = fields.Boolean(default=False)
|
|
164
|
+
installable = fields.Boolean(default=True)
|
|
165
|
+
auto_install = fields.Boolean(
|
|
166
|
+
string="Auto-Install",
|
|
167
|
+
default=False,
|
|
168
|
+
)
|
|
169
|
+
sloc_python = fields.Integer("Python", help="Python source lines of code")
|
|
170
|
+
sloc_xml = fields.Integer("XML", help="XML source lines of code")
|
|
171
|
+
sloc_js = fields.Integer("JS", help="JavaScript source lines of code")
|
|
172
|
+
sloc_css = fields.Integer("CSS", help="CSS source lines of code")
|
|
173
|
+
last_scanned_commit = fields.Char()
|
|
174
|
+
removed = fields.Boolean()
|
|
175
|
+
addons_path = fields.Char(
|
|
176
|
+
help="Technical field. Where the module is located in the repository."
|
|
177
|
+
)
|
|
178
|
+
full_path = fields.Char(compute="_compute_full_path")
|
|
179
|
+
url = fields.Char("URL", compute="_compute_url")
|
|
180
|
+
specific = fields.Boolean(
|
|
181
|
+
help=(
|
|
182
|
+
"Module specific to a project repository."
|
|
183
|
+
"It cannot be used across different projects."
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
_sql_constraints = [
|
|
188
|
+
(
|
|
189
|
+
"module_id_branch_id_repository_id_uniq",
|
|
190
|
+
"UNIQUE (module_id, branch_id, repository_id)",
|
|
191
|
+
"This module already exists for this repository/branch.",
|
|
192
|
+
),
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
def init(self):
|
|
196
|
+
# Index to complete unique constraint 'module_id_branch_id_repository_id_uniq'.
|
|
197
|
+
# This is mandatory to support repository_id=NULL in the constraint,
|
|
198
|
+
# so we cannot create the same orphaned module twice.
|
|
199
|
+
indexes = [
|
|
200
|
+
# PostgreSQL < 15 (partial indexes)
|
|
201
|
+
"""
|
|
202
|
+
CREATE UNIQUE INDEX IF NOT EXISTS odoo_module_branch_uniq_not_null
|
|
203
|
+
ON odoo_module_branch (module_id, branch_id, repository_id)
|
|
204
|
+
WHERE repository_id IS NOT NULL;
|
|
205
|
+
""",
|
|
206
|
+
"""
|
|
207
|
+
CREATE UNIQUE INDEX IF NOT EXISTS odoo_module_branch_uniq_null
|
|
208
|
+
ON odoo_module_branch (module_id, branch_id)
|
|
209
|
+
WHERE repository_id IS NULL;
|
|
210
|
+
"""
|
|
211
|
+
# PostgreSQL >= 15 (with NULLS NOT DISTINCT)
|
|
212
|
+
# """
|
|
213
|
+
# CREATE UNIQUE INDEX odoo_module_branch_uniq
|
|
214
|
+
# ON odoo_module_branch (module_id, branch_id, repository_id)
|
|
215
|
+
# NULLS NOT DISTINCT;
|
|
216
|
+
# """
|
|
217
|
+
]
|
|
218
|
+
for index in indexes:
|
|
219
|
+
self._cr.execute(index)
|
|
220
|
+
|
|
221
|
+
@api.constrains("specific", "dependency_ids")
|
|
222
|
+
def _check_generic_depends_on_specific(self):
|
|
223
|
+
for rec in self:
|
|
224
|
+
if not rec.specific:
|
|
225
|
+
specific_deps = rec.dependency_ids.filtered("specific")
|
|
226
|
+
if specific_deps:
|
|
227
|
+
msg = _(
|
|
228
|
+
"Generic module %(generic_mod)s cannot depend "
|
|
229
|
+
"on specific module(s): %(specific_mods)s"
|
|
230
|
+
)
|
|
231
|
+
raise ValidationError(
|
|
232
|
+
msg
|
|
233
|
+
% {
|
|
234
|
+
"generic_mod": rec.module_name,
|
|
235
|
+
"specific_mods": ", ".join(
|
|
236
|
+
specific_deps.mapped("module_name")
|
|
237
|
+
),
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
@api.depends("module_name", "addons_path")
|
|
242
|
+
def _compute_full_path(self):
|
|
243
|
+
for rec in self:
|
|
244
|
+
rec.full_path = pathlib.Path(rec.addons_path or ".").joinpath(
|
|
245
|
+
rec.module_name
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
@api.depends(
|
|
249
|
+
"repository_id.repo_url",
|
|
250
|
+
"branch_name",
|
|
251
|
+
"repository_branch_id.cloned_branch",
|
|
252
|
+
"addons_path",
|
|
253
|
+
"module_name",
|
|
254
|
+
)
|
|
255
|
+
def _compute_url(self):
|
|
256
|
+
for rec in self:
|
|
257
|
+
rec.url = False
|
|
258
|
+
if not rec.repository_id:
|
|
259
|
+
continue
|
|
260
|
+
branch = rec.branch_name
|
|
261
|
+
if rec.repository_branch_id.cloned_branch:
|
|
262
|
+
branch = rec.repository_branch_id.cloned_branch
|
|
263
|
+
module_path = "/".join([rec.addons_path or ".", rec.module_name])
|
|
264
|
+
rec.url = rec.repository_id._get_resource_url(branch, module_path)
|
|
265
|
+
|
|
266
|
+
@api.depends("repository_branch_id.name", "module_id.name")
|
|
267
|
+
def _compute_name(self):
|
|
268
|
+
for rec in self:
|
|
269
|
+
rec.name = f"{rec.repository_branch_id.name or '?'} - {rec.module_id.name}"
|
|
270
|
+
|
|
271
|
+
@api.depends(
|
|
272
|
+
"dependency_ids.global_dependency_level",
|
|
273
|
+
"dependency_ids.non_std_dependency_level",
|
|
274
|
+
"dependency_ids.is_standard",
|
|
275
|
+
)
|
|
276
|
+
def _compute_dependency_level(self):
|
|
277
|
+
for rec in self:
|
|
278
|
+
global_max_parent_level = max(
|
|
279
|
+
[dep.global_dependency_level for dep in rec.dependency_ids] + [0]
|
|
280
|
+
)
|
|
281
|
+
rec.global_dependency_level = global_max_parent_level + 1
|
|
282
|
+
non_std_max_parent_level = max(
|
|
283
|
+
[
|
|
284
|
+
dep.non_std_dependency_level
|
|
285
|
+
for dep in rec.dependency_ids
|
|
286
|
+
if not dep.is_standard
|
|
287
|
+
]
|
|
288
|
+
+ [0]
|
|
289
|
+
)
|
|
290
|
+
rec.non_std_dependency_level = (
|
|
291
|
+
# Set 0 on all std modules so they will always have a dependency
|
|
292
|
+
# level inferior to non-std modules
|
|
293
|
+
(non_std_max_parent_level + 1)
|
|
294
|
+
if not rec.is_standard
|
|
295
|
+
else 0
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
def _get_recursive_dependencies(self, domain=None):
|
|
299
|
+
"""Return all dependencies recursively.
|
|
300
|
+
|
|
301
|
+
A domain can be applied to restrict the modules to return, e.g:
|
|
302
|
+
|
|
303
|
+
>>> mod._get_recursive_dependencies([("org_id", "=", "OCA")])
|
|
304
|
+
|
|
305
|
+
"""
|
|
306
|
+
if not domain:
|
|
307
|
+
domain = []
|
|
308
|
+
dependencies = self.dependency_ids.filtered_domain(domain)
|
|
309
|
+
dep_ids = set(dependencies.ids)
|
|
310
|
+
for dep in dependencies:
|
|
311
|
+
dep_ids |= set(
|
|
312
|
+
dep._get_recursive_dependencies().filtered_domain(domain).ids
|
|
313
|
+
)
|
|
314
|
+
return self.browse(dep_ids)
|
|
315
|
+
|
|
316
|
+
def open_recursive_dependencies(self):
|
|
317
|
+
self.ensure_one()
|
|
318
|
+
xml_id = "odoo_repository.odoo_module_branch_action_recursive_dependencies"
|
|
319
|
+
action = self.env["ir.actions.actions"]._for_xml_id(xml_id)
|
|
320
|
+
action["name"] = "All dependencies"
|
|
321
|
+
action["domain"] = [("id", "in", self._get_recursive_dependencies().ids)]
|
|
322
|
+
return action
|
|
323
|
+
|
|
324
|
+
def action_find_pr_url(self):
|
|
325
|
+
"""Find the PR on GitHub that adds this module."""
|
|
326
|
+
self.ensure_one()
|
|
327
|
+
if self.pr_url or self.repository_branch_id or self.specific:
|
|
328
|
+
return False
|
|
329
|
+
values = {"pr_url": False}
|
|
330
|
+
pr_urls = self._find_pr_urls_from_github(self.branch_id, self.module_id)
|
|
331
|
+
for pr_url in pr_urls:
|
|
332
|
+
values["pr_url"] = pr_url
|
|
333
|
+
# Get the relevant repository from PR URL if not yet defined
|
|
334
|
+
if not self.repository_branch_id:
|
|
335
|
+
repository = self._find_repository_from_pr_url(pr_url)
|
|
336
|
+
if not repository:
|
|
337
|
+
continue
|
|
338
|
+
repository_branch = self.env["odoo.repository.branch"].search(
|
|
339
|
+
[
|
|
340
|
+
("repository_id", "=", repository.id),
|
|
341
|
+
("branch_id", "=", self.branch_id.id),
|
|
342
|
+
]
|
|
343
|
+
)
|
|
344
|
+
if repository_branch:
|
|
345
|
+
values["repository_branch_id"] = repository_branch.id
|
|
346
|
+
break
|
|
347
|
+
self.sudo().write(values)
|
|
348
|
+
return True
|
|
349
|
+
|
|
350
|
+
def _find_pr_urls_from_github(self, branch, module):
|
|
351
|
+
"""Find the GitHub Pull Requests adding `module` on `branch`."""
|
|
352
|
+
# Look for an open PR first, then unmerged (which includes closed ones)
|
|
353
|
+
for pr_state in ("open", "unmerged"):
|
|
354
|
+
url = (
|
|
355
|
+
f"search/issues?q=is:pr+is:{pr_state}+base:{branch.name}"
|
|
356
|
+
f"+in:title+{module.name}"
|
|
357
|
+
)
|
|
358
|
+
try:
|
|
359
|
+
# Mitigate 'API rate limit exceeded' GitHub API error
|
|
360
|
+
# by adding a random waiting time of 1-4s
|
|
361
|
+
time.sleep(random.randrange(1, 5))
|
|
362
|
+
prs = github.request(self.env, url)
|
|
363
|
+
except RuntimeError as exc:
|
|
364
|
+
raise RetryableJobError("Error while looking for PR URL") from exc
|
|
365
|
+
for pr in prs.get("items", []):
|
|
366
|
+
yield pr["html_url"]
|
|
367
|
+
|
|
368
|
+
def _find_repository_from_pr_url(self, pr_url):
|
|
369
|
+
"""Return the repository corresponding to `pr_url`."""
|
|
370
|
+
# Extract organization and repository name from PR url
|
|
371
|
+
path_parts = list(filter(None, urlparse(pr_url).path.split("/")))
|
|
372
|
+
org, repository = path_parts[:2]
|
|
373
|
+
repository_model = self.env["odoo.repository"].with_context(active_test=False)
|
|
374
|
+
return repository_model.search(
|
|
375
|
+
[
|
|
376
|
+
("org_id", "=", org),
|
|
377
|
+
("name", "=", repository),
|
|
378
|
+
]
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
@api.model
|
|
382
|
+
@api.returns("odoo.module.branch")
|
|
383
|
+
def push_scanned_data(self, repo_branch_id, module, data):
|
|
384
|
+
"""Entry point for the scanner to push its data."""
|
|
385
|
+
module = self._get_module(module)
|
|
386
|
+
repo_branch = self.env["odoo.repository.branch"].browse(repo_branch_id)
|
|
387
|
+
values = self._prepare_module_branch_values(repo_branch, module, data)
|
|
388
|
+
return self._create_or_update(repo_branch, module, values)
|
|
389
|
+
|
|
390
|
+
def _prepare_module_branch_values(self, repo_branch, module, data):
|
|
391
|
+
# Get existing module.branch (hosted in scanned repo) if any
|
|
392
|
+
module_branch = self._get_module_branch(
|
|
393
|
+
repo_branch.branch_id, module, repo=repo_branch.repository_id
|
|
394
|
+
)
|
|
395
|
+
# Prepare the 'odoo.module.branch' values
|
|
396
|
+
manifest = data.get("manifest", {})
|
|
397
|
+
values = {
|
|
398
|
+
"repository_branch_id": repo_branch.id,
|
|
399
|
+
"branch_id": repo_branch.branch_id.id,
|
|
400
|
+
"module_id": module.id,
|
|
401
|
+
"is_standard": data["is_standard"],
|
|
402
|
+
"is_enterprise": data["is_enterprise"],
|
|
403
|
+
"is_community": data["is_community"],
|
|
404
|
+
"last_scanned_commit": data.get("last_scanned_commit", False),
|
|
405
|
+
"addons_path": data["relative_path"],
|
|
406
|
+
"specific": repo_branch.repository_id.specific,
|
|
407
|
+
# Unset PR URL once the module is available in the repository.
|
|
408
|
+
"pr_url": False,
|
|
409
|
+
}
|
|
410
|
+
if manifest:
|
|
411
|
+
category_id = self._get_module_category_id(manifest.get("category", ""))
|
|
412
|
+
author_ids = self._get_author_ids(manifest.get("author", ""))
|
|
413
|
+
maintainer_ids = self._get_maintainer_ids(
|
|
414
|
+
tuple(manifest.get("maintainers", []))
|
|
415
|
+
)
|
|
416
|
+
dev_status_id = self._get_dev_status_id(
|
|
417
|
+
manifest.get("development_status", "")
|
|
418
|
+
)
|
|
419
|
+
dependency_ids = []
|
|
420
|
+
external_dependencies = {}
|
|
421
|
+
python_dependency_ids = []
|
|
422
|
+
if manifest.get("installable", True):
|
|
423
|
+
dependency_ids = self._get_dependency_ids(
|
|
424
|
+
repo_branch, manifest.get("depends", [])
|
|
425
|
+
)
|
|
426
|
+
external_dependencies = manifest.get("external_dependencies", {})
|
|
427
|
+
python_dependency_ids = self._get_python_dependency_ids(
|
|
428
|
+
tuple(external_dependencies.get("python", []))
|
|
429
|
+
)
|
|
430
|
+
license_id = self._get_license_id(manifest.get("license", ""))
|
|
431
|
+
values.update(
|
|
432
|
+
{
|
|
433
|
+
"title": manifest.get("name", False),
|
|
434
|
+
"summary": manifest.get(
|
|
435
|
+
"summary", manifest.get("description", False)
|
|
436
|
+
),
|
|
437
|
+
"category_id": category_id,
|
|
438
|
+
"author_ids": [(6, 0, author_ids)],
|
|
439
|
+
"maintainer_ids": [(6, 0, maintainer_ids)],
|
|
440
|
+
"dependency_ids": [(6, 0, dependency_ids)],
|
|
441
|
+
"external_dependencies": external_dependencies,
|
|
442
|
+
"python_dependency_ids": [(6, 0, python_dependency_ids)],
|
|
443
|
+
"license_id": license_id,
|
|
444
|
+
"version": manifest.get("version", False),
|
|
445
|
+
"development_status_id": dev_status_id,
|
|
446
|
+
"application": manifest.get("application", False),
|
|
447
|
+
"installable": manifest.get("installable", True),
|
|
448
|
+
"auto_install": manifest.get("auto_install", False),
|
|
449
|
+
}
|
|
450
|
+
)
|
|
451
|
+
if data.get("last_scanned_commit"):
|
|
452
|
+
values.update(
|
|
453
|
+
{
|
|
454
|
+
"removed": False,
|
|
455
|
+
"sloc_python": data["code"]["Python"],
|
|
456
|
+
"sloc_xml": data["code"]["XML"],
|
|
457
|
+
"sloc_js": data["code"]["JavaScript"],
|
|
458
|
+
"sloc_css": data["code"]["CSS"],
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
# Handle module removal
|
|
462
|
+
elif module_branch:
|
|
463
|
+
values.update(
|
|
464
|
+
{
|
|
465
|
+
"installable": False,
|
|
466
|
+
"removed": True,
|
|
467
|
+
}
|
|
468
|
+
)
|
|
469
|
+
# Handle versions history
|
|
470
|
+
if values.get("installable"):
|
|
471
|
+
versions = self._prepare_module_branch_version_ids_values(
|
|
472
|
+
repo_branch,
|
|
473
|
+
module_branch,
|
|
474
|
+
module,
|
|
475
|
+
# If no history versions was scanned (could happen if versions are
|
|
476
|
+
# part of an unfetched branch), create one corresponding to the
|
|
477
|
+
# current manifest version if any but without commit.
|
|
478
|
+
versions=(
|
|
479
|
+
data.get("versions")
|
|
480
|
+
or (
|
|
481
|
+
{values["version"]: {"commit": None}}
|
|
482
|
+
if values.get("version")
|
|
483
|
+
else {}
|
|
484
|
+
)
|
|
485
|
+
),
|
|
486
|
+
)
|
|
487
|
+
if versions:
|
|
488
|
+
values["version_ids"] = versions
|
|
489
|
+
return values
|
|
490
|
+
|
|
491
|
+
def _create_or_update(self, repo_branch, module, values):
|
|
492
|
+
"""Create or update a `odoo.module.branch` record from scanned module.
|
|
493
|
+
|
|
494
|
+
This method will try to link/update an existing module in DB, that could be:
|
|
495
|
+
- already scanned in the current repository (simple update)
|
|
496
|
+
- orphaned (update the repository of such module)
|
|
497
|
+
- unmerged/pending (only if the scanned repository hosts generic modules)
|
|
498
|
+
"""
|
|
499
|
+
branch = repo_branch.branch_id
|
|
500
|
+
module_branch = False
|
|
501
|
+
module_branch_in_repo = self._get_module_branch(
|
|
502
|
+
branch, module, repo=repo_branch.repository_id
|
|
503
|
+
)
|
|
504
|
+
# Module was already scanned in the current repository: update it
|
|
505
|
+
if module_branch_in_repo:
|
|
506
|
+
module_branch = module_branch_in_repo
|
|
507
|
+
# Module was never scanned in the current repository:
|
|
508
|
+
else:
|
|
509
|
+
# Check if an orphaned module exists
|
|
510
|
+
orphaned_module_branch = self._get_orphaned_module_branch(branch, module)
|
|
511
|
+
if orphaned_module_branch:
|
|
512
|
+
module_branch = orphaned_module_branch
|
|
513
|
+
# Check if an unmerged module exists if the scanned repo is generic
|
|
514
|
+
elif not repo_branch.repository_id.specific:
|
|
515
|
+
unmerged_module_branch = self._get_unmerged_module_branch(
|
|
516
|
+
branch, module
|
|
517
|
+
)
|
|
518
|
+
if unmerged_module_branch:
|
|
519
|
+
module_branch = unmerged_module_branch
|
|
520
|
+
module_branch = self._filter_module_to_update(repo_branch, module_branch)
|
|
521
|
+
if module_branch:
|
|
522
|
+
values["repository_branch_id"] = repo_branch.id
|
|
523
|
+
module_branch.sudo().write(values)
|
|
524
|
+
else:
|
|
525
|
+
module_branch = self.sudo().create(values)
|
|
526
|
+
return module_branch
|
|
527
|
+
|
|
528
|
+
def _filter_module_to_update(self, repo_branch, module_branch):
|
|
529
|
+
"""Hook called by '_create_or_update'.
|
|
530
|
+
|
|
531
|
+
Can be overriden to return `False` to force the creation of a new
|
|
532
|
+
`odoo.module.branch` record linked to the scanned repository.
|
|
533
|
+
"""
|
|
534
|
+
return module_branch
|
|
535
|
+
|
|
536
|
+
@api.model
|
|
537
|
+
def _get_existing_version(self, module, manifest_value, commit):
|
|
538
|
+
if not commit:
|
|
539
|
+
return self.env["odoo.module.branch.version"]
|
|
540
|
+
return self.env["odoo.module.branch.version"].search(
|
|
541
|
+
[
|
|
542
|
+
("module_name", "=", module.name),
|
|
543
|
+
("manifest_value", "=", manifest_value),
|
|
544
|
+
("commit", "=", commit),
|
|
545
|
+
],
|
|
546
|
+
limit=1,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
def _prepare_module_branch_version_ids_values(
|
|
550
|
+
self, repo_branch, module_branch, module, versions
|
|
551
|
+
):
|
|
552
|
+
# Insert new versions
|
|
553
|
+
version_ids = []
|
|
554
|
+
other_odoo_versions = (
|
|
555
|
+
self.env["odoo.branch"]._get_all_odoo_versions() - repo_branch.branch_id
|
|
556
|
+
)
|
|
557
|
+
for manifest_value, data in versions.items():
|
|
558
|
+
# Version scanned doesn't belong to the current branch, skipping
|
|
559
|
+
if any(
|
|
560
|
+
manifest_value.startswith(odoo_version.name + ".")
|
|
561
|
+
for odoo_version in other_odoo_versions
|
|
562
|
+
):
|
|
563
|
+
continue
|
|
564
|
+
name = adapt_version(repo_branch.branch_id.name, manifest_value)
|
|
565
|
+
# As we could import versions history from previous Odoo releases
|
|
566
|
+
# (i.e. the branch has been started from a previous one), check if
|
|
567
|
+
# it hasn't been imported already thanks to the related commit SHA
|
|
568
|
+
version = self._get_existing_version(module, manifest_value, data["commit"])
|
|
569
|
+
if version and module_branch:
|
|
570
|
+
# Skip if the version has already been imported for a
|
|
571
|
+
# previous Odoo release
|
|
572
|
+
if version.branch_id.sequence < module_branch.branch_id.sequence:
|
|
573
|
+
continue
|
|
574
|
+
# Corner case: we scanned a version that was already imported
|
|
575
|
+
# through a newer Odoo branch. Downgrade the existing version
|
|
576
|
+
# to the current module branch.
|
|
577
|
+
if version.branch_id.sequence > module_branch.branch_id.sequence:
|
|
578
|
+
version.write(
|
|
579
|
+
{
|
|
580
|
+
"module_branch_id": module_branch.id,
|
|
581
|
+
"name": name,
|
|
582
|
+
}
|
|
583
|
+
)
|
|
584
|
+
continue
|
|
585
|
+
module_version = module_branch.version_ids.filtered(
|
|
586
|
+
lambda v: v.name == name and v.manifest_value == manifest_value
|
|
587
|
+
)
|
|
588
|
+
values = {
|
|
589
|
+
"name": name,
|
|
590
|
+
"manifest_value": manifest_value,
|
|
591
|
+
"commit": data["commit"],
|
|
592
|
+
"has_migration_script": data.get("migration_script", False),
|
|
593
|
+
}
|
|
594
|
+
if module_version:
|
|
595
|
+
version_ids.append(fields.Command.update(module_version.id, values))
|
|
596
|
+
else:
|
|
597
|
+
version_ids.append(fields.Command.create(values))
|
|
598
|
+
return version_ids
|
|
599
|
+
|
|
600
|
+
@tools.ormcache("category_name")
|
|
601
|
+
def _get_module_category_id(self, category_name):
|
|
602
|
+
if category_name:
|
|
603
|
+
rec = self.env["odoo.module.category"].search(
|
|
604
|
+
[("name", "=", category_name)], limit=1
|
|
605
|
+
)
|
|
606
|
+
if not rec:
|
|
607
|
+
rec = (
|
|
608
|
+
self.env["odoo.module.category"]
|
|
609
|
+
.sudo()
|
|
610
|
+
.create({"name": category_name})
|
|
611
|
+
)
|
|
612
|
+
return rec.id
|
|
613
|
+
return False
|
|
614
|
+
|
|
615
|
+
@tools.ormcache("names")
|
|
616
|
+
def _get_author_ids(self, names):
|
|
617
|
+
if names:
|
|
618
|
+
# Some Odoo std modules have a list instead of a string as 'author'
|
|
619
|
+
if isinstance(names, str):
|
|
620
|
+
names = [name.strip() for name in names.split(",")]
|
|
621
|
+
authors = self.env["odoo.author"].search([("name", "in", names)])
|
|
622
|
+
missing_author_names = set(names) - set(authors.mapped("name"))
|
|
623
|
+
missing_authors = self.env["odoo.author"]
|
|
624
|
+
if missing_author_names:
|
|
625
|
+
missing_authors = (
|
|
626
|
+
self.env["odoo.author"]
|
|
627
|
+
.sudo()
|
|
628
|
+
.create([{"name": name} for name in missing_author_names])
|
|
629
|
+
)
|
|
630
|
+
return (authors | missing_authors).ids
|
|
631
|
+
return []
|
|
632
|
+
|
|
633
|
+
@tools.ormcache("names")
|
|
634
|
+
def _get_maintainer_ids(self, names):
|
|
635
|
+
if names:
|
|
636
|
+
maintainers = self.env["odoo.maintainer"].search([("name", "in", names)])
|
|
637
|
+
missing_maintainer_names = set(names) - set(maintainers.mapped("name"))
|
|
638
|
+
created = self.env["odoo.maintainer"]
|
|
639
|
+
if missing_maintainer_names:
|
|
640
|
+
created = created.sudo().create(
|
|
641
|
+
[{"name": name} for name in missing_maintainer_names]
|
|
642
|
+
)
|
|
643
|
+
return (maintainers | created).ids
|
|
644
|
+
return []
|
|
645
|
+
|
|
646
|
+
@tools.ormcache("name")
|
|
647
|
+
def _get_dev_status_id(self, name):
|
|
648
|
+
if name:
|
|
649
|
+
rec = self.env["odoo.module.dev.status"].search(
|
|
650
|
+
[("name", "=", name)], limit=1
|
|
651
|
+
)
|
|
652
|
+
if not rec:
|
|
653
|
+
rec = self.env["odoo.module.dev.status"].sudo().create({"name": name})
|
|
654
|
+
return rec.id
|
|
655
|
+
return False
|
|
656
|
+
|
|
657
|
+
@api.model
|
|
658
|
+
def _find(self, branch, module, repo, domain=None):
|
|
659
|
+
"""Find an `odoo.module.branch` record matching parameters.
|
|
660
|
+
|
|
661
|
+
The lookup of the module is in this order:
|
|
662
|
+
- search in `repo`
|
|
663
|
+
- search among generic modules in other repositories
|
|
664
|
+
- search among orphaned modules
|
|
665
|
+
|
|
666
|
+
Additional search criteria can be added with `domain`,
|
|
667
|
+
e.g. `domain=[('installable', '=', True)]`.
|
|
668
|
+
"""
|
|
669
|
+
# Look for the module first in the current repository
|
|
670
|
+
module_branch = self.browse()
|
|
671
|
+
if repo:
|
|
672
|
+
module_branch = self._get_module_branch(
|
|
673
|
+
branch, module, repo=repo, domain=domain
|
|
674
|
+
)
|
|
675
|
+
# Then look among generic modules
|
|
676
|
+
if not module_branch:
|
|
677
|
+
modules_branch = self._get_module_branch(
|
|
678
|
+
branch,
|
|
679
|
+
module,
|
|
680
|
+
domain=expression.AND(
|
|
681
|
+
[
|
|
682
|
+
domain or [],
|
|
683
|
+
[("specific", "=", False), ("repository_id", "!=", False)],
|
|
684
|
+
],
|
|
685
|
+
),
|
|
686
|
+
)
|
|
687
|
+
module_branch = fields.first(modules_branch)
|
|
688
|
+
# Otherwise look for the module among orphaned modules
|
|
689
|
+
if not module_branch:
|
|
690
|
+
module_branch = self._get_orphaned_module_branch(
|
|
691
|
+
branch, module, domain=domain
|
|
692
|
+
)
|
|
693
|
+
return module_branch
|
|
694
|
+
|
|
695
|
+
@api.model
|
|
696
|
+
def _find_or_create(self, branch, module, repo, domain=None):
|
|
697
|
+
"""Find an `odoo.module.branch` record (see `_find`), or create an orphaned one."""
|
|
698
|
+
module_branch = self._find(branch, module, repo, domain=domain)
|
|
699
|
+
# If still not found, create the module as an orphaned module
|
|
700
|
+
# (it will hopefully be bound to a repository later)
|
|
701
|
+
if not module_branch:
|
|
702
|
+
module_branch = self.sudo()._create_orphaned_module_branch(branch, module)
|
|
703
|
+
return module_branch
|
|
704
|
+
|
|
705
|
+
def _get_dependency_ids(self, repo_branch, depends: list):
|
|
706
|
+
dependency_ids = []
|
|
707
|
+
for depend in depends:
|
|
708
|
+
module = self._get_module(depend)
|
|
709
|
+
dependency = self._find_or_create(
|
|
710
|
+
repo_branch.branch_id, module, repo_branch.repository_id
|
|
711
|
+
)
|
|
712
|
+
dependency_ids.append(dependency.id)
|
|
713
|
+
return dependency_ids
|
|
714
|
+
|
|
715
|
+
@tools.ormcache("packages")
|
|
716
|
+
def _get_python_dependency_ids(self, packages):
|
|
717
|
+
if packages:
|
|
718
|
+
dependencies = self.env["odoo.python.dependency"].search(
|
|
719
|
+
[("name", "in", packages)]
|
|
720
|
+
)
|
|
721
|
+
missing_dependencies = set(packages) - set(dependencies.mapped("name"))
|
|
722
|
+
created = self.env["odoo.python.dependency"]
|
|
723
|
+
if missing_dependencies:
|
|
724
|
+
created = created.sudo().create(
|
|
725
|
+
[{"name": package} for package in missing_dependencies]
|
|
726
|
+
)
|
|
727
|
+
return (dependencies | created).ids
|
|
728
|
+
return []
|
|
729
|
+
|
|
730
|
+
@tools.ormcache("license_name")
|
|
731
|
+
def _get_license_id(self, license_name):
|
|
732
|
+
if license_name:
|
|
733
|
+
license_model = self.env["odoo.license"]
|
|
734
|
+
rec = license_model.search([("name", "=", license_name)], limit=1)
|
|
735
|
+
if not rec:
|
|
736
|
+
rec = license_model.sudo().create({"name": license_name})
|
|
737
|
+
return rec.id
|
|
738
|
+
return False
|
|
739
|
+
|
|
740
|
+
def _get_module(self, name):
|
|
741
|
+
module = self.env["odoo.module"].search([("name", "=", name)])
|
|
742
|
+
if not module:
|
|
743
|
+
module = self.env["odoo.module"].sudo().create({"name": name})
|
|
744
|
+
return module
|
|
745
|
+
|
|
746
|
+
@api.model
|
|
747
|
+
def _get_module_branch_domain(self, branch, module, repo=None, domain=None):
|
|
748
|
+
"""Return the domain to identify an `odoo.module.branch` record."""
|
|
749
|
+
_domain = [
|
|
750
|
+
("branch_id", "=", branch.id),
|
|
751
|
+
("module_id", "=", module.id),
|
|
752
|
+
]
|
|
753
|
+
if repo:
|
|
754
|
+
_domain.append(("repository_id", "=", repo.id))
|
|
755
|
+
elif repo is False:
|
|
756
|
+
_domain.append(("repository_id", "=", False))
|
|
757
|
+
if domain:
|
|
758
|
+
_domain.extend(domain)
|
|
759
|
+
return _domain
|
|
760
|
+
|
|
761
|
+
@api.model
|
|
762
|
+
def _get_module_branch(self, branch, module, repo=None, domain=None):
|
|
763
|
+
"""Return the `odoo.module.branch` if it already exists. Do not create it."""
|
|
764
|
+
domain = self._get_module_branch_domain(
|
|
765
|
+
branch, module, repo=repo, domain=domain
|
|
766
|
+
)
|
|
767
|
+
return self.search(domain)
|
|
768
|
+
|
|
769
|
+
@api.model
|
|
770
|
+
def _get_orphaned_module_branch_domain(self, branch, module, domain=None):
|
|
771
|
+
"""Return the domain to identify an orphaned module (without repo)."""
|
|
772
|
+
return self._get_module_branch_domain(branch, module, repo=False, domain=domain)
|
|
773
|
+
|
|
774
|
+
@api.model
|
|
775
|
+
def _get_orphaned_module_branch(self, branch, module, domain=None):
|
|
776
|
+
"""Return an orphaned module matching `branch` and `module`."""
|
|
777
|
+
domain = self._get_orphaned_module_branch_domain(branch, module, domain=domain)
|
|
778
|
+
return self.search(domain)
|
|
779
|
+
|
|
780
|
+
@api.model
|
|
781
|
+
def _get_unmerged_module_branch_domain(self, branch, module):
|
|
782
|
+
"""Return the domain to identify an unmerged module (coming from a PR)."""
|
|
783
|
+
domain = self._get_module_branch_domain(branch, module)
|
|
784
|
+
domain.extend(
|
|
785
|
+
[
|
|
786
|
+
("specific", "=", False),
|
|
787
|
+
("repository_id", "!=", False),
|
|
788
|
+
("pr_url", "!=", False),
|
|
789
|
+
]
|
|
790
|
+
)
|
|
791
|
+
return domain
|
|
792
|
+
|
|
793
|
+
@api.model
|
|
794
|
+
def _get_unmerged_module_branch(self, branch, module):
|
|
795
|
+
"""Return an unmerged module matching `branch` and `module`."""
|
|
796
|
+
domain = self._get_unmerged_module_branch_domain(branch, module)
|
|
797
|
+
return self.search(domain)
|
|
798
|
+
|
|
799
|
+
def _create_orphaned_module_branch(self, branch, module):
|
|
800
|
+
"""Create an orphaned module."""
|
|
801
|
+
values = {
|
|
802
|
+
"module_id": module.id,
|
|
803
|
+
"branch_id": branch.id,
|
|
804
|
+
}
|
|
805
|
+
return self.create(values)
|
|
806
|
+
|
|
807
|
+
# TODO adds ormcache
|
|
808
|
+
def _get_modules_data(self, orgs=None, repositories=None, branches=None):
|
|
809
|
+
"""Returns modules data matching the criteria.
|
|
810
|
+
|
|
811
|
+
E.g.:
|
|
812
|
+
|
|
813
|
+
>>> self._get_modules_data(
|
|
814
|
+
... orgs=['OCA'],
|
|
815
|
+
... repositories=['server-env'],
|
|
816
|
+
... branches=['15.0', '16.0'],
|
|
817
|
+
... )
|
|
818
|
+
|
|
819
|
+
"""
|
|
820
|
+
domain = self._get_modules_domain(orgs, repositories, branches)
|
|
821
|
+
modules = self.search(domain)
|
|
822
|
+
data = []
|
|
823
|
+
for module in modules:
|
|
824
|
+
data.append(module._to_dict())
|
|
825
|
+
return data
|
|
826
|
+
|
|
827
|
+
def _get_modules_domain(self, orgs=None, repositories=None, branches=None):
|
|
828
|
+
domain = [
|
|
829
|
+
# Do not return orphans modules
|
|
830
|
+
("org_id", "!=", False),
|
|
831
|
+
("repository_id", "!=", False),
|
|
832
|
+
("branch_id", "!=", False),
|
|
833
|
+
]
|
|
834
|
+
if orgs:
|
|
835
|
+
domain.append(("org_id", "in", orgs))
|
|
836
|
+
if repositories:
|
|
837
|
+
domain.append(("repository_id", "in", repositories))
|
|
838
|
+
if branches:
|
|
839
|
+
domain.append(("branch_id", "in", branches))
|
|
840
|
+
return domain
|
|
841
|
+
|
|
842
|
+
def _to_dict(self):
|
|
843
|
+
"""Convert module data to a dictionary."""
|
|
844
|
+
self.ensure_one()
|
|
845
|
+
return {
|
|
846
|
+
"module": self.module_name,
|
|
847
|
+
"branch": self.branch_id.name,
|
|
848
|
+
"repository": self.repository_branch_id._to_dict(),
|
|
849
|
+
"title": self.title,
|
|
850
|
+
"summary": self.summary,
|
|
851
|
+
"authors": self.author_ids.mapped("name"),
|
|
852
|
+
"maintainers": self.maintainer_ids.mapped("name"),
|
|
853
|
+
"depends": self.dependency_ids.mapped("module_name"),
|
|
854
|
+
"category": self.category_id.name,
|
|
855
|
+
"license": self.license_id.name,
|
|
856
|
+
"version": self.version,
|
|
857
|
+
"versions": [version._to_dict() for version in self.version_ids],
|
|
858
|
+
"development_status": self.development_status_id.name,
|
|
859
|
+
"application": self.application,
|
|
860
|
+
"installable": self.installable,
|
|
861
|
+
"auto_install": self.auto_install,
|
|
862
|
+
"external_dependencies": self.external_dependencies,
|
|
863
|
+
"is_standard": self.is_standard,
|
|
864
|
+
"is_enterprise": self.is_enterprise,
|
|
865
|
+
"is_community": self.is_community,
|
|
866
|
+
"sloc_python": self.sloc_python,
|
|
867
|
+
"sloc_xml": self.sloc_xml,
|
|
868
|
+
"sloc_js": self.sloc_js,
|
|
869
|
+
"sloc_css": self.sloc_css,
|
|
870
|
+
"last_scanned_commit": self.last_scanned_commit,
|
|
871
|
+
"addons_path": self.addons_path,
|
|
872
|
+
"pr_url": self.pr_url,
|
|
873
|
+
}
|