From 33ac12fed0a20fd063eddb0bdde9df6b721fab77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Mon, 16 Feb 2026 08:11:58 +0100 Subject: [PATCH 1/2] [IMP] odoo_repository: sync OCA repositories Do not maintain the list of OCA repositories manually anymore, but sync it automatically with https://github.com/OCA/repo-maintainer-conf. All repositories having a default branch set to `master` or `main` will be skipped (such repositories are not hosting Odoo modules). Others like OpenUpgrade or OCB will be skipped too. --- odoo_repository/__init__.py | 1 + odoo_repository/__manifest__.py | 9 +- odoo_repository/components/__init__.py | 1 + .../components/oca_repository_synchronizer.py | 215 ++++++++++++++ odoo_repository/data/ir_config_parameter.xml | 11 + odoo_repository/data/ir_cron.xml | 17 ++ odoo_repository/data/odoo.repository.csv | 216 -------------- odoo_repository/data/odoo_mca_backend.xml | 8 + odoo_repository/data/queue_job.xml | 11 + .../migrations/16.0.1.5.0/pre-migration.py | 48 +++ odoo_repository/models/__init__.py | 1 + odoo_repository/models/odoo_mca_backend.py | 17 ++ odoo_repository/models/odoo_repository.py | 31 ++ odoo_repository/models/res_config_settings.py | 9 + odoo_repository/readme/DESCRIPTION.md | 2 +- odoo_repository/security/ir.model.access.csv | 1 + odoo_repository/tests/__init__.py | 1 + .../tests/test_oca_repository_synchronizer.py | 279 ++++++++++++++++++ .../tests/test_odoo_module_branch.py | 1 + odoo_repository/views/res_config_settings.xml | 21 ++ requirements.txt | 1 + 21 files changed, 682 insertions(+), 219 deletions(-) create mode 100644 odoo_repository/components/__init__.py create mode 100644 odoo_repository/components/oca_repository_synchronizer.py create mode 100644 odoo_repository/data/ir_config_parameter.xml delete mode 100644 odoo_repository/data/odoo.repository.csv create mode 100644 odoo_repository/data/odoo_mca_backend.xml create mode 100644 odoo_repository/migrations/16.0.1.5.0/pre-migration.py create mode 100644 odoo_repository/models/odoo_mca_backend.py create mode 100644 odoo_repository/tests/test_oca_repository_synchronizer.py diff --git a/odoo_repository/__init__.py b/odoo_repository/__init__.py index f7209b1..5f713a8 100644 --- a/odoo_repository/__init__.py +++ b/odoo_repository/__init__.py @@ -1,2 +1,3 @@ from . import models +from . import components from . import controllers diff --git a/odoo_repository/__manifest__.py b/odoo_repository/__manifest__.py index 18bbdce..7fb93df 100644 --- a/odoo_repository/__manifest__.py +++ b/odoo_repository/__manifest__.py @@ -1,9 +1,10 @@ # Copyright 2023 Camptocamp SA +# Copyright 2026 Sébastien Alix # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) { "name": "Odoo Repositories Data", "summary": "Base module to host data collected from Odoo repositories.", - "version": "16.0.1.4.4", + "version": "16.0.1.5.0", "category": "Tools", "author": "Camptocamp, Odoo Community Association (OCA)", "website": "https://github.com/OCA/module-composition-analysis", @@ -15,9 +16,10 @@ "data/odoo_repository_org.xml", "data/odoo_repository_addons_path.xml", "data/odoo_repository.xml", - "data/odoo.repository.csv", "data/odoo_branch.xml", "data/queue_job.xml", + "data/odoo_mca_backend.xml", + "data/ir_config_parameter.xml", "views/menu.xml", "views/authentication_token.xml", "views/ssh_key.xml", @@ -45,11 +47,14 @@ "base_time_window", # OCA/queue "queue_job", + # OCA/connector + "component", ], "external_dependencies": { "python": [ "gitpython", "odoo-addons-parser", + "pyyaml", # TODO to publish # "odoo-repository-scanner" ], diff --git a/odoo_repository/components/__init__.py b/odoo_repository/components/__init__.py new file mode 100644 index 0000000..f8f3805 --- /dev/null +++ b/odoo_repository/components/__init__.py @@ -0,0 +1 @@ +from . import oca_repository_synchronizer diff --git a/odoo_repository/components/oca_repository_synchronizer.py b/odoo_repository/components/oca_repository_synchronizer.py new file mode 100644 index 0000000..aa9c8fc --- /dev/null +++ b/odoo_repository/components/oca_repository_synchronizer.py @@ -0,0 +1,215 @@ +# Copyright 2026 Sébastien Alix +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import logging + +import requests +import yaml + +from odoo import Command + +from odoo.addons.component.core import Component +from odoo.addons.queue_job.exception import RetryableJobError + +from ..utils import github + +_logger = logging.getLogger(__name__) + + +class OCARepositorySynchronizer(Component): + """Component for synchronizing OCA repositories from GitHub configuration. + + This component is syncing the list of OCA repositories in local database + from https://github.com/OCA/repo-maintainer-conf repository. + It creates new repositories, updates existing ones, and archive those + no longer declared in the OCA configuration. + """ + + _name = "oca.repository.synchronizer" + _collection = "odoo.mca.backend" + _usage = "oca.repository.synchronizer" + + github_conf_api_url = "repos/OCA/repo-maintainer-conf/contents/conf/repo" + + def _get_oca_repo_blacklist(self): + """Get OCA repository blacklist from configuration parameter. + + Returns: + list: List of repository names to skip during synchronization + """ + param_value = ( + self.env["ir.config_parameter"] + .sudo() + .get_param("odoo_repository.oca_repo_blacklist", default="") + ) + return [repo.strip() for repo in param_value.split(",") if repo.strip()] + + def run(self): + """Execute OCA repository synchronization. + + Fetches repository configurations from OCA/repo-maintainer-conf, + parses the YAML files, and synchronizes the local repository database by: + - Creating new repositories that exist in OCA config but not locally + - Updating existing repositories with any changes + - Archiving repositories that no longer exist in OCA config + """ + try: + # Fetch YAML configurations from GitHub + repo_configs = self._fetch_oca_repo_configurations() + # Parse and extract repository data + current_repos = self._parse_oca_repo_configurations(repo_configs) + # Sync repositories (create/update/archive) + stats = self._sync_oca_repositories(current_repos) + _logger.info("OCA repository sync completed: %s", stats) + return stats + except Exception as e: + raise RetryableJobError("Failed to fetch OCA repositories") from e + + def _fetch_oca_repo_configurations(self): + """Fetch OCA repository configurations from GitHub. + + Returns: + dict: Repository configurations keyed by filename + """ + try: + # Get list of files in conf/repo directory + files = github.request(self.work.env, self.github_conf_api_url) + repo_configs = {} + for file_info in files: + if file_info["name"].endswith(".yml"): + # Fetch the actual YAML content + download_url = file_info["download_url"] + response = requests.get(download_url, timeout=30) + response.raise_for_status() + repo_configs[file_info["name"]] = response.text + return repo_configs + except Exception as e: + _logger.error("Failed to fetch OCA repo configurations: %s", str(e)) + raise + + def _parse_oca_repo_configurations(self, repo_configs: dict): + """Parse YAML configurations and extract OCA repository data. + + Args: + repo_configs: Dict of filename -> YAML content + + Returns: + dict: Repository data keyed by repository name, only including + repositories with non-empty branches + """ + current_repos = {} + for filename, yaml_content in repo_configs.items(): + try: + # Parse YAML content + data = yaml.safe_load(yaml_content) + if not data: + continue + # Process each repository in the YAML file + for repo_name, repo_config in data.items(): + # Get blacklist from backend configuration + blacklist = self._get_oca_repo_blacklist() + if repo_name in blacklist: + _logger.info("Skipping import of OCA repository %s", repo_name) + continue + # Skip repositories with default_branch = 'master' or 'main' + # These are not hosting Odoo modules + default_branch = repo_config.get("default_branch", "") + if default_branch in ["master", "main"]: + _logger.info( + "Skipping OCA repository %s " + "(default_branch=%s, not hosting Odoo modules)", + repo_name, + default_branch, + ) + continue + # Only include repositories with branches. + # Others repositories are probably not hosting Odoo modules. + branches = repo_config.get("branches", []) + if not branches: + continue + # Construct repository URLs + repo_url = f"https://github.com/OCA/{repo_name}" # noqa: E231 + current_repos[repo_name] = { + "name": repo_name, + "repo_url": repo_url, + "clone_url": repo_url, + "repo_type": "github", + } + except yaml.YAMLError as e: + _logger.warning("Failed to parse YAML file %s: %s", filename, str(e)) + continue + except Exception as e: + _logger.warning("Error processing file %s: %s", filename, str(e)) + continue + return current_repos + + def _sync_oca_repositories(self, current_repos): + """Synchronize OCA repositories with current configuration. + + Args: + current_repos: Dict of repository data from OCA configuration + + Returns: + dict: Statistics about created/updated/archived repositories + """ + stats = {"created": 0, "updated": 0, "archived": 0} + env = self.work.env + # Get OCA organization + oca_org = env.ref( + "odoo_repository.odoo_repository_org_oca", raise_if_not_found=False + ) + if not oca_org: + _logger.warning("OCA organization not found in database") + return stats + # Get existing OCA repositories + odoo_repository = env["odoo.repository"] + existing_repos = odoo_repository.with_context(active_test=False).search( + [("org_id", "=", oca_org.id)] + ) + # Process current repositories + for repo_name, repo_data in current_repos.items(): + # Check if repository already exists + repo = existing_repos.filtered(lambda r: r.name == repo_name) + if repo: + # Update existing repository + update_vals = self._prepare_oca_repository_update_vals(repo_data) + # Only update if values have changed + if any(repo[field] != update_vals[field] for field in update_vals): + repo.write(update_vals) + stats["updated"] += 1 + else: + # Create new repository + create_vals = self._prepare_oca_repository_create_vals(repo_data) + odoo_repository.create(create_vals) + stats["created"] += 1 + # Archive repositories that no longer exist in configuration + for repo in existing_repos: + if repo.name not in current_repos and repo.active: + repo.active = repo.to_scan = False + stats["archived"] += 1 + return stats + + def _prepare_oca_repository_update_vals(self, repo_data): + return { + "repo_url": repo_data["repo_url"], + "clone_url": repo_data["clone_url"], + "repo_type": repo_data["repo_type"], + "active": True, + } + + def _prepare_oca_repository_create_vals(self, repo_data): + oca_org = self.work.env.ref("odoo_repository.odoo_repository_org_oca") + addons_path_community = self.work.env.ref( + "odoo_repository.odoo_repository_addons_path_community" + ) + return { + "org_id": oca_org.id, + "name": repo_data["name"], + "repo_url": repo_data["repo_url"], + "clone_url": repo_data["clone_url"], + "repo_type": repo_data["repo_type"], + "active": True, + "sequence": 200, + "to_scan": True, # Enable scanning by default + "addons_path_ids": [Command.link(addons_path_community.id)], + } diff --git a/odoo_repository/data/ir_config_parameter.xml b/odoo_repository/data/ir_config_parameter.xml new file mode 100644 index 0000000..369c98f --- /dev/null +++ b/odoo_repository/data/ir_config_parameter.xml @@ -0,0 +1,11 @@ + + + + + + odoo_repository.oca_repo_blacklist + OCB,OpenUpgrade + + + diff --git a/odoo_repository/data/ir_cron.xml b/odoo_repository/data/ir_cron.xml index 6c5e8ca..b437b06 100644 --- a/odoo_repository/data/ir_cron.xml +++ b/odoo_repository/data/ir_cron.xml @@ -1,5 +1,6 @@ @@ -35,4 +36,20 @@ model.cron_fetch_data() + + Odoo Repositories - Sync OCA repositories + 1 + weeks + -1 + + + + + code + model.cron_sync_oca_repositories() + + diff --git a/odoo_repository/data/odoo.repository.csv b/odoo_repository/data/odoo.repository.csv deleted file mode 100644 index 652e0c9..0000000 --- a/odoo_repository/data/odoo.repository.csv +++ /dev/null @@ -1,216 +0,0 @@ -id,org_id:id,name,repo_url,clone_url,repo_type -repo_oca_account_analytic,odoo_repository_org_oca,account-analytic,https://github.com/OCA/account-analytic,https://github.com/OCA/account-analytic,github -repo_oca_account_budgeting,odoo_repository_org_oca,account-budgeting,https://github.com/OCA/account-budgeting,https://github.com/OCA/account-budgeting,github -repo_oca_account_closing,odoo_repository_org_oca,account-closing,https://github.com/OCA/account-closing,https://github.com/OCA/account-closing,github -repo_oca_account_consolidation,odoo_repository_org_oca,account-consolidation,https://github.com/OCA/account-consolidation,https://github.com/OCA/account-consolidation,github -repo_oca_account_financial_reporting,odoo_repository_org_oca,account-financial-reporting,https://github.com/OCA/account-financial-reporting,https://github.com/OCA/account-financial-reporting,github -repo_oca_account_financial_tools,odoo_repository_org_oca,account-financial-tools,https://github.com/OCA/account-financial-tools,https://github.com/OCA/account-financial-tools,github -repo_oca_account_fiscal_rule,odoo_repository_org_oca,account-fiscal-rule,https://github.com/OCA/account-fiscal-rule,https://github.com/OCA/account-fiscal-rule,github -repo_oca_account_invoice_reporting,odoo_repository_org_oca,account-invoice-reporting,https://github.com/OCA/account-invoice-reporting,https://github.com/OCA/account-invoice-reporting,github -repo_oca_account_invoicing,odoo_repository_org_oca,account-invoicing,https://github.com/OCA/account-invoicing,https://github.com/OCA/account-invoicing,github -repo_oca_account_payment,odoo_repository_org_oca,account-payment,https://github.com/OCA/account-payment,https://github.com/OCA/account-payment,github -repo_oca_account_reconcile,odoo_repository_org_oca,account-reconcile,https://github.com/OCA/account-reconcile,https://github.com/OCA/account-reconcile,github -repo_oca_agreement,odoo_repository_org_oca,agreement,https://github.com/OCA/agreement,https://github.com/OCA/agreement,github -repo_oca_apps_store,odoo_repository_org_oca,apps-store,https://github.com/OCA/apps-store,https://github.com/OCA/apps-store,github -repo_oca_bank_payment,odoo_repository_org_oca,bank-payment,https://github.com/OCA/bank-payment,https://github.com/OCA/bank-payment,github -repo_oca_bank_payment_alternative,odoo_repository_org_oca,bank-payment-alternative,https://github.com/OCA/bank-payment-alternative,https://github.com/OCA/bank-payment-alternative,github -repo_oca_bank_statement_import,odoo_repository_org_oca,bank-statement-import,https://github.com/OCA/bank-statement-import,https://github.com/OCA/bank-statement-import,github -repo_oca_brand,odoo_repository_org_oca,brand,https://github.com/OCA/brand,https://github.com/OCA/brand,github -repo_oca_business_requirement,odoo_repository_org_oca,business-requirement,https://github.com/OCA/business-requirement,https://github.com/OCA/business-requirement,github -repo_oca_calendar,odoo_repository_org_oca,calendar,https://github.com/OCA/calendar,https://github.com/OCA/calendar,github -repo_oca_commission,odoo_repository_org_oca,commission,https://github.com/OCA/commission,https://github.com/OCA/commission,github -repo_oca_community_data_files,odoo_repository_org_oca,community-data-files,https://github.com/OCA/community-data-files,https://github.com/OCA/community-data-files,github -repo_oca_connector,odoo_repository_org_oca,connector,https://github.com/OCA/connector,https://github.com/OCA/connector,github -repo_oca_connector_accountedge,odoo_repository_org_oca,connector-accountedge,https://github.com/OCA/connector-accountedge,https://github.com/OCA/connector-accountedge,github -repo_oca_connector_cmis,odoo_repository_org_oca,connector-cmis,https://github.com/OCA/connector-cmis,https://github.com/OCA/connector-cmis,github -repo_oca_connector_ecommerce,odoo_repository_org_oca,connector-ecommerce,https://github.com/OCA/connector-ecommerce,https://github.com/OCA/connector-ecommerce,github -repo_oca_connector_infor,odoo_repository_org_oca,connector-infor,https://github.com/OCA/connector-infor,https://github.com/OCA/connector-infor,github -repo_oca_connector_interfaces,odoo_repository_org_oca,connector-interfaces,https://github.com/OCA/connector-interfaces,https://github.com/OCA/connector-interfaces,github -repo_oca_connector_jira,odoo_repository_org_oca,connector-jira,https://github.com/OCA/connector-jira,https://github.com/OCA/connector-jira,github -repo_oca_connector_lengow,odoo_repository_org_oca,connector-lengow,https://github.com/OCA/connector-lengow,https://github.com/OCA/connector-lengow,github -repo_oca_connector_lims,odoo_repository_org_oca,connector-lims,https://github.com/OCA/connector-lims,https://github.com/OCA/connector-lims,github -repo_oca_connector_magento,odoo_repository_org_oca,connector-magento,https://github.com/OCA/connector-magento,https://github.com/OCA/connector-magento,github -repo_oca_connector_magento_php_extension,odoo_repository_org_oca,connector-magento-php-extension,https://github.com/OCA/connector-magento-php-extension,https://github.com/OCA/connector-magento-php-extension,github -repo_oca_connector_odoo2odoo,odoo_repository_org_oca,connector-odoo2odoo,https://github.com/OCA/connector-odoo2odoo,https://github.com/OCA/connector-odoo2odoo,github -repo_oca_connector_prestashop,odoo_repository_org_oca,connector-prestashop,https://github.com/OCA/connector-prestashop,https://github.com/OCA/connector-prestashop,github -repo_oca_connector_redmine,odoo_repository_org_oca,connector-redmine,https://github.com/OCA/connector-redmine,https://github.com/OCA/connector-redmine,github -repo_oca_connector_sage,odoo_repository_org_oca,connector-sage,https://github.com/OCA/connector-sage,https://github.com/OCA/connector-sage,github -repo_oca_connector_salesforce,odoo_repository_org_oca,connector-salesforce,https://github.com/OCA/connector-salesforce,https://github.com/OCA/connector-salesforce,github -repo_oca_connector_spscommerce,odoo_repository_org_oca,connector-spscommerce,https://github.com/OCA/connector-spscommerce,https://github.com/OCA/connector-spscommerce,github -repo_oca_connector_telephony,odoo_repository_org_oca,connector-telephony,https://github.com/OCA/connector-telephony,https://github.com/OCA/connector-telephony,github -repo_oca_connector_woocommerce,odoo_repository_org_oca,connector-woocommerce,https://github.com/OCA/connector-woocommerce,https://github.com/OCA/connector-woocommerce,github -repo_oca_contract,odoo_repository_org_oca,contract,https://github.com/OCA/contract,https://github.com/OCA/contract,github -repo_oca_cooperative,odoo_repository_org_oca,cooperative,https://github.com/OCA/cooperative,https://github.com/OCA/cooperative,github -repo_oca_credit_control,odoo_repository_org_oca,credit-control,https://github.com/OCA/credit-control,https://github.com/OCA/credit-control,github -repo_oca_crm,odoo_repository_org_oca,crm,https://github.com/OCA/crm,https://github.com/OCA/crm,github -repo_oca_currency,odoo_repository_org_oca,currency,https://github.com/OCA/currency,https://github.com/OCA/currency,github -repo_oca_data_protection,odoo_repository_org_oca,data-protection,https://github.com/OCA/data-protection,https://github.com/OCA/data-protection,github -repo_oca_ddmrp,odoo_repository_org_oca,ddmrp,https://github.com/OCA/ddmrp,https://github.com/OCA/ddmrp,github -repo_oca_delivery_carrier,odoo_repository_org_oca,delivery-carrier,https://github.com/OCA/delivery-carrier,https://github.com/OCA/delivery-carrier,github -repo_oca_department,odoo_repository_org_oca,department,https://github.com/OCA/department,https://github.com/OCA/department,github -repo_oca_dms,odoo_repository_org_oca,dms,https://github.com/OCA/dms,https://github.com/OCA/dms,github -repo_oca_donation,odoo_repository_org_oca,donation,https://github.com/OCA/donation,https://github.com/OCA/donation,github -repo_oca_e_commerce,odoo_repository_org_oca,e-commerce,https://github.com/OCA/e-commerce,https://github.com/OCA/e-commerce,github -repo_oca_e_learning,odoo_repository_org_oca,e-learning,https://github.com/OCA/e-learning,https://github.com/OCA/e-learning,github -repo_oca_edi,odoo_repository_org_oca,edi,https://github.com/OCA/edi,https://github.com/OCA/edi,github -repo_oca_edi_framework,odoo_repository_org_oca,edi-framework,https://github.com/OCA/edi-framework,https://github.com/OCA/edi-framework,github -repo_oca_event,odoo_repository_org_oca,event,https://github.com/OCA/event,https://github.com/OCA/event,github -repo_oca_field_service,odoo_repository_org_oca,field-service,https://github.com/OCA/field-service,https://github.com/OCA/field-service,github -repo_oca_fleet,odoo_repository_org_oca,fleet,https://github.com/OCA/fleet,https://github.com/OCA/fleet,github -repo_oca_geospatial,odoo_repository_org_oca,geospatial,https://github.com/OCA/geospatial,https://github.com/OCA/geospatial,github -repo_oca_helpdesk,odoo_repository_org_oca,helpdesk,https://github.com/OCA/helpdesk,https://github.com/OCA/helpdesk,github -repo_oca_hr,odoo_repository_org_oca,hr,https://github.com/OCA/hr,https://github.com/OCA/hr,github -repo_oca_hr_attendance,odoo_repository_org_oca,hr-attendance,https://github.com/OCA/hr-attendance,https://github.com/OCA/hr-attendance,github -repo_oca_hr_expense,odoo_repository_org_oca,hr-expense,https://github.com/OCA/hr-expense,https://github.com/OCA/hr-expense,github -repo_oca_hr_holidays,odoo_repository_org_oca,hr-holidays,https://github.com/OCA/hr-holidays,https://github.com/OCA/hr-holidays,github -repo_oca_infrastructure,odoo_repository_org_oca,infrastructure,https://github.com/OCA/infrastructure,https://github.com/OCA/infrastructure,github -repo_oca_interface_github,odoo_repository_org_oca,interface-github,https://github.com/OCA/interface-github,https://github.com/OCA/interface-github,github -repo_oca_intrastat_extrastat,odoo_repository_org_oca,intrastat-extrastat,https://github.com/OCA/intrastat-extrastat,https://github.com/OCA/intrastat-extrastat,github -repo_oca_iot,odoo_repository_org_oca,iot,https://github.com/OCA/iot,https://github.com/OCA/iot,github -repo_oca_knowledge,odoo_repository_org_oca,knowledge,https://github.com/OCA/knowledge,https://github.com/OCA/knowledge,github -repo_oca_l10n_argentina,odoo_repository_org_oca,l10n-argentina,https://github.com/OCA/l10n-argentina,https://github.com/OCA/l10n-argentina,github -repo_oca_l10n_austria,odoo_repository_org_oca,l10n-austria,https://github.com/OCA/l10n-austria,https://github.com/OCA/l10n-austria,github -repo_oca_l10n_belarus,odoo_repository_org_oca,l10n-belarus,https://github.com/OCA/l10n-belarus,https://github.com/OCA/l10n-belarus,github -repo_oca_l10n_belgium,odoo_repository_org_oca,l10n-belgium,https://github.com/OCA/l10n-belgium,https://github.com/OCA/l10n-belgium,github -repo_oca_l10n_brazil,odoo_repository_org_oca,l10n-brazil,https://github.com/OCA/l10n-brazil,https://github.com/OCA/l10n-brazil,github -repo_oca_l10n_cambodia,odoo_repository_org_oca,l10n-cambodia,https://github.com/OCA/l10n-cambodia,https://github.com/OCA/l10n-cambodia,github -repo_oca_l10n_canada,odoo_repository_org_oca,l10n-canada,https://github.com/OCA/l10n-canada,https://github.com/OCA/l10n-canada,github -repo_oca_l10n_chile,odoo_repository_org_oca,l10n-chile,https://github.com/OCA/l10n-chile,https://github.com/OCA/l10n-chile,github -repo_oca_l10n_china,odoo_repository_org_oca,l10n-china,https://github.com/OCA/l10n-china,https://github.com/OCA/l10n-china,github -repo_oca_l10n_colombia,odoo_repository_org_oca,l10n-colombia,https://github.com/OCA/l10n-colombia,https://github.com/OCA/l10n-colombia,github -repo_oca_l10n_costa_rica,odoo_repository_org_oca,l10n-costa-rica,https://github.com/OCA/l10n-costa-rica,https://github.com/OCA/l10n-costa-rica,github -repo_oca_l10n_croatia,odoo_repository_org_oca,l10n-croatia,https://github.com/OCA/l10n-croatia,https://github.com/OCA/l10n-croatia,github -repo_oca_l10n_ecuador,odoo_repository_org_oca,l10n-ecuador,https://github.com/OCA/l10n-ecuador,https://github.com/OCA/l10n-ecuador,github -repo_oca_l10n_estonia,odoo_repository_org_oca,l10n-estonia,https://github.com/OCA/l10n-estonia,https://github.com/OCA/l10n-estonia,github -repo_oca_l10n_ethiopia,odoo_repository_org_oca,l10n-ethiopia,https://github.com/OCA/l10n-ethiopia,https://github.com/OCA/l10n-ethiopia,github -repo_oca_l10n_finland,odoo_repository_org_oca,l10n-finland,https://github.com/OCA/l10n-finland,https://github.com/OCA/l10n-finland,github -repo_oca_l10n_france,odoo_repository_org_oca,l10n-france,https://github.com/OCA/l10n-france,https://github.com/OCA/l10n-france,github -repo_oca_l10n_germany,odoo_repository_org_oca,l10n-germany,https://github.com/OCA/l10n-germany,https://github.com/OCA/l10n-germany,github -repo_oca_l10n_greece,odoo_repository_org_oca,l10n-greece,https://github.com/OCA/l10n-greece,https://github.com/OCA/l10n-greece,github -repo_oca_l10n_india,odoo_repository_org_oca,l10n-india,https://github.com/OCA/l10n-india,https://github.com/OCA/l10n-india,github -repo_oca_l10n_indonesia,odoo_repository_org_oca,l10n-indonesia,https://github.com/OCA/l10n-indonesia,https://github.com/OCA/l10n-indonesia,github -repo_oca_l10n_iran,odoo_repository_org_oca,l10n-iran,https://github.com/OCA/l10n-iran,https://github.com/OCA/l10n-iran,github -repo_oca_l10n_ireland,odoo_repository_org_oca,l10n-ireland,https://github.com/OCA/l10n-ireland,https://github.com/OCA/l10n-ireland,github -repo_oca_l10n_italy,odoo_repository_org_oca,l10n-italy,https://github.com/OCA/l10n-italy,https://github.com/OCA/l10n-italy,github -repo_oca_l10n_japan,odoo_repository_org_oca,l10n-japan,https://github.com/OCA/l10n-japan,https://github.com/OCA/l10n-japan,github -repo_oca_l10n_luxemburg,odoo_repository_org_oca,l10n-luxemburg,https://github.com/OCA/l10n-luxemburg,https://github.com/OCA/l10n-luxemburg,github -repo_oca_l10n_macedonia,odoo_repository_org_oca,l10n-macedonia,https://github.com/OCA/l10n-macedonia,https://github.com/OCA/l10n-macedonia,github -repo_oca_l10n_mexico,odoo_repository_org_oca,l10n-mexico,https://github.com/OCA/l10n-mexico,https://github.com/OCA/l10n-mexico,github -repo_oca_l10n_morocco,odoo_repository_org_oca,l10n-morocco,https://github.com/OCA/l10n-morocco,https://github.com/OCA/l10n-morocco,github -repo_oca_l10n_netherlands,odoo_repository_org_oca,l10n-netherlands,https://github.com/OCA/l10n-netherlands,https://github.com/OCA/l10n-netherlands,github -repo_oca_l10n_norway,odoo_repository_org_oca,l10n-norway,https://github.com/OCA/l10n-norway,https://github.com/OCA/l10n-norway,github -repo_oca_l10n_peru,odoo_repository_org_oca,l10n-peru,https://github.com/OCA/l10n-peru,https://github.com/OCA/l10n-peru,github -repo_oca_l10n_poland,odoo_repository_org_oca,l10n-poland,https://github.com/OCA/l10n-poland,https://github.com/OCA/l10n-poland,github -repo_oca_l10n_portugal,odoo_repository_org_oca,l10n-portugal,https://github.com/OCA/l10n-portugal,https://github.com/OCA/l10n-portugal,github -repo_oca_l10n_romania,odoo_repository_org_oca,l10n-romania,https://github.com/OCA/l10n-romania,https://github.com/OCA/l10n-romania,github -repo_oca_l10n_russia,odoo_repository_org_oca,l10n-russia,https://github.com/OCA/l10n-russia,https://github.com/OCA/l10n-russia,github -repo_oca_l10n_slovenia,odoo_repository_org_oca,l10n-slovenia,https://github.com/OCA/l10n-slovenia,https://github.com/OCA/l10n-slovenia,github -repo_oca_l10n_spain,odoo_repository_org_oca,l10n-spain,https://github.com/OCA/l10n-spain,https://github.com/OCA/l10n-spain,github -repo_oca_l10n_switzerland,odoo_repository_org_oca,l10n-switzerland,https://github.com/OCA/l10n-switzerland,https://github.com/OCA/l10n-switzerland,github -repo_oca_l10n_taiwan,odoo_repository_org_oca,l10n-taiwan,https://github.com/OCA/l10n-taiwan,https://github.com/OCA/l10n-taiwan,github -repo_oca_l10n_thailand,odoo_repository_org_oca,l10n-thailand,https://github.com/OCA/l10n-thailand,https://github.com/OCA/l10n-thailand,github -repo_oca_l10n_turkey,odoo_repository_org_oca,l10n-turkey,https://github.com/OCA/l10n-turkey,https://github.com/OCA/l10n-turkey,github -repo_oca_l10n_ukraine,odoo_repository_org_oca,l10n-ukraine,https://github.com/OCA/l10n-ukraine,https://github.com/OCA/l10n-ukraine,github -repo_oca_l10n_united_kingdom,odoo_repository_org_oca,l10n-united-kingdom,https://github.com/OCA/l10n-united-kingdom,https://github.com/OCA/l10n-united-kingdom,github -repo_oca_l10n_uruguay,odoo_repository_org_oca,l10n-uruguay,https://github.com/OCA/l10n-uruguay,https://github.com/OCA/l10n-uruguay,github -repo_oca_l10n_usa,odoo_repository_org_oca,l10n-usa,https://github.com/OCA/l10n-usa,https://github.com/OCA/l10n-usa,github -repo_oca_l10n_venezuela,odoo_repository_org_oca,l10n-venezuela,https://github.com/OCA/l10n-venezuela,https://github.com/OCA/l10n-venezuela,github -repo_oca_l10n_vietnam,odoo_repository_org_oca,l10n-vietnam,https://github.com/OCA/l10n-vietnam,https://github.com/OCA/l10n-vietnam,github -repo_oca_mail,odoo_repository_org_oca,mail,https://github.com/OCA/mail,https://github.com/OCA/mail,github -repo_oca_maintenance,odoo_repository_org_oca,maintenance,https://github.com/OCA/maintenance,https://github.com/OCA/maintenance,github -repo_oca_management_system,odoo_repository_org_oca,management-system,https://github.com/OCA/management-system,https://github.com/OCA/management-system,github -repo_oca_manufacture,odoo_repository_org_oca,manufacture,https://github.com/OCA/manufacture,https://github.com/OCA/manufacture,github -repo_oca_manufacture_reporting,odoo_repository_org_oca,manufacture-reporting,https://github.com/OCA/manufacture-reporting,https://github.com/OCA/manufacture-reporting,github -repo_oca_margin_analysis,odoo_repository_org_oca,margin-analysis,https://github.com/OCA/margin-analysis,https://github.com/OCA/margin-analysis,github -repo_oca_mis_builder,odoo_repository_org_oca,mis-builder,https://github.com/OCA/mis-builder,https://github.com/OCA/mis-builder,github -repo_oca_mis_builder_contrib,odoo_repository_org_oca,mis-builder-contrib,https://github.com/OCA/mis-builder-contrib,https://github.com/OCA/mis-builder-contrib,github -repo_oca_multi_company,odoo_repository_org_oca,multi-company,https://github.com/OCA/multi-company,https://github.com/OCA/multi-company,github -repo_oca_oca_custom,odoo_repository_org_oca,oca-custom,https://github.com/OCA/oca-custom,https://github.com/OCA/oca-custom,github -repo_oca_odoo_pim,odoo_repository_org_oca,odoo-pim,https://github.com/OCA/odoo-pim,https://github.com/OCA/odoo-pim,github -repo_oca_operating_unit,odoo_repository_org_oca,operating-unit,https://github.com/OCA/operating-unit,https://github.com/OCA/operating-unit,github -repo_oca_partner_contact,odoo_repository_org_oca,partner-contact,https://github.com/OCA/partner-contact,https://github.com/OCA/partner-contact,github -repo_oca_payroll,odoo_repository_org_oca,payroll,https://github.com/OCA/payroll,https://github.com/OCA/payroll,github -repo_oca_pms,odoo_repository_org_oca,pms,https://github.com/OCA/pms,https://github.com/OCA/pms,github -repo_oca_pos,odoo_repository_org_oca,pos,https://github.com/OCA/pos,https://github.com/OCA/pos,github -repo_oca_product_attribute,odoo_repository_org_oca,product-attribute,https://github.com/OCA/product-attribute,https://github.com/OCA/product-attribute,github -repo_oca_product_configurator,odoo_repository_org_oca,product-configurator,https://github.com/OCA/product-configurator,https://github.com/OCA/product-configurator,github -repo_oca_product_kitting,odoo_repository_org_oca,product-kitting,https://github.com/OCA/product-kitting,https://github.com/OCA/product-kitting,github -repo_oca_product_pack,odoo_repository_org_oca,product-pack,https://github.com/OCA/product-pack,https://github.com/OCA/product-pack,github -repo_oca_product_variant,odoo_repository_org_oca,product-variant,https://github.com/OCA/product-variant,https://github.com/OCA/product-variant,github -repo_oca_program,odoo_repository_org_oca,program,https://github.com/OCA/program,https://github.com/OCA/program,github -repo_oca_project,odoo_repository_org_oca,project,https://github.com/OCA/project,https://github.com/OCA/project,github -repo_oca_project_agile,odoo_repository_org_oca,project-agile,https://github.com/OCA/project-agile,https://github.com/OCA/project-agile,github -repo_oca_project_reporting,odoo_repository_org_oca,project-reporting,https://github.com/OCA/project-reporting,https://github.com/OCA/project-reporting,github -repo_oca_purchase_reporting,odoo_repository_org_oca,purchase-reporting,https://github.com/OCA/purchase-reporting,https://github.com/OCA/purchase-reporting,github -repo_oca_purchase_workflow,odoo_repository_org_oca,purchase-workflow,https://github.com/OCA/purchase-workflow,https://github.com/OCA/purchase-workflow,github -repo_oca_pwa_builder,odoo_repository_org_oca,pwa-builder,https://github.com/OCA/pwa-builder,https://github.com/OCA/pwa-builder,github -repo_oca_queue,odoo_repository_org_oca,queue,https://github.com/OCA/queue,https://github.com/OCA/queue,github -repo_oca_repair,odoo_repository_org_oca,repair,https://github.com/OCA/repair,https://github.com/OCA/repair,github -repo_oca_report_print_send,odoo_repository_org_oca,report-print-send,https://github.com/OCA/report-print-send,https://github.com/OCA/report-print-send,github -repo_oca_reporting_engine,odoo_repository_org_oca,reporting-engine,https://github.com/OCA/reporting-engine,https://github.com/OCA/reporting-engine,github -repo_oca_resource,odoo_repository_org_oca,resource,https://github.com/OCA/resource,https://github.com/OCA/resource,github -repo_oca_rest_api,odoo_repository_org_oca,rest-api,https://github.com/OCA/rest-api,https://github.com/OCA/rest-api,github -repo_oca_rest_framework,odoo_repository_org_oca,rest-framework,https://github.com/OCA/rest-framework,https://github.com/OCA/rest-framework,github -repo_oca_rma,odoo_repository_org_oca,rma,https://github.com/OCA/rma,https://github.com/OCA/rma,github -repo_oca_role_policy,odoo_repository_org_oca,role-policy,https://github.com/OCA/role-policy,https://github.com/OCA/role-policy,github -repo_oca_sale_blanket,odoo_repository_org_oca,sale-blanket,https://github.com/OCA/sale-blanket,https://github.com/OCA/sale-blanket,github -repo_oca_sale_channel,odoo_repository_org_oca,sale-channel,https://github.com/OCA/sale-channel,https://github.com/OCA/sale-channel,github -repo_oca_sale_financial,odoo_repository_org_oca,sale-financial,https://github.com/OCA/sale-financial,https://github.com/OCA/sale-financial,github -repo_oca_sale_promotion,odoo_repository_org_oca,sale-promotion,https://github.com/OCA/sale-promotion,https://github.com/OCA/sale-promotion,github -repo_oca_sale_reporting,odoo_repository_org_oca,sale-reporting,https://github.com/OCA/sale-reporting,https://github.com/OCA/sale-reporting,github -repo_oca_sale_workflow,odoo_repository_org_oca,sale-workflow,https://github.com/OCA/sale-workflow,https://github.com/OCA/sale-workflow,github -repo_oca_search_engine,odoo_repository_org_oca,search-engine,https://github.com/OCA/search-engine,https://github.com/OCA/search-engine,github -repo_oca_server_auth,odoo_repository_org_oca,server-auth,https://github.com/OCA/server-auth,https://github.com/OCA/server-auth,github -repo_oca_server_backend,odoo_repository_org_oca,server-backend,https://github.com/OCA/server-backend,https://github.com/OCA/server-backend,github -repo_oca_server_brand,odoo_repository_org_oca,server-brand,https://github.com/OCA/server-brand,https://github.com/OCA/server-brand,github -repo_oca_server_env,odoo_repository_org_oca,server-env,https://github.com/OCA/server-env,https://github.com/OCA/server-env,github -repo_oca_server_tools,odoo_repository_org_oca,server-tools,https://github.com/OCA/server-tools,https://github.com/OCA/server-tools,github -repo_oca_server_ux,odoo_repository_org_oca,server-ux,https://github.com/OCA/server-ux,https://github.com/OCA/server-ux,github -repo_oca_shift_planning,odoo_repository_org_oca,shift-planning,https://github.com/OCA/shift-planning,https://github.com/OCA/shift-planning,github -repo_oca_shopfloor_app,odoo_repository_org_oca,shopfloor-app,https://github.com/OCA/shopfloor-app,https://github.com/OCA/shopfloor-app -repo_oca_sign,odoo_repository_org_oca,sign,https://github.com/OCA/sign,https://github.com/OCA/sign,github -repo_oca_social,odoo_repository_org_oca,social,https://github.com/OCA/social,https://github.com/OCA/social,github -repo_oca_spreadsheet,odoo_repository_org_oca,spreadsheet,https://github.com/OCA/spreadsheet,https://github.com/OCA/spreadsheet,github -repo_oca_stock_logistics_availability,odoo_repository_org_oca,stock-logistics-availability,https://github.com/OCA/stock-logistics-availability,https://github.com/OCA/stock-logistics-availability,github -repo_oca_stock_logistics_barcode,odoo_repository_org_oca,stock-logistics-barcode,https://github.com/OCA/stock-logistics-barcode,https://github.com/OCA/stock-logistics-barcode,github -repo_oca_stock_logistics_interfaces,odoo_repository_org_oca,stock-logistics-interfaces,https://github.com/OCA/stock-logistics-interfaces,https://github.com/OCA/stock-logistics-interfaces,github -repo_oca_stock_logistics_putaway,odoo_repository_org_oca,stock-logistics-putaway,https://github.com/OCA/stock-logistics-putaway,https://github.com/OCA/stock-logistics-putaway,github -repo_oca_stock_logistics_orderpoint,odoo_repository_org_oca,stock-logistics-orderpoint,https://github.com/OCA/stock-logistics-orderpoint,https://github.com/OCA/stock-logistics-orderpoint,github -repo_oca_stock_logistics_release_channel,odoo_repository_org_oca,stock-logistics-release-channel,https://github.com/OCA/stock-logistics-release-channel,https://github.com/OCA/stock-logistics-release-channel,github -repo_oca_stock_logistics_reporting,odoo_repository_org_oca,stock-logistics-reporting,https://github.com/OCA/stock-logistics-reporting,https://github.com/OCA/stock-logistics-reporting,github -repo_oca_stock_logistics_request,odoo_repository_org_oca,stock-logistics-request,https://github.com/OCA/stock-logistics-request,https://github.com/OCA/stock-logistics-request,github -repo_oca_stock_logistics_reservation,odoo_repository_org_oca,stock-logistics-reservation,https://github.com/OCA/stock-logistics-reservation,https://github.com/OCA/stock-logistics-reservation,github -repo_oca_stock_logistics_shopfloor,odoo_repository_org_oca,stock-logistics-shopfloor,https://github.com/OCA/stock-logistics-shopfloor,https://github.com/OCA/stock-logistics-shopfloor,github -repo_oca_stock_logistics_tracking,odoo_repository_org_oca,stock-logistics-tracking,https://github.com/OCA/stock-logistics-tracking,https://github.com/OCA/stock-logistics-tracking,github -repo_oca_stock_logistics_transport,odoo_repository_org_oca,stock-logistics-transport,https://github.com/OCA/stock-logistics-transport,https://github.com/OCA/stock-logistics-transport,github -repo_oca_stock_logistics_warehouse,odoo_repository_org_oca,stock-logistics-warehouse,https://github.com/OCA/stock-logistics-warehouse,https://github.com/OCA/stock-logistics-warehouse,github -repo_oca_stock_logistics_workflow,odoo_repository_org_oca,stock-logistics-workflow,https://github.com/OCA/stock-logistics-workflow,https://github.com/OCA/stock-logistics-workflow,github -repo_oca_stock_weighing,odoo_repository_org_oca,stock-weighing,https://github.com/OCA/stock-weighing,https://github.com/OCA/stock-weighing,github -repo_oca_storage,odoo_repository_org_oca,storage,https://github.com/OCA/storage,https://github.com/OCA/storage,github -repo_oca_survey,odoo_repository_org_oca,survey,https://github.com/OCA/survey,https://github.com/OCA/survey,github -repo_oca_timesheet,odoo_repository_org_oca,timesheet,https://github.com/OCA/timesheet,https://github.com/OCA/timesheet,github -repo_oca_vertical_abbey,odoo_repository_org_oca,vertical-abbey,https://github.com/OCA/vertical-abbey,https://github.com/OCA/vertical-abbey,github -repo_oca_vertical_agriculture,odoo_repository_org_oca,vertical-agriculture,https://github.com/OCA/vertical-agriculture,https://github.com/OCA/vertical-agriculture,github -repo_oca_vertical_association,odoo_repository_org_oca,vertical-association,https://github.com/OCA/vertical-association,https://github.com/OCA/vertical-association,github -repo_oca_vertical_community,odoo_repository_org_oca,vertical-community,https://github.com/OCA/vertical-community,https://github.com/OCA/vertical-community,github -repo_oca_vertical_construction,odoo_repository_org_oca,vertical-construction,https://github.com/OCA/vertical-construction,https://github.com/OCA/vertical-construction,github -repo_oca_vertical_cooperative_supermarket,odoo_repository_org_oca,vertical-cooperative-supermarket,https://github.com/OCA/vertical-cooperative-supermarket,https://github.com/OCA/vertical-cooperative-supermarket,github -repo_oca_vertical_edition,odoo_repository_org_oca,vertical-edition,https://github.com/OCA/vertical-edition,https://github.com/OCA/vertical-edition,github -repo_oca_vertical_education,odoo_repository_org_oca,vertical-education,https://github.com/OCA/vertical-education,https://github.com/OCA/vertical-education,github -repo_oca_vertical_hotel,odoo_repository_org_oca,vertical-hotel,https://github.com/OCA/vertical-hotel,https://github.com/OCA/vertical-hotel,github -repo_oca_vertical_isp,odoo_repository_org_oca,vertical-isp,https://github.com/OCA/vertical-isp,https://github.com/OCA/vertical-isp,github -repo_oca_vertical_medical,odoo_repository_org_oca,vertical-medical,https://github.com/OCA/vertical-medical,https://github.com/OCA/vertical-medical,github -repo_oca_vertical_ngo,odoo_repository_org_oca,vertical-ngo,https://github.com/OCA/vertical-ngo,https://github.com/OCA/vertical-ngo,github -repo_oca_vertical_realestate,odoo_repository_org_oca,vertical-realestate,https://github.com/OCA/vertical-realestate,https://github.com/OCA/vertical-realestate,github -repo_oca_vertical_rental,odoo_repository_org_oca,vertical-rental,https://github.com/OCA/vertical-rental,https://github.com/OCA/vertical-rental,github -repo_oca_vertical_travel,odoo_repository_org_oca,vertical-travel,https://github.com/OCA/vertical-travel,https://github.com/OCA/vertical-travel,github -repo_oca_wallet,odoo_repository_org_oca,wallet,https://github.com/OCA/wallet,https://github.com/OCA/wallet,github -repo_oca_web,odoo_repository_org_oca,web,https://github.com/OCA/web,https://github.com/OCA/web,github -repo_oca_web_api,odoo_repository_org_oca,web-api,https://github.com/OCA/web-api,https://github.com/OCA/web-api,github -repo_oca_web_api_contrib,odoo_repository_org_oca,web-api-contrib,https://github.com/OCA/web-api-contrib,https://github.com/OCA/web-api-contrib,github -repo_oca_webhook,odoo_repository_org_oca,webhook,https://github.com/OCA/webhook,https://github.com/OCA/webhook,github -repo_oca_webkit_tools,odoo_repository_org_oca,webkit-tools,https://github.com/OCA/webkit-tools,https://github.com/OCA/webkit-tools,github -repo_oca_website,odoo_repository_org_oca,website,https://github.com/OCA/website,https://github.com/OCA/website,github -repo_oca_website_cms,odoo_repository_org_oca,website-cms,https://github.com/OCA/website-cms,https://github.com/OCA/website-cms,github -repo_oca_website_themes,odoo_repository_org_oca,website-themes,https://github.com/OCA/website-themes,https://github.com/OCA/website-themes,github -repo_oca_wms,odoo_repository_org_oca,wms,https://github.com/OCA/wms,https://github.com/OCA/wms,github diff --git a/odoo_repository/data/odoo_mca_backend.xml b/odoo_repository/data/odoo_mca_backend.xml new file mode 100644 index 0000000..ee36ea7 --- /dev/null +++ b/odoo_repository/data/odoo_mca_backend.xml @@ -0,0 +1,8 @@ + + + + + MCA + + diff --git a/odoo_repository/data/queue_job.xml b/odoo_repository/data/queue_job.xml index 2129210..5fd1c93 100644 --- a/odoo_repository/data/queue_job.xml +++ b/odoo_repository/data/queue_job.xml @@ -1,5 +1,6 @@ @@ -53,4 +54,14 @@ + + + _sync_oca_repositories_job + + + + diff --git a/odoo_repository/migrations/16.0.1.5.0/pre-migration.py b/odoo_repository/migrations/16.0.1.5.0/pre-migration.py new file mode 100644 index 0000000..ac69c5b --- /dev/null +++ b/odoo_repository/migrations/16.0.1.5.0/pre-migration.py @@ -0,0 +1,48 @@ +# Copyright 2026 Sébastien Alix +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import logging + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + if not version: + return + remove_xmlids_from_oca_repositories(cr) + + +def remove_xmlids_from_oca_repositories(cr): + """Remove XML-IDs from OCA repositories to prevent deletion during upgrade.""" + _logger.info("Removing XML-IDs from OCA repositories...") + # First, get the OCA organization ID + cr.execute("SELECT id FROM odoo_repository_org WHERE name = 'OCA'") + oca_org_result = cr.fetchone() + if not oca_org_result: + _logger.warning("OCA organization not found in database") + return + oca_org_id = oca_org_result[0] + # Get all OCA repositories that have XML-IDs + cr.execute( + """ + SELECT imd.id, imd.res_id + FROM ir_model_data imd + JOIN odoo_repository repo ON imd.res_id = repo.id + WHERE imd.model = 'odoo.repository' + AND repo.org_id = %s + """, + (oca_org_id,), + ) + xmlid_records = cr.fetchall() + if not xmlid_records: + _logger.info("No OCA repositories with XML-IDs found") + return + # Delete the XML-IDs from ir.model.data + xmlid_ids = [record[0] for record in xmlid_records] + repository_ids = [record[1] for record in xmlid_records] + cr.execute("DELETE FROM ir_model_data WHERE id = ANY(%s)", (xmlid_ids,)) + _logger.info( + "Removed %s XML-IDs from OCA repositories (repository IDs: %s)", + len(xmlid_ids), + repository_ids, + ) diff --git a/odoo_repository/models/__init__.py b/odoo_repository/models/__init__.py index e32af68..6c8e744 100644 --- a/odoo_repository/models/__init__.py +++ b/odoo_repository/models/__init__.py @@ -10,6 +10,7 @@ from . import odoo_python_dependency from . import odoo_repository_org from . import odoo_repository_addons_path +from . import odoo_mca_backend from . import odoo_repository from . import odoo_repository_branch from . import odoo_module_branch diff --git a/odoo_repository/models/odoo_mca_backend.py b/odoo_repository/models/odoo_mca_backend.py new file mode 100644 index 0000000..166a3e4 --- /dev/null +++ b/odoo_repository/models/odoo_mca_backend.py @@ -0,0 +1,17 @@ +# Copyright 2026 Sébastien Alix +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class OdooMCABackend(models.Model): + """Backend collection for Odoo MCA system. + + This backend is used as the collection for all MCA-related components. + """ + + _name = "odoo.mca.backend" + _inherit = "collection.base" + _description = "Odoo MCA Backend" + + name = fields.Char(required=True) diff --git a/odoo_repository/models/odoo_repository.py b/odoo_repository/models/odoo_repository.py index c9e4a01..f8de349 100644 --- a/odoo_repository/models/odoo_repository.py +++ b/odoo_repository/models/odoo_repository.py @@ -1,4 +1,5 @@ # Copyright 2023 Camptocamp SA +# Copyright 2026 Sébastien Alix # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) import json @@ -669,6 +670,36 @@ def unlink(self): rec.branch_ids.module_ids.sudo().unlink() return super().unlink() + @api.model + def cron_sync_oca_repositories(self): + """Create and update OCA repositories from GitHub configuration. + + This method spawns a job to fetch the repository configurations from + OCA/repo-maintainer-conf, parse the YAML files, and synchronize the local + repository database by: + - Creating new repositories that exist in OCA config but not locally + - Updating existing repositories with any changes + - Archiving repositories that no longer exist in OCA config + """ + delayable = self.delayable( + identity_key=identity_exact, + priority=1, + ) + delayable._sync_oca_repositories_job() + delayable.delay() + return True + + @api.model + def _sync_oca_repositories_job(self): + """Queue job method to synchronize OCA repositories. + + Performs the actual synchronization of OCA repositories from GitHub configuration. + """ + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + return synchronizer.run() + def open_modules(self): self.ensure_one() xml_id = "odoo_repository.odoo_module_branch_action" diff --git a/odoo_repository/models/res_config_settings.py b/odoo_repository/models/res_config_settings.py index 50376d7..2f52566 100644 --- a/odoo_repository/models/res_config_settings.py +++ b/odoo_repository/models/res_config_settings.py @@ -1,4 +1,5 @@ # Copyright 2023 Camptocamp SA +# Copyright 2026 Sébastien Alix # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo import fields, models @@ -21,3 +22,11 @@ class ResConfigSettings(models.TransientModel): config_odoo_repository_main_node_url = fields.Char( string="Endpoint URL", config_parameter="odoo_repository_main_node_url" ) + config_odoo_repository_oca_blacklist = fields.Char( + string="OCA Repository Blacklist", + config_parameter="odoo_repository.oca_repo_blacklist", + help=( + "Comma-separated list of OCA repositories to skip during synchronization. " + "Example: OCB,OpenUpgrade" + ), + ) diff --git a/odoo_repository/readme/DESCRIPTION.md b/odoo_repository/readme/DESCRIPTION.md index 2fea33b..fc891ae 100644 --- a/odoo_repository/readme/DESCRIPTION.md +++ b/odoo_repository/readme/DESCRIPTION.md @@ -2,5 +2,5 @@ Base module to host data collected from Odoo repositories. It allows you to: - declare the Odoo versions (last 3 versions by default) -- declare repositories containing modules (Odoo and OCA repositories included by default) +- declare repositories containing modules (Odoo repositories included by default, and OCA ones are synced automatically) - scan these repositories to collect modules informations per Odoo version diff --git a/odoo_repository/security/ir.model.access.csv b/odoo_repository/security/ir.model.access.csv index db75b49..b3b9972 100644 --- a/odoo_repository/security/ir.model.access.csv +++ b/odoo_repository/security/ir.model.access.csv @@ -25,3 +25,4 @@ access_odoo_module_branch_user,odoo_module_branch_user,model_odoo_module_branch, access_odoo_module_branch_manager,odoo_module_branch_manager,model_odoo_module_branch,group_odoo_repository_manager,1,1,0,0 access_odoo_module_branch_version_user,odoo_module_branch_version_user,model_odoo_module_branch_version,group_odoo_repository_user,1,0,0,0 access_odoo_module_branch_version_manager,odoo_module_branch_version_manager,model_odoo_module_branch_version,group_odoo_repository_manager,1,1,1,1 +access_odoo_mca_backend_user,odoo_mca_backend_user,model_odoo_mca_backend,group_odoo_repository_user,1,0,0,0 diff --git a/odoo_repository/tests/__init__.py b/odoo_repository/tests/__init__.py index a806756..8f3f0b6 100644 --- a/odoo_repository/tests/__init__.py +++ b/odoo_repository/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_odoo_repository_scan from . import test_sync_node from . import test_odoo_module_branch +from . import test_oca_repository_synchronizer diff --git a/odoo_repository/tests/test_oca_repository_synchronizer.py b/odoo_repository/tests/test_oca_repository_synchronizer.py new file mode 100644 index 0000000..a6cb684 --- /dev/null +++ b/odoo_repository/tests/test_oca_repository_synchronizer.py @@ -0,0 +1,279 @@ +# Copyright 2026 Sébastien Alix +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from unittest.mock import MagicMock, patch + +from odoo import tools + +from odoo.addons.component.tests.common import TransactionComponentCase +from odoo.addons.queue_job.exception import RetryableJobError + + +class TestOCARepositorySynchronizer(TransactionComponentCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, queue_job__no_delay=True)) + cls.repository_model = cls.env["odoo.repository"] + cls.oca_org = cls.env.ref("odoo_repository.odoo_repository_org_oca") + # Create a test repository that should be archived + cls.test_repo = cls.repository_model.create( + { + "org_id": cls.oca_org.id, + "name": "test-archive-repo", + "repo_url": "https://github.com/OCA/test-archive-repo", + "clone_url": "https://github.com/OCA/test-archive-repo", + "repo_type": "github", + "active": True, + } + ) + + def _mock_requests_get(self, mock_get): + # Mock GitHub API response for directory listing + mock_dir_response = MagicMock() + mock_dir_response.json.return_value = [ + { + "name": "test.yml", + "download_url": ( + "https://raw.githubusercontent.com/" + "OCA/repo-maintainer-conf/master/conf/repo/test.yml" + ), + } + ] + mock_dir_response.raise_for_status.return_value = None + # Mock YAML file content + mock_yaml_response = MagicMock() + mock_yaml_response.text = """ + test-repo: + branches: + - "16.0" + name: Test + """ + mock_yaml_response.raise_for_status.return_value = None + + # Setup mock to return different responses based on URL + def get_side_effect(url, **kwargs): + if "contents/conf/repo" in url: + return mock_dir_response + else: + return mock_yaml_response + + mock_get.side_effect = get_side_effect + + def test_parse_oca_repo_configurations(self): + """Test OCA repository configurations parsing. + + Only those with branches are returned. + """ + yaml_content = """ + test-repo-no-branches: + branches: [] + name: Test Repo + test-repo-with-branches: + branches: + - "16.0" + - "17.0" + name: Test Repo With Branches + """ + repo_configs = {"test.yml": yaml_content} + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + result = synchronizer._parse_oca_repo_configurations(repo_configs) + # Should only include repo with branches + self.assertEqual(len(result), 1) + self.assertNotIn("test-repo-no-branches", result) + self.assertIn("test-repo-with-branches", result) + repo_data = result["test-repo-with-branches"] + self.assertEqual(repo_data["name"], "test-repo-with-branches") + self.assertEqual( + repo_data["repo_url"], "https://github.com/OCA/test-repo-with-branches" + ) + + def test_parse_oca_repo_configurations_default_branch(self): + """Test OCA repository configurations parsing with default_branch filtering. + + Repositories with default_branch='master' or 'main' should be skipped. + """ + yaml_content = """ + repo-master: + default_branch: master + branches: + - "16.0" + name: Master Branch Repo + repo-main: + default_branch: main + branches: + - "16.0" + name: Main Branch Repo + repo-odoo-branch: + default_branch: 16.0 + branches: + - "16.0" + - "17.0" + name: Odoo Branch Repo + repo-no-default: + branches: + - "16.0" + name: No Default Branch Repo + """ + repo_configs = {"test.yml": yaml_content} + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + result = synchronizer._parse_oca_repo_configurations(repo_configs) + # Should only include repos with non-master/main default_branch or no default_branch + self.assertEqual(len(result), 2) + self.assertNotIn("repo-master", result) + self.assertNotIn("repo-main", result) + self.assertIn("repo-odoo-branch", result) + self.assertIn("repo-no-default", result) + + def test_sync_oca_repositories_archive(self): + """Test that repositories not in current config are archived.""" + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + # Mock current repositories (empty = no repos in config) + current_repos = {} + stats = synchronizer._sync_oca_repositories(current_repos) + # Check that our test repo was archived + self.assertEqual(stats["archived"], 1) + self.test_repo.invalidate_model() + self.assertFalse(self.test_repo.active) + self.assertFalse(self.test_repo.to_scan) + + def test_sync_oca_repositories_create(self): + """Test creation of new repositories from config.""" + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + current_repos = { + "new-test-repo": { + "name": "new-test-repo", + "repo_url": "https://github.com/OCA/new-test-repo", + "clone_url": "https://github.com/OCA/new-test-repo", + "repo_type": "github", + } + } + stats = synchronizer._sync_oca_repositories(current_repos) + self.assertEqual(stats["created"], 1) + # Check that repository was created + new_repo = self.repository_model.search([("name", "=", "new-test-repo")]) + self.assertTrue(new_repo.exists()) + self.assertEqual(new_repo.org_id, self.oca_org) + self.assertEqual(new_repo.repo_url, "https://github.com/OCA/new-test-repo") + self.assertTrue(new_repo.active) + self.assertTrue(new_repo.to_scan) + + def test_sync_oca_repositories_update(self): + """Test updating existing repositories.""" + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + current_repos = { + "test-archive-repo": { + "name": "test-archive-repo", + "repo_url": "https://github.com/OCA/test-archive-repo-updated", + "clone_url": "https://github.com/OCA/test-archive-repo-updated", + "repo_type": "github", + } + } + stats = synchronizer._sync_oca_repositories(current_repos) + self.assertEqual(stats["updated"], 1) + # Check that repository was updated + self.test_repo.invalidate_model() + self.assertEqual( + self.test_repo.repo_url, "https://github.com/OCA/test-archive-repo-updated" + ) + self.assertTrue(self.test_repo.active) + + @patch("requests.get") + def test_fetch_oca_repo_configurations(self, mock_get): + """Test fetching repository configuration from GitHub.""" + self._mock_requests_get(mock_get) + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + result = synchronizer._fetch_oca_repo_configurations() + self.assertIn("test.yml", result) + self.assertIn("test-repo:", result["test.yml"]) + + @patch("requests.get") + def test_run_oca_repository_synchronizer(self, mock_get): + """Test running the repository synchronizer component (main entrypoint).""" + self._mock_requests_get(mock_get) + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + stats = synchronizer.run() + self.assertEqual(stats["created"], 1) + self.assertEqual(stats["updated"], 0) + + def test_run_oca_repository_synchronizer_error(self): + """Test running the repository synchronizer component raising exception.""" + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + synchronizer.github_conf_api_url = "wrong_api_url" + with self.assertRaises(RetryableJobError): + with tools.mute_logger( + "odoo.addons.odoo_repository.components.oca_repository_synchronizer" + ): + synchronizer.run() + + @patch("requests.get") + def test_cron_sync_oca_repositories(self, mock_get): + """Test cron method syncing OCA repositories from GitHub.""" + self._mock_requests_get(mock_get) + result = self.repository_model.cron_sync_oca_repositories() + self.assertTrue(result) + test_repo = self.repository_model.search([("name", "=", "test-repo")]) + self.assertEqual(test_repo.name, "test-repo") + self.assertEqual( + test_repo.repo_url, test_repo.clone_url, "https://github.com/OCA/test-repo" + ) + + def test_oca_repo_blacklist_configurable(self): + """Test that OCA repository blacklist is configurable through settings.""" + yaml_content = """ + blacklisted-repo: + branches: + - "16.0" + name: Blacklisted Repo + normal-repo: + branches: + - "16.0" + name: Normal Repo + """ + repo_configs = {"test.yml": yaml_content} + # Test with default blacklist (should skip OCB and OpenUpgrade) + mca_backend = self.env.ref("odoo_repository.mca_backend") + with mca_backend.work_on("odoo.repository") as work: + synchronizer = work.component(usage="oca.repository.synchronizer") + # Test default blacklist + default_blacklist = synchronizer._get_oca_repo_blacklist() + self.assertIn("OCB", default_blacklist) + self.assertIn("OpenUpgrade", default_blacklist) + # Test parsing with custom blacklist (comma-separated) + self.env["ir.config_parameter"].sudo().set_param( + "odoo_repository.oca_repo_blacklist", "OCB,OpenUpgrade,blacklisted-repo" + ) + custom_blacklist = synchronizer._get_oca_repo_blacklist() + self.assertIn("OCB", custom_blacklist) + self.assertIn("OpenUpgrade", custom_blacklist) + self.assertIn("blacklisted-repo", custom_blacklist) + # Test that blacklisted repo is skipped + result = synchronizer._parse_oca_repo_configurations(repo_configs) + self.assertNotIn("blacklisted-repo", result) + self.assertIn("normal-repo", result) + # Test with empty blacklist + self.env["ir.config_parameter"].sudo().set_param( + "odoo_repository.oca_repo_blacklist", "" + ) + empty_blacklist = synchronizer._get_oca_repo_blacklist() + self.assertEqual(empty_blacklist, []) + # Test that both repos are included when blacklist is empty + result = synchronizer._parse_oca_repo_configurations(repo_configs) + self.assertIn("blacklisted-repo", result) + self.assertIn("normal-repo", result) diff --git a/odoo_repository/tests/test_odoo_module_branch.py b/odoo_repository/tests/test_odoo_module_branch.py index c0424d7..096b027 100644 --- a/odoo_repository/tests/test_odoo_module_branch.py +++ b/odoo_repository/tests/test_odoo_module_branch.py @@ -1,4 +1,5 @@ # Copyright 2024 Camptocamp SA +# Copyright 2026 Sébastien Alix # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo.exceptions import ValidationError diff --git a/odoo_repository/views/res_config_settings.xml b/odoo_repository/views/res_config_settings.xml index 2bd509b..b10bd97 100644 --- a/odoo_repository/views/res_config_settings.xml +++ b/odoo_repository/views/res_config_settings.xml @@ -1,5 +1,6 @@ @@ -95,6 +96,26 @@ +
+
+ OCA Repository Blacklist +
+ Comma-separated list of OCA repositories to skip during synchronization. +
+
+
+ +
+
+
+
diff --git a/requirements.txt b/requirements.txt index 82a7323..977fea3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ gitpython oca-port odoo-addons-parser +pyyaml From 3f0022688aae15c66a072effe7576764dbfdc654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alix?= Date: Mon, 16 Feb 2026 08:54:28 +0100 Subject: [PATCH 2/2] [FIX] odoo_repository_migration: fix tests OCA repositories do not have external IDs anymore. --- .../tests/test_odoo_module_branch.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/odoo_repository_migration/tests/test_odoo_module_branch.py b/odoo_repository_migration/tests/test_odoo_module_branch.py index b5b3835..8da7cc4 100644 --- a/odoo_repository_migration/tests/test_odoo_module_branch.py +++ b/odoo_repository_migration/tests/test_odoo_module_branch.py @@ -1,4 +1,5 @@ # Copyright 2024 Camptocamp SA +# Copyright 2026 Sébastien Alix # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from odoo.addons.odoo_repository.tests import common @@ -22,7 +23,14 @@ def setUp(self): last_scanned_commit="sha", ) self.std_repository = self.env.ref("odoo_repository.odoo_repository_odoo_odoo") - self.oca_repository = self.env.ref("odoo_repository.repo_oca_server_tools") + oca_org = self.env.ref("odoo_repository.odoo_repository_org_oca") + self.oca_repository = self.env["odoo.repository"].create( + { + "org_id": oca_org.id, + "name": "test-repo", + "repo_url": "https://github.com/OCA/test-repo", + } + ) self.gen_repository = self.env["odoo.repository"].create( { "name": "new_repo",