Skip to content
Merged
6 changes: 1 addition & 5 deletions odoo_project_migration/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

===========================
Odoo Project Migration Data
===========================
Expand All @@ -17,7 +13,7 @@ Odoo Project Migration Data
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmodule--composition--analysis-lightgray.png?logo=github
Expand Down
5 changes: 4 additions & 1 deletion odoo_project_migration/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# Copyright 2023 Camptocamp SA
# Copyright 2026 Akretion (https://www.akretion.com).
# @author Sébastien Alix <sebastien.alix@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
{
"name": "Odoo Project Migration Data",
"summary": "Analyze your Odoo project migrations.",
"version": "18.0.1.0.0",
"version": "18.0.1.1.0",
"category": "Tools",
"author": "Camptocamp, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/module-composition-analysis",
"data": [
"security/ir.model.access.csv",
"views/odoo_module_branch_timeline.xml",
"views/odoo_module_branch_migration.xml",
"views/odoo_project.xml",
"views/odoo_project_module_migration.xml",
Expand Down
26 changes: 26 additions & 0 deletions odoo_project_migration/migrations/18.0.1.1.0/post-migrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2026 Akretion (https://www.akretion.com).
# @author Sébastien Alix <sebastien.alix@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

import logging

from odoo import SUPERUSER_ID, api

_logger = logging.getLogger(__name__)


def migrate(cr, version):
if not version:
return
env = api.Environment(cr, SUPERUSER_ID, {})
recompute_timeline_project_migration_data(env)


def recompute_timeline_project_migration_data(env):
"""Recompute project migration data related to timelines."""
timelines = env["odoo.module.branch.timeline"].search([])
_logger.info(
"Recompute project migration data related to %s timelines...", len(timelines)
)
timelines.migration_ids.force_update()
timelines.project_migration_ids.force_update()
2 changes: 2 additions & 0 deletions odoo_project_migration/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from . import odoo_module_branch
from . import odoo_module_branch_timeline
from . import odoo_module_branch_migration
from . import odoo_project_module
from . import odoo_project_module_migration
Expand Down
33 changes: 33 additions & 0 deletions odoo_project_migration/models/odoo_module_branch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2026 Akretion (https://www.akretion.com).
# @author Sébastien Alix <sebastien.alix@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import models


class OdooModuleBranch(models.Model):
_inherit = "odoo.module.branch"

def _update_migration_data(self):
# Override to refresh project migration data as well
res = super()._update_migration_data()
for rec in self:
timelines = rec._get_related_timelines()
impacted_modules = rec.module_id | timelines._get_all_impacted_modules()
migrations = self.env["odoo.project.module.migration"].search(
[
("module_id", "in", impacted_modules.ids),
(
"migration_path_id.source_branch_id.sequence",
"<=",
rec.branch_id.sequence,
),
(
"migration_path_id.target_branch_id.sequence",
">=",
rec.branch_id.sequence,
),
]
)
migrations.force_update()
return res
17 changes: 16 additions & 1 deletion odoo_project_migration/models/odoo_module_branch_migration.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
# Copyright 2025 Camptocamp SA
# Copyright 2026 Akretion (https://www.akretion.com).
# @author Sébastien Alix <sebastien.alix@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import fields, models
from odoo import _, fields, models


class OdooModuleBranchMigration(models.Model):
_inherit = "odoo.module.branch.migration"

odoo_project_ids = fields.Many2many(related="module_branch_id.odoo_project_ids")
odoo_project_module_migration_ids = fields.One2many(
comodel_name="odoo.project.module.migration",
inverse_name="module_migration_id",
string="Project Migrations",
)

def open_project_migrations(self):
self.ensure_one()
xml_id = "odoo_project_migration.odoo_project_module_migration_action"
action = self.env["ir.actions.actions"]._for_xml_id(xml_id)
action["name"] = _("Project migrations")
action["domain"] = [("id", "in", self.odoo_project_module_migration_ids.ids)]
return action
77 changes: 77 additions & 0 deletions odoo_project_migration/models/odoo_module_branch_timeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2026 Akretion (https://www.akretion.com).
# @author Sébastien Alix <sebastien.alix@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import _, api, fields, models


class OdooModuleBranchTimeline(models.Model):
_inherit = "odoo.module.branch.timeline"

project_migration_ids = fields.Many2many(
comodel_name="odoo.project.module.migration",
string="Project Migrations",
compute="_compute_project_migration_ids",
)

@api.depends("module_branch_id", "next_module_id", "odoo_version_id")
def _compute_project_migration_ids(self):
for rec in self:
impacted_modules = rec._get_all_impacted_modules()
rec.project_migration_ids = (
self.env["odoo.project.module.migration"]
.search(
[
(
"migration_path_id.target_branch_id.sequence",
">=",
rec.odoo_version_id.sequence,
),
"|",
"|",
("module_id", "in", impacted_modules.ids),
("renamed_to_module_id", "in", impacted_modules.ids),
("replaced_by_module_id", "in", impacted_modules.ids),
]
)
.sudo()
)

# Update relevant project migration records on timeline update.
#
# When a module is renamed or replaced, we refresh all impacted project
# migration data records as done in 'odoo_repository_migration' module for
# 'odoo.module.branch.migration' records.
#
# E.g.
# if a module on 17.0 is set as renamed starting from 18.0, existing
# project migration data (17.0 -> 18.0, or 17.0 -> 19.0) will be aware
# of such change even if migration data are not collected on source
# repository.

@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
records.project_migration_ids.force_update()
return records

@api.model
def write(self, vals):
project_migrations = self.project_migration_ids
res = super().write(vals)
(self.project_migration_ids | project_migrations).force_update()
return res

def unlink(self):
project_migrations = self.project_migration_ids
res = super().unlink()
project_migrations.force_update()
return res

def open_project_migrations(self):
self.ensure_one()
xml_id = "odoo_project_migration.odoo_project_module_migration_action"
action = self.env["ir.actions.actions"]._for_xml_id(xml_id)
action["name"] = _("Project Migrations")
action["domain"] = [("id", "in", self.project_migration_ids.ids)]
return action
69 changes: 60 additions & 9 deletions odoo_project_migration/models/odoo_project_module_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ class OdooProjectModuleMigration(models.Model):
),
compute="_compute_migration_script_ids",
)
renamed_to_module_id = fields.Many2one(
comodel_name="odoo.module",
compute="_compute_renamed_to_module_id",
string="Renamed to",
store=True,
index=True,
)
replaced_by_module_id = fields.Many2one(
comodel_name="odoo.module",
compute="_compute_replaced_by_module_id",
string="Replaced by",
store=True,
index=True,
)
state = fields.Selection(
# Same as in 'odoo.module.branch.migration' but set a state even for
# modules with no migration data, could be Odoo S.A. std modules
Expand Down Expand Up @@ -124,16 +138,16 @@ def _compute_project_module_id(self):
@api.depends(
"source_module_branch_id",
"migration_path_id",
"module_migration_id.replaced_by_module_id",
"module_migration_id.renamed_to_module_id",
"replaced_by_module_id",
"renamed_to_module_id",
)
def _compute_target_module_branch_id(self):
module_branch_model = self.env["odoo.module.branch"]
for rec in self:
# Look for the right module technical name
module = (
rec.module_migration_id.replaced_by_module_id
or rec.module_migration_id.renamed_to_module_id
rec.replaced_by_module_id
or rec.renamed_to_module_id
or rec.source_module_branch_id.module_id
)
rec.target_module_branch_id = module_branch_model._find(
Expand All @@ -145,9 +159,6 @@ def _compute_target_module_branch_id(self):

# NOTE: 'migration_scan' is here to re-trigger the computation
# each time the source module has its state updated regarding migration.
# FIXME: this could trigger too much computations on irrelevant records
# (one not related to the updated migration path), we should switch to
# component events to handle such cases.
@api.depends("migration_path_id", "source_module_branch_id.migration_scan")
def _compute_module_migration_id(self):
migration_model = self.env["odoo.module.branch.migration"]
Expand Down Expand Up @@ -186,10 +197,41 @@ def _compute_migration_script_ids(self):
).sorted(key=lambda v: (v.branch_sequence, v.sequence))
rec.migration_script_ids = current_release_versions | new_release_versions

@api.depends("module_migration_id.state")
# NOTE: _renamed_to_module_in_target_version relies also on timelines, and
# this computed field will be recomputed automatically on timeline updates.
@api.depends("migration_path_id")
def _compute_renamed_to_module_id(self):
for rec in self:
rec.renamed_to_module_id = (
rec.source_module_branch_id._renamed_to_module_in_target_version(
rec.migration_path_id.target_branch_id
)
)

# NOTE: _replaced_by_module_in_target_version relies also on timelines, and
# this computed field will be recomputed automatically on timeline updates.
@api.depends("migration_path_id")
def _compute_replaced_by_module_id(self):
for rec in self:
rec.replaced_by_module_id = (
rec.source_module_branch_id._replaced_by_module_in_target_version(
rec.migration_path_id.target_branch_id
)
)

@api.depends(
"module_migration_id.state",
"replaced_by_module_id",
"source_module_branch_id.is_standard",
"target_module_branch_id.repository_branch_id",
)
def _compute_state(self):
for rec in self:
rec.state = rec.module_migration_id.state
if rec.replaced_by_module_id:
# Module replaced by another one
rec.state = "replaced"
continue
if not rec.module_migration_id:
# Default state (used by project specific modules)
rec.state = "migrate"
Expand All @@ -198,6 +240,15 @@ def _compute_state(self):
rec.state = (
"available" if rec.target_module_branch_id else "removed"
)
elif rec.target_module_branch_id:
elif rec.target_module_branch_id.repository_branch_id:
# repo with collect_migration_data = False
rec.state = "available"

def force_update(self):
"""Ensure the project migration data are updated."""
self_sudo = self.sudo()
self_sudo._compute_module_migration_id()
self_sudo._compute_replaced_by_module_id()
self_sudo._compute_renamed_to_module_id()
self_sudo._compute_target_module_branch_id()
self_sudo._compute_state()
24 changes: 9 additions & 15 deletions odoo_project_migration/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<title>Odoo Project Migration Data</title>
<style type="text/css">

/*
Expand Down Expand Up @@ -360,21 +360,16 @@
</style>
</head>
<body>
<div class="document">
<div class="document" id="odoo-project-migration-data">
<h1 class="title">Odoo Project Migration Data</h1>


<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="odoo-project-migration-data">
<h1>Odoo Project Migration Data</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:78d9eb31bf1d0a331277dd4a82234922195c846f027f1049a2d44eede126279a
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/module-composition-analysis/tree/18.0/odoo_project_migration"><img alt="OCA/module-composition-analysis" src="https://img.shields.io/badge/github-OCA%2Fmodule--composition--analysis-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/module-composition-analysis-18-0/module-composition-analysis-18-0-odoo_project_migration"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/module-composition-analysis&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/module-composition-analysis/tree/18.0/odoo_project_migration"><img alt="OCA/module-composition-analysis" src="https://img.shields.io/badge/github-OCA%2Fmodule--composition--analysis-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/module-composition-analysis-18-0/module-composition-analysis-18-0-odoo_project_migration"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/module-composition-analysis&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module integrates the migration data collected by
<tt class="docutils literal">odoo_repository_migration</tt> module in your Odoo projects. It allows to
generate migration reports, giving some hints about the effort to
Expand All @@ -392,23 +387,23 @@ <h1>Odoo Project Migration Data</h1>
</ul>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-1">Bug Tracker</a></h2>
<h1><a class="toc-backref" href="#toc-entry-1">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/module-composition-analysis/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/module-composition-analysis/issues/new?body=module:%20odoo_project_migration%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-2">Credits</a></h2>
<h1><a class="toc-backref" href="#toc-entry-2">Credits</a></h1>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-3">Authors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-3">Authors</a></h2>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-4">Contributors</a></h3>
<h2><a class="toc-backref" href="#toc-entry-4">Contributors</a></h2>
<ul class="simple">
<li>Camptocamp<ul>
<li>Sébastien Alix &lt;<a class="reference external" href="mailto:seb&#64;usr-src.org">seb&#64;usr-src.org</a>&gt;</li>
Expand All @@ -417,7 +412,7 @@ <h3><a class="toc-backref" href="#toc-entry-4">Contributors</a></h3>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h3>
<h2><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand All @@ -430,6 +425,5 @@ <h3><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h3>
</div>
</div>
</div>
</div>
</body>
</html>
Loading
Loading