From c907fc792b8ebd85845c07dde545e4ab779077f1 Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Thu, 9 Nov 2023 00:36:33 +0100 Subject: [PATCH 01/92] 17.0 - Initial setup --- .github/workflows/documentation-commit.yml | 66 ++++++++++ .github/workflows/test-migration.yml | 143 +++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 .github/workflows/documentation-commit.yml create mode 100644 .github/workflows/test-migration.yml diff --git a/.github/workflows/documentation-commit.yml b/.github/workflows/documentation-commit.yml new file mode 100644 index 000000000000..7438211c4abc --- /dev/null +++ b/.github/workflows/documentation-commit.yml @@ -0,0 +1,66 @@ +# On each push in 16.0 branch, +# AND if the coverage file changed, +# build documentation branch and commit the changes +# so that the changes are visible on the website +# https://oca.github.io/OpenUpgrade/ + +name: Build and commit documentation + +on: + push: + paths: ["docsource/modules150-160.rst"] + +jobs: + documentation-commit: + runs-on: ubuntu-latest + steps: + - name: Check out OpenUpgrade Documentation + uses: actions/checkout@v2 + with: + ref: "documentation" + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Check out Odoo + uses: actions/checkout@v2 + with: + repository: odoo/odoo + ref: "16.0" + fetch-depth: 1 + path: odoo + - name: Configuration + run: | + sudo apt update + sudo apt install \ + expect \ + expect-dev \ + libevent-dev \ + libldap2-dev \ + libsasl2-dev \ + libxml2-dev \ + libxslt1-dev \ + nodejs \ + python3-lxml \ + python3-passlib \ + python3-psycopg2 \ + python3-serial \ + python3-simplejson \ + python3-werkzeug \ + python3-yaml \ + unixodbc-dev + - name: Requirements Installation + run: | + pip install -q -r odoo/requirements.txt + pip install -r ./requirements.txt + - name: OpenUpgrade Docs + run: | + # try to build the documentation + sh ./build_openupgrade_docs + - name: Commit changes + uses: EndBug/add-and-commit@v9 + with: + add: "docs" + default_author: github_actions + message: "[UPD] HTML documentation" diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml new file mode 100644 index 000000000000..097177f1e859 --- /dev/null +++ b/.github/workflows/test-migration.yml @@ -0,0 +1,143 @@ +# This workflow will install Python dependencies, run tests and lint with a +# single version of Python. For more information see: +# https://help.github.com/actions/language-and-framework-guides\ +# /using-python-with-github-actions + +name: Test OpenUpgrade migration + +on: + push: + branches: ["16.0*"] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + env: + DB: "openupgrade" + DB_HOST: "localhost" + DB_PASSWORD: "odoo" + DB_PORT: 5432 + DB_USERNAME: "odoo" + DOWNLOADS: https://github.com/OCA/OpenUpgrade/releases/download/databases + ODOO: "./odoo/odoo-bin" + PGHOST: "localhost" + PGPASSWORD: "odoo" + PGUSER: "odoo" + OPENUPGRADE_USE_DEMO: "yes" + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Configure Postgres + uses: harmon758/postgresql-action@v1 + with: + postgresql version: "14" + postgresql user: ${DB_USERNAME} + postgresql password: ${DB_PASSWORD} + - name: Wait / Sleep + uses: jakejarvis/wait-action@v0.1.0 + with: + time: "10s" + - name: DB Creation + run: createdb $DB + - name: DB Restore + run: | + wget -q -O- $DOWNLOADS/15.0.psql | pg_restore -d $DB --no-owner + - name: Check out Odoo + uses: actions/checkout@v2 + with: + repository: odoo/odoo + ref: "16.0" + fetch-depth: 1 + path: odoo + - name: Check out previous Odoo + uses: actions/checkout@v2 + with: + repository: odoo/odoo + ref: "15.0" + fetch-depth: 1 + path: odoo-old + - name: Check out OpenUpgrade + uses: actions/checkout@v2 + with: + path: openupgrade + - name: Configuration + run: | + sudo apt update + sudo apt install \ + expect \ + expect-dev \ + libevent-dev \ + libldap2-dev \ + libsasl2-dev \ + libxml2-dev \ + libxslt1-dev \ + nodejs \ + python3-lxml \ + python3-passlib \ + python3-psycopg2 \ + python3-serial \ + python3-simplejson \ + python3-werkzeug \ + python3-yaml \ + unixodbc-dev + - name: Requirements Installation + run: | + pip install -q -r odoo/requirements.txt + pip install -r ./openupgrade/requirements.txt + # this is for v15 l10n_eg_edi_eta which crashes without it + pip install asn1crypto + - name: Test data + run: | + for snippet in openupgrade/openupgrade_scripts/scripts/*/*/tests/data*.py; do + odoo-old/odoo-bin shell -d $DB < $snippet + done + - name: OpenUpgrade test + run: | + # select modules and perform the upgrade + MODULES_OLD=$(\ + sed -n '/^+========/,$p' \ + openupgrade/docsource/modules150-160.rst \ + | grep "Done\|Partial\|Nothing" \ + | grep -v "theme_" \ + | sed -rn 's/((^\| *\|del\| *)|^\| *)([0-9a-z_]*)[ \|].*/\3/g p' \ + | sed '/^\s*$/d' \ + | paste -d, -s) + MODULES_NEW=$(\ + sed -n '/^+========/,$p' \ + openupgrade/docsource/modules150-160.rst \ + | grep "Done\|Partial\|Nothing" \ + | grep -v "theme_" \ + | sed -rn 's/((^\| *\|new\| *)|^\| *)([0-9a-z_]*)[ \|].*/\3/g p' \ + | sed '/^\s*$/d' \ + | paste -d, -s) + if [ -z "$MODULES_NEW" ]; then + echo "No modules to test yet" + exit + fi + REQUEST="update ir_module_module set state='uninstalled' \ + where name not in ('$(echo $MODULES_OLD | sed -e "s/,/','/g")')" + echo Set the modules as not installable if they are not in the following list : $MODULES_OLD + echo Running $REQUEST + psql $DB -c "$REQUEST" + ADDONS_PATHS="\ + $GITHUB_WORKSPACE/odoo/addons \ + $GITHUB_WORKSPACE/odoo/odoo/addons \ + $GITHUB_WORKSPACE/openupgrade" + echo Execution of Openupgrade with the update of the following modules : $MODULES_NEW + # Silence redundant logs from unlinking records (1 line is enough) + # to prevent log overflow + OPENUPGRADE_TESTS=1 $ODOO \ + --addons-path=`echo $ADDONS_PATHS | awk -v OFS="," '$1=$1'` \ + --database=$DB \ + --db_host=$DB_HOST \ + --db_password=$DB_PASSWORD \ + --db_port=$DB_PORT \ + --db_user=$DB_USERNAME \ + --load=base,web,openupgrade_framework \ + --log-handler odoo.models.unlink:WARNING \ + --test-enable \ + --stop-after-init \ + --update=$MODULES_NEW From 488b0c08c537b6040bdb969bac4b15fd00b0579b Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Thu, 9 Nov 2023 00:54:17 +0100 Subject: [PATCH 02/92] [INIT] Adapt to V17 - Apply new copier template. (from v1.14.2 to v1.18 : replace setup by pyproject.toml, replace black by ruff, ...) - Disable .github/workflows/test.yml because the openupgrade is not initialized yet - add new empty modules160-170.rst file --- .github/workflows/documentation-commit.yml | 6 +++--- .github/workflows/test-migration.yml | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/documentation-commit.yml b/.github/workflows/documentation-commit.yml index 7438211c4abc..721721ef8b27 100644 --- a/.github/workflows/documentation-commit.yml +++ b/.github/workflows/documentation-commit.yml @@ -1,4 +1,4 @@ -# On each push in 16.0 branch, +# On each push in 17.0 branch, # AND if the coverage file changed, # build documentation branch and commit the changes # so that the changes are visible on the website @@ -8,7 +8,7 @@ name: Build and commit documentation on: push: - paths: ["docsource/modules150-160.rst"] + paths: ["docsource/modules160-170.rst"] jobs: documentation-commit: @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v2 with: repository: odoo/odoo - ref: "16.0" + ref: "17.0" fetch-depth: 1 path: odoo - name: Configuration diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 097177f1e859..6f58dffe85cc 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -6,9 +6,9 @@ name: Test OpenUpgrade migration on: - push: - branches: ["16.0*"] - pull_request: + # push: + # branches: ["17.0*"] + # pull_request: jobs: test: @@ -44,19 +44,19 @@ jobs: run: createdb $DB - name: DB Restore run: | - wget -q -O- $DOWNLOADS/15.0.psql | pg_restore -d $DB --no-owner + wget -q -O- $DOWNLOADS/16.0.psql | pg_restore -d $DB --no-owner - name: Check out Odoo uses: actions/checkout@v2 with: repository: odoo/odoo - ref: "16.0" + ref: "17.0" fetch-depth: 1 path: odoo - name: Check out previous Odoo uses: actions/checkout@v2 with: repository: odoo/odoo - ref: "15.0" + ref: "16.0" fetch-depth: 1 path: odoo-old - name: Check out OpenUpgrade @@ -87,7 +87,7 @@ jobs: run: | pip install -q -r odoo/requirements.txt pip install -r ./openupgrade/requirements.txt - # this is for v15 l10n_eg_edi_eta which crashes without it + # this is for v16 l10n_eg_edi_eta which crashes without it pip install asn1crypto - name: Test data run: | @@ -99,7 +99,7 @@ jobs: # select modules and perform the upgrade MODULES_OLD=$(\ sed -n '/^+========/,$p' \ - openupgrade/docsource/modules150-160.rst \ + openupgrade/docsource/modules160-170.rst \ | grep "Done\|Partial\|Nothing" \ | grep -v "theme_" \ | sed -rn 's/((^\| *\|del\| *)|^\| *)([0-9a-z_]*)[ \|].*/\3/g p' \ @@ -107,7 +107,7 @@ jobs: | paste -d, -s) MODULES_NEW=$(\ sed -n '/^+========/,$p' \ - openupgrade/docsource/modules150-160.rst \ + openupgrade/docsource/modules160-170.rst \ | grep "Done\|Partial\|Nothing" \ | grep -v "theme_" \ | sed -rn 's/((^\| *\|new\| *)|^\| *)([0-9a-z_]*)[ \|].*/\3/g p' \ From 085605163ec073d0a89df87e0a8bdf8732bfd64a Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Thu, 9 Nov 2023 00:54:17 +0100 Subject: [PATCH 03/92] [INIT] Realy disable .github/workflows/test.yml because the openupgrade is not initialized yet --- .github/workflows/test-migration.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 6f58dffe85cc..cf8470221c88 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -6,9 +6,8 @@ name: Test OpenUpgrade migration on: - # push: - # branches: ["17.0*"] - # pull_request: + push: + branches: ["18.0*"] jobs: test: From 3efc2eadabb3eb7f3d211e5eb3725d4f9014fac7 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Fri, 2 Feb 2024 20:26:00 +0100 Subject: [PATCH 04/92] [MIG] openupgrade_scripts: scaffolding the 17.0 module --- openupgrade_scripts/README.rst | 88 ++++ openupgrade_scripts/__init__.py | 0 openupgrade_scripts/__manifest__.py | 20 + openupgrade_scripts/pyproject.toml | 3 + openupgrade_scripts/readme/CONFIGURE.md | 11 + openupgrade_scripts/readme/DESCRIPTION.md | 2 + openupgrade_scripts/readme/INSTALL.md | 2 + .../static/description/banner.png | Bin 0 -> 26859 bytes .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 430 ++++++++++++++++++ 10 files changed, 556 insertions(+) create mode 100644 openupgrade_scripts/README.rst create mode 100644 openupgrade_scripts/__init__.py create mode 100644 openupgrade_scripts/__manifest__.py create mode 100644 openupgrade_scripts/pyproject.toml create mode 100644 openupgrade_scripts/readme/CONFIGURE.md create mode 100644 openupgrade_scripts/readme/DESCRIPTION.md create mode 100644 openupgrade_scripts/readme/INSTALL.md create mode 100644 openupgrade_scripts/static/description/banner.png create mode 100644 openupgrade_scripts/static/description/icon.png create mode 100644 openupgrade_scripts/static/description/index.html diff --git a/openupgrade_scripts/README.rst b/openupgrade_scripts/README.rst new file mode 100644 index 000000000000..e73fb2c9f8fc --- /dev/null +++ b/openupgrade_scripts/README.rst @@ -0,0 +1,88 @@ +=================== +Openupgrade Scripts +=================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:1e4e622a463d6aaf9a8985ff9e4dfd399e5d0d34cf47c1513ded9740a7925a43 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/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%2Fopenupgrade-lightgray.png?logo=github + :target: https://github.com/OCA/openupgrade/tree/17.0/openupgrade_scripts + :alt: OCA/openupgrade +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/openupgrade-17-0/openupgrade-17-0-openupgrade_scripts + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/openupgrade&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module is a containers of migration script to migrate from 16.0 to +17.0 version. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +This module does not need to be installed on a database. It simply needs +to be available via your ``addons-path``. + +Configuration +============= + +- call your odoo instance with the option + ``--upgrade-path=/PATH_TO_openupgrade_scripts_MODULE/scripts/`` + +or + +- add the key to your configuration file: + +.. code:: shell + + [options] + upgrade_path = /PATH_TO_openupgrade_scripts_MODULE/scripts/ + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +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 +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/openupgrade `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/openupgrade_scripts/__init__.py b/openupgrade_scripts/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openupgrade_scripts/__manifest__.py b/openupgrade_scripts/__manifest__.py new file mode 100644 index 000000000000..6e920dfff450 --- /dev/null +++ b/openupgrade_scripts/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright Odoo Community Association (OCA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Openupgrade Scripts", + "summary": """Module that contains all the migrations analysis + and scripts for migrate Odoo SA modules.""", + "author": "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/OpenUpgrade", + "category": "Migration", + "version": "17.0.1.0.0", + "license": "AGPL-3", + "depends": ["base"], + "images": ["static/description/banner.jpg"], + "external_dependencies": { + "python": [ + "openupgradelib @ git+https://github.com/OCA/openupgradelib.git@master" + ] + }, + "installable": True, +} diff --git a/openupgrade_scripts/pyproject.toml b/openupgrade_scripts/pyproject.toml new file mode 100644 index 000000000000..4231d0cccb3d --- /dev/null +++ b/openupgrade_scripts/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/openupgrade_scripts/readme/CONFIGURE.md b/openupgrade_scripts/readme/CONFIGURE.md new file mode 100644 index 000000000000..102ae2481b2f --- /dev/null +++ b/openupgrade_scripts/readme/CONFIGURE.md @@ -0,0 +1,11 @@ +- call your odoo instance with the option + `--upgrade-path=/PATH_TO_openupgrade_scripts_MODULE/scripts/` + +or + +- add the key to your configuration file: + +``` shell +[options] +upgrade_path = /PATH_TO_openupgrade_scripts_MODULE/scripts/ +``` diff --git a/openupgrade_scripts/readme/DESCRIPTION.md b/openupgrade_scripts/readme/DESCRIPTION.md new file mode 100644 index 000000000000..ed2d25468b08 --- /dev/null +++ b/openupgrade_scripts/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module is a containers of migration script to migrate from 16.0 to +17.0 version. diff --git a/openupgrade_scripts/readme/INSTALL.md b/openupgrade_scripts/readme/INSTALL.md new file mode 100644 index 000000000000..471a1d162ce1 --- /dev/null +++ b/openupgrade_scripts/readme/INSTALL.md @@ -0,0 +1,2 @@ +This module does not need to be installed on a database. It simply needs +to be available via your `addons-path`. diff --git a/openupgrade_scripts/static/description/banner.png b/openupgrade_scripts/static/description/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..9dd67ec04cf40822d9d976b548273f4472b83cea GIT binary patch literal 26859 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/openupgrade_scripts/static/description/index.html b/openupgrade_scripts/static/description/index.html new file mode 100644 index 000000000000..d63ff6e71f69 --- /dev/null +++ b/openupgrade_scripts/static/description/index.html @@ -0,0 +1,430 @@ + + + + + + +Openupgrade Scripts + + + +
+

Openupgrade Scripts

+ + +

Beta License: AGPL-3 OCA/openupgrade Translate me on Weblate Try me on Runboat

+

This module is a containers of migration script to migrate from 16.0 to +17.0 version.

+

Table of contents

+ +
+

Installation

+

This module does not need to be installed on a database. It simply needs +to be available via your addons-path.

+
+
+

Configuration

+
    +
  • call your odoo instance with the option +--upgrade-path=/PATH_TO_openupgrade_scripts_MODULE/scripts/
  • +
+

or

+
    +
  • add the key to your configuration file:
  • +
+
+[options]
+upgrade_path = /PATH_TO_openupgrade_scripts_MODULE/scripts/
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +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 +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/openupgrade project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + From 5544580d25a796783216c46c22b96048ac904029 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 27 Feb 2024 18:59:06 +0000 Subject: [PATCH 05/92] [BOT] post-merge updates --- openupgrade_scripts/README.rst | 18 +++++++++--------- .../static/description/index.html | 11 +++++------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/openupgrade_scripts/README.rst b/openupgrade_scripts/README.rst index e73fb2c9f8fc..c94d1ca26fc8 100644 --- a/openupgrade_scripts/README.rst +++ b/openupgrade_scripts/README.rst @@ -7,7 +7,7 @@ Openupgrade Scripts !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:1e4e622a463d6aaf9a8985ff9e4dfd399e5d0d34cf47c1513ded9740a7925a43 + !! source digest: sha256:a87d5b3cc86db365a3fd1ffce8f80833105ddbf1ad7b8f9318ebc8cf6cdf5a40 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -16,14 +16,14 @@ Openupgrade Scripts .. |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%2Fopenupgrade-lightgray.png?logo=github - :target: https://github.com/OCA/openupgrade/tree/17.0/openupgrade_scripts - :alt: OCA/openupgrade +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2FOpenUpgrade-lightgray.png?logo=github + :target: https://github.com/OCA/OpenUpgrade/tree/17.0/openupgrade_scripts + :alt: OCA/OpenUpgrade .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/openupgrade-17-0/openupgrade-17-0-openupgrade_scripts + :target: https://translation.odoo-community.org/projects/OpenUpgrade-17-0/OpenUpgrade-17-0-openupgrade_scripts :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/openupgrade&target_branch=17.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/OpenUpgrade&target_branch=17.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -60,10 +60,10 @@ or Bug Tracker =========== -Bugs are tracked on `GitHub Issues `_. +Bugs are tracked on `GitHub Issues `_. 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -83,6 +83,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/openupgrade `_ project on GitHub. +This module is part of the `OCA/OpenUpgrade `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/openupgrade_scripts/static/description/index.html b/openupgrade_scripts/static/description/index.html index d63ff6e71f69..15e7adb4d8ff 100644 --- a/openupgrade_scripts/static/description/index.html +++ b/openupgrade_scripts/static/description/index.html @@ -1,4 +1,3 @@ - @@ -367,9 +366,9 @@

Openupgrade Scripts

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:1e4e622a463d6aaf9a8985ff9e4dfd399e5d0d34cf47c1513ded9740a7925a43 +!! source digest: sha256:a87d5b3cc86db365a3fd1ffce8f80833105ddbf1ad7b8f9318ebc8cf6cdf5a40 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/openupgrade Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

This module is a containers of migration script to migrate from 16.0 to 17.0 version.

Table of contents

@@ -406,10 +405,10 @@

Configuration

Bug Tracker

-

Bugs are tracked on GitHub Issues. +

Bugs are tracked on GitHub Issues. 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 -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -421,7 +420,7 @@

Maintainers

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

-

This module is part of the OCA/openupgrade project on GitHub.

+

This module is part of the OCA/OpenUpgrade project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

From 9061ffdd8b5273307e4b398d2fdc0a028eed413c Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Wed, 28 Feb 2024 10:54:07 +0100 Subject: [PATCH 06/92] [FIX] define openupgradelib git url in test-requirements.txt --- openupgrade_scripts/__manifest__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openupgrade_scripts/__manifest__.py b/openupgrade_scripts/__manifest__.py index 6e920dfff450..7d52e6b74a43 100644 --- a/openupgrade_scripts/__manifest__.py +++ b/openupgrade_scripts/__manifest__.py @@ -11,10 +11,6 @@ "license": "AGPL-3", "depends": ["base"], "images": ["static/description/banner.jpg"], - "external_dependencies": { - "python": [ - "openupgradelib @ git+https://github.com/OCA/openupgradelib.git@master" - ] - }, + "external_dependencies": {"python": ["openupgradelib"]}, "installable": True, } From e2f32888ba660852e9cd3c0c157af5aa43f0344d Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 28 Feb 2024 10:07:00 +0000 Subject: [PATCH 07/92] [BOT] post-merge updates --- openupgrade_scripts/README.rst | 2 +- openupgrade_scripts/__manifest__.py | 2 +- openupgrade_scripts/static/description/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openupgrade_scripts/README.rst b/openupgrade_scripts/README.rst index c94d1ca26fc8..4674292a99bb 100644 --- a/openupgrade_scripts/README.rst +++ b/openupgrade_scripts/README.rst @@ -7,7 +7,7 @@ Openupgrade Scripts !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:a87d5b3cc86db365a3fd1ffce8f80833105ddbf1ad7b8f9318ebc8cf6cdf5a40 + !! source digest: sha256:3cb15c664a340eb7bbf13cf5062e582f1221d6433d0f22033885de27bb1f64d4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/openupgrade_scripts/__manifest__.py b/openupgrade_scripts/__manifest__.py index 7d52e6b74a43..b603b008efd0 100644 --- a/openupgrade_scripts/__manifest__.py +++ b/openupgrade_scripts/__manifest__.py @@ -7,7 +7,7 @@ "author": "Odoo Community Association (OCA)", "website": "https://github.com/OCA/OpenUpgrade", "category": "Migration", - "version": "17.0.1.0.0", + "version": "17.0.1.0.1", "license": "AGPL-3", "depends": ["base"], "images": ["static/description/banner.jpg"], diff --git a/openupgrade_scripts/static/description/index.html b/openupgrade_scripts/static/description/index.html index 15e7adb4d8ff..e9e8b2b8a5b0 100644 --- a/openupgrade_scripts/static/description/index.html +++ b/openupgrade_scripts/static/description/index.html @@ -366,7 +366,7 @@

Openupgrade Scripts

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:a87d5b3cc86db365a3fd1ffce8f80833105ddbf1ad7b8f9318ebc8cf6cdf5a40 +!! source digest: sha256:3cb15c664a340eb7bbf13cf5062e582f1221d6433d0f22033885de27bb1f64d4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

This module is a containers of migration script to migrate from 16.0 to From 321cf24f0442f8c6e2cafec56c1ca62f6380adb7 Mon Sep 17 00:00:00 2001 From: Roy Le Date: Tue, 14 May 2024 08:38:35 +0700 Subject: [PATCH 08/92] [IMP] openupgrade_framework: pre-commit auto fixes and enable .github/workflows/test.yml --- .github/workflows/test-migration.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index cf8470221c88..fb98fca398da 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -7,7 +7,8 @@ name: Test OpenUpgrade migration on: push: - branches: ["18.0*"] + branches: ["17.0*"] + pull_request: jobs: test: From 2dcee94ae76d340ba2bb1cf2ff160fb919615f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20=C4=90=E1=BA=A1i=20D=C6=B0=C6=A1ng?= Date: Wed, 22 May 2024 11:33:11 +0700 Subject: [PATCH 09/92] [MIG] openupgrade_framework v17 -This commit remove unecessary patch -Check if test exist then execute it in the test.yml -Remove the test loader from patch, detail at https://github.com/OCA/OpenUpgrade/pull/4327#discussion_r1546380226 --- .github/workflows/test-migration.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index fb98fca398da..01e721f2268c 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -91,9 +91,11 @@ jobs: pip install asn1crypto - name: Test data run: | - for snippet in openupgrade/openupgrade_scripts/scripts/*/*/tests/data*.py; do - odoo-old/odoo-bin shell -d $DB < $snippet - done + if test -n "$(ls openupgrade/openupgrade_scripts/scripts/*/*/tests/data*.py 2> /dev/null)"; then + for snippet in openupgrade/openupgrade_scripts/scripts/*/*/tests/data*.py; do + odoo-old/odoo-bin shell -d $DB < $snippet + done + fi - name: OpenUpgrade test run: | # select modules and perform the upgrade From 7c664472fb77251034ff1e0ae3ea45760aa653c9 Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Mon, 30 Sep 2024 12:50:40 +0200 Subject: [PATCH 10/92] [INIT] 18.0: Replace version numbers. --- .github/workflows/documentation-commit.yml | 6 +++--- .github/workflows/test-migration.yml | 12 ++++++------ openupgrade_scripts/README.rst | 4 ++-- openupgrade_scripts/__manifest__.py | 2 +- openupgrade_scripts/readme/DESCRIPTION.md | 4 ++-- openupgrade_scripts/static/description/index.html | 15 +++++++++------ 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.github/workflows/documentation-commit.yml b/.github/workflows/documentation-commit.yml index 721721ef8b27..cdaaaf4f584a 100644 --- a/.github/workflows/documentation-commit.yml +++ b/.github/workflows/documentation-commit.yml @@ -1,4 +1,4 @@ -# On each push in 17.0 branch, +# On each push in 18.0 branch, # AND if the coverage file changed, # build documentation branch and commit the changes # so that the changes are visible on the website @@ -8,7 +8,7 @@ name: Build and commit documentation on: push: - paths: ["docsource/modules160-170.rst"] + paths: ["docsource/modules170-180.rst"] jobs: documentation-commit: @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v2 with: repository: odoo/odoo - ref: "17.0" + ref: "18.0" fetch-depth: 1 path: odoo - name: Configuration diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 01e721f2268c..4f54872b3750 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -7,7 +7,7 @@ name: Test OpenUpgrade migration on: push: - branches: ["17.0*"] + branches: ["18.0*"] pull_request: jobs: @@ -44,19 +44,19 @@ jobs: run: createdb $DB - name: DB Restore run: | - wget -q -O- $DOWNLOADS/16.0.psql | pg_restore -d $DB --no-owner + wget -q -O- $DOWNLOADS/17.0.psql | pg_restore -d $DB --no-owner - name: Check out Odoo uses: actions/checkout@v2 with: repository: odoo/odoo - ref: "17.0" + ref: "18.0" fetch-depth: 1 path: odoo - name: Check out previous Odoo uses: actions/checkout@v2 with: repository: odoo/odoo - ref: "16.0" + ref: "17.0" fetch-depth: 1 path: odoo-old - name: Check out OpenUpgrade @@ -101,7 +101,7 @@ jobs: # select modules and perform the upgrade MODULES_OLD=$(\ sed -n '/^+========/,$p' \ - openupgrade/docsource/modules160-170.rst \ + openupgrade/docsource/modules170-180.rst \ | grep "Done\|Partial\|Nothing" \ | grep -v "theme_" \ | sed -rn 's/((^\| *\|del\| *)|^\| *)([0-9a-z_]*)[ \|].*/\3/g p' \ @@ -109,7 +109,7 @@ jobs: | paste -d, -s) MODULES_NEW=$(\ sed -n '/^+========/,$p' \ - openupgrade/docsource/modules160-170.rst \ + openupgrade/docsource/modules170-180.rst \ | grep "Done\|Partial\|Nothing" \ | grep -v "theme_" \ | sed -rn 's/((^\| *\|new\| *)|^\| *)([0-9a-z_]*)[ \|].*/\3/g p' \ diff --git a/openupgrade_scripts/README.rst b/openupgrade_scripts/README.rst index 4674292a99bb..45d2866f9431 100644 --- a/openupgrade_scripts/README.rst +++ b/openupgrade_scripts/README.rst @@ -28,8 +28,8 @@ Openupgrade Scripts |badge1| |badge2| |badge3| |badge4| |badge5| -This module is a containers of migration script to migrate from 16.0 to -17.0 version. +This module is a containers of migration script to migrate from 17.0 to +18.0 version. **Table of contents** diff --git a/openupgrade_scripts/__manifest__.py b/openupgrade_scripts/__manifest__.py index b603b008efd0..d08da8e19c9e 100644 --- a/openupgrade_scripts/__manifest__.py +++ b/openupgrade_scripts/__manifest__.py @@ -7,7 +7,7 @@ "author": "Odoo Community Association (OCA)", "website": "https://github.com/OCA/OpenUpgrade", "category": "Migration", - "version": "17.0.1.0.1", + "version": "18.0.1.0.0", "license": "AGPL-3", "depends": ["base"], "images": ["static/description/banner.jpg"], diff --git a/openupgrade_scripts/readme/DESCRIPTION.md b/openupgrade_scripts/readme/DESCRIPTION.md index ed2d25468b08..79779e0698e4 100644 --- a/openupgrade_scripts/readme/DESCRIPTION.md +++ b/openupgrade_scripts/readme/DESCRIPTION.md @@ -1,2 +1,2 @@ -This module is a containers of migration script to migrate from 16.0 to -17.0 version. +This module is a containers of migration script to migrate from 17.0 to +18.0 version. diff --git a/openupgrade_scripts/static/description/index.html b/openupgrade_scripts/static/description/index.html index e9e8b2b8a5b0..6a682c5834ee 100644 --- a/openupgrade_scripts/static/description/index.html +++ b/openupgrade_scripts/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -369,8 +370,8 @@

Openupgrade Scripts

!! source digest: sha256:3cb15c664a340eb7bbf13cf5062e582f1221d6433d0f22033885de27bb1f64d4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

-

This module is a containers of migration script to migrate from 16.0 to -17.0 version.

+

This module is a containers of migration script to migrate from 17.0 to +18.0 version.

Table of contents

    @@ -416,7 +417,9 @@

    Credits

    Maintainers

    This module is maintained by the OCA.

    -Odoo Community Association + +Odoo Community Association +

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    From 501965906aeefd7486ac8c4f9e7c5ac1685d127b Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 11 Oct 2024 12:02:10 +0200 Subject: [PATCH 11/92] [FIX] Don't execute regular tests --- .github/workflows/test-migration.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 4f54872b3750..bad3d201b9de 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -140,6 +140,5 @@ jobs: --db_user=$DB_USERNAME \ --load=base,web,openupgrade_framework \ --log-handler odoo.models.unlink:WARNING \ - --test-enable \ --stop-after-init \ --update=$MODULES_NEW From dfdfb1b29aaa6a638dcd59e4e31e385911dc134b Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 18 Nov 2024 18:05:31 +0100 Subject: [PATCH 12/92] [OU-ADD] enable tests, add decorator to make them convenient to use --- .github/workflows/test-migration.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index bad3d201b9de..1af409ebb054 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -91,8 +91,8 @@ jobs: pip install asn1crypto - name: Test data run: | - if test -n "$(ls openupgrade/openupgrade_scripts/scripts/*/*/tests/data*.py 2> /dev/null)"; then - for snippet in openupgrade/openupgrade_scripts/scripts/*/*/tests/data*.py; do + if test -n "$(ls openupgrade/openupgrade_scripts/scripts/*/tests/data*.py 2> /dev/null)"; then + for snippet in openupgrade/openupgrade_scripts/scripts/*/tests/data*.py; do odoo-old/odoo-bin shell -d $DB < $snippet done fi @@ -131,7 +131,7 @@ jobs: echo Execution of Openupgrade with the update of the following modules : $MODULES_NEW # Silence redundant logs from unlinking records (1 line is enough) # to prevent log overflow - OPENUPGRADE_TESTS=1 $ODOO \ + $ODOO \ --addons-path=`echo $ADDONS_PATHS | awk -v OFS="," '$1=$1'` \ --database=$DB \ --db_host=$DB_HOST \ @@ -139,6 +139,8 @@ jobs: --db_port=$DB_PORT \ --db_user=$DB_USERNAME \ --load=base,web,openupgrade_framework \ + --test-enable \ + --test-tags openupgrade \ --log-handler odoo.models.unlink:WARNING \ --stop-after-init \ --update=$MODULES_NEW From 60ecc67789f1f643605b90c50b7e28b71af55b27 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 18 Nov 2024 18:29:59 +0100 Subject: [PATCH 13/92] [OU-FIX] apply doodba's patch to fix building gevent --- .github/workflows/documentation-commit.yml | 1 + .github/workflows/test-migration.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/documentation-commit.yml b/.github/workflows/documentation-commit.yml index cdaaaf4f584a..2d5a5f5cf677 100644 --- a/.github/workflows/documentation-commit.yml +++ b/.github/workflows/documentation-commit.yml @@ -52,6 +52,7 @@ jobs: unixodbc-dev - name: Requirements Installation run: | + sed -i -E "s/(gevent==)21\.8\.0( ; sys_platform != 'win32' and python_version == '3.10')/\122.10.2\2/;s/(greenlet==)1.1.2( ; sys_platform != 'win32' and python_version == '3.10')/\12.0.2\2/" odoo/requirements.txt pip install -q -r odoo/requirements.txt pip install -r ./requirements.txt - name: OpenUpgrade Docs diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 1af409ebb054..4ef31247cb2f 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -85,6 +85,7 @@ jobs: unixodbc-dev - name: Requirements Installation run: | + sed -i -E "s/(gevent==)21\.8\.0( ; sys_platform != 'win32' and python_version == '3.10')/\122.10.2\2/;s/(greenlet==)1.1.2( ; sys_platform != 'win32' and python_version == '3.10')/\12.0.2\2/" odoo/requirements.txt pip install -q -r odoo/requirements.txt pip install -r ./openupgrade/requirements.txt # this is for v16 l10n_eg_edi_eta which crashes without it From 520bf7b6ee861bfddd6fb54e3c8af44e2cfedf5a Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 18 Nov 2024 23:25:19 +0100 Subject: [PATCH 14/92] [OU-ADD] explicitly disable loading demo data --- .github/workflows/test-migration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 4ef31247cb2f..ece160723203 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -45,6 +45,7 @@ jobs: - name: DB Restore run: | wget -q -O- $DOWNLOADS/17.0.psql | pg_restore -d $DB --no-owner + psql $DB -c "UPDATE ir_module_module SET demo=False" - name: Check out Odoo uses: actions/checkout@v2 with: @@ -144,4 +145,5 @@ jobs: --test-tags openupgrade \ --log-handler odoo.models.unlink:WARNING \ --stop-after-init \ + --without-demo=$MODULES_NEW \ --update=$MODULES_NEW From 6fb393eec0ce0ab42f7218686e2a2c39853f4857 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Sat, 30 Nov 2024 09:13:32 +0100 Subject: [PATCH 15/92] [OU-ADD] enable coverage/codecov --- .github/workflows/test-migration.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index ece160723203..ca5494ff8029 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -91,6 +91,7 @@ jobs: pip install -r ./openupgrade/requirements.txt # this is for v16 l10n_eg_edi_eta which crashes without it pip install asn1crypto + pip install coverage - name: Test data run: | if test -n "$(ls openupgrade/openupgrade_scripts/scripts/*/tests/data*.py 2> /dev/null)"; then @@ -133,6 +134,8 @@ jobs: echo Execution of Openupgrade with the update of the following modules : $MODULES_NEW # Silence redundant logs from unlinking records (1 line is enough) # to prevent log overflow + coverage run \ + --source=$GITHUB_WORKSPACE/openupgrade/openupgrade_scripts/scripts \ $ODOO \ --addons-path=`echo $ADDONS_PATHS | awk -v OFS="," '$1=$1'` \ --database=$DB \ @@ -147,3 +150,9 @@ jobs: --stop-after-init \ --without-demo=$MODULES_NEW \ --update=$MODULES_NEW + - name: Generate coverage.xml + run: coverage xml + - uses: codecov/codecov-action@v4 + with: + files: coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} From 47b0d646a3ef192f42a587a7407eb7d5397ab8b3 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 20 Jan 2025 12:35:50 +0100 Subject: [PATCH 16/92] [ADD] install openupgradelib from github --- .github/workflows/test-migration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index ca5494ff8029..fd197796d7fc 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -89,6 +89,7 @@ jobs: sed -i -E "s/(gevent==)21\.8\.0( ; sys_platform != 'win32' and python_version == '3.10')/\122.10.2\2/;s/(greenlet==)1.1.2( ; sys_platform != 'win32' and python_version == '3.10')/\12.0.2\2/" odoo/requirements.txt pip install -q -r odoo/requirements.txt pip install -r ./openupgrade/requirements.txt + pip install -U git+https://github.com/oca/openupgradelib # this is for v16 l10n_eg_edi_eta which crashes without it pip install asn1crypto pip install coverage From a0f16c53824c612fa691813e08efc317faf645b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Ra=C3=AFch?= Date: Thu, 13 Feb 2025 12:49:55 +0100 Subject: [PATCH 17/92] [IMP] copier and github workflows --- .github/workflows/test-migration.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index fd197796d7fc..3837bc650a03 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -12,7 +12,7 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: DB: "openupgrade" DB_HOST: "localhost" @@ -27,7 +27,7 @@ jobs: OPENUPGRADE_USE_DEMO: "yes" steps: - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Configure Postgres @@ -47,21 +47,21 @@ jobs: wget -q -O- $DOWNLOADS/17.0.psql | pg_restore -d $DB --no-owner psql $DB -c "UPDATE ir_module_module SET demo=False" - name: Check out Odoo - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: odoo/odoo ref: "18.0" fetch-depth: 1 path: odoo - name: Check out previous Odoo - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: odoo/odoo ref: "17.0" fetch-depth: 1 path: odoo-old - name: Check out OpenUpgrade - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: path: openupgrade - name: Configuration From ed64465b64a5c06176e6e91ae989861d230be68d Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 21 Mar 2025 11:46:27 +0000 Subject: [PATCH 18/92] [BOT] post-merge updates --- openupgrade_scripts/README.rst | 18 +++++++++--------- .../static/description/index.html | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/openupgrade_scripts/README.rst b/openupgrade_scripts/README.rst index 45d2866f9431..5c8965120a34 100644 --- a/openupgrade_scripts/README.rst +++ b/openupgrade_scripts/README.rst @@ -7,7 +7,7 @@ Openupgrade Scripts !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:3cb15c664a340eb7bbf13cf5062e582f1221d6433d0f22033885de27bb1f64d4 + !! source digest: sha256:822b62802d167eb9c5a7f7fe5a2ec56b891dd6aa8e81d31929e0f123f1e8346f !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -17,13 +17,13 @@ Openupgrade Scripts :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2FOpenUpgrade-lightgray.png?logo=github - :target: https://github.com/OCA/OpenUpgrade/tree/17.0/openupgrade_scripts + :target: https://github.com/OCA/OpenUpgrade/tree/18.0/openupgrade_scripts :alt: OCA/OpenUpgrade .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/OpenUpgrade-17-0/OpenUpgrade-17-0-openupgrade_scripts + :target: https://translation.odoo-community.org/projects/OpenUpgrade-18-0/OpenUpgrade-18-0-openupgrade_scripts :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/OpenUpgrade&target_branch=17.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/OpenUpgrade&target_branch=18.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -45,12 +45,12 @@ to be available via your ``addons-path``. Configuration ============= -- call your odoo instance with the option - ``--upgrade-path=/PATH_TO_openupgrade_scripts_MODULE/scripts/`` +- call your odoo instance with the option + ``--upgrade-path=/PATH_TO_openupgrade_scripts_MODULE/scripts/`` or -- add the key to your configuration file: +- add the key to your configuration file: .. code:: shell @@ -63,7 +63,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -83,6 +83,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/OpenUpgrade `_ project on GitHub. +This module is part of the `OCA/OpenUpgrade `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/openupgrade_scripts/static/description/index.html b/openupgrade_scripts/static/description/index.html index 6a682c5834ee..329d42ef0587 100644 --- a/openupgrade_scripts/static/description/index.html +++ b/openupgrade_scripts/static/description/index.html @@ -367,9 +367,9 @@

    Openupgrade Scripts

    !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:3cb15c664a340eb7bbf13cf5062e582f1221d6433d0f22033885de27bb1f64d4 +!! source digest: sha256:822b62802d167eb9c5a7f7fe5a2ec56b891dd6aa8e81d31929e0f123f1e8346f !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

    Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

    +

    Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

    This module is a containers of migration script to migrate from 17.0 to 18.0 version.

    Table of contents

    @@ -409,7 +409,7 @@

    Bug Tracker

    Bugs are tracked on GitHub Issues. 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 -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    @@ -423,7 +423,7 @@

    Maintainers

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    -

    This module is part of the OCA/OpenUpgrade project on GitHub.

    +

    This module is part of the OCA/OpenUpgrade project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

From adb78d08ab973f181300a681833506ea3229f162 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 24 Mar 2025 08:05:59 +0100 Subject: [PATCH 19/92] [ADD] update analysis bot --- .github/workflows/generate-analysis-cron.yml | 18 ++ .github/workflows/generate-analysis.yml | 226 +++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 .github/workflows/generate-analysis-cron.yml create mode 100644 .github/workflows/generate-analysis.yml diff --git a/.github/workflows/generate-analysis-cron.yml b/.github/workflows/generate-analysis-cron.yml new file mode 100644 index 000000000000..5c3c10c00565 --- /dev/null +++ b/.github/workflows/generate-analysis-cron.yml @@ -0,0 +1,18 @@ +name: Generate analysis files - cron and push + +on: + push: + # when module merges are added to apriori, this + # might cause changes in the analysis + paths: ["openupgrade_scripts/apriori.py"] + schedule: + # every monday at 04:00 + - cron: "0 4 * * 1" + +jobs: + generate-analysis18: + if: github.repository == 'OCA/OpenUpgrade' && (github.event_name == 'schedule' || github.ref_name == '18.0') + uses: ./.github/workflows/generate-analysis.yml + with: + from_version: "17.0" + to_version: "18.0" diff --git a/.github/workflows/generate-analysis.yml b/.github/workflows/generate-analysis.yml new file mode 100644 index 000000000000..3978fd8bed57 --- /dev/null +++ b/.github/workflows/generate-analysis.yml @@ -0,0 +1,226 @@ +name: Generate analysis files + +on: + workflow_call: + inputs: + from_version: + required: true + type: string + from_version_requirements: + default: 'gevent==22.10.2 greenlet==2.0.2' + type: string + from_version_requirements_sed: + default: '/^gevent\>/d; /^greenlet\>/d' + type: string + to_version: + required: true + type: string + to_version_requirements: + default: 'gevent==22.10.2 greenlet==2.0.2' + type: string + to_version_requirements_sed: + default: '/^gevent\>/d; /^greenlet\>/d' + type: string + python_version: + default: "3.10" + type: string + postgres_version: + default: "14" + type: string + +jobs: + generate-analysis: + name: Generate analysis ${{ inputs.to_version }} + runs-on: ubuntu-latest + env: + PGHOST: "localhost" + PGPASSWORD: "odoo" + PGUSER: "odoo" + FROM_VERSION: "${{ inputs.from_version }}" + TO_VERSION: "${{ inputs.to_version }}" + services: + postgres: + image: postgres:${{ inputs.postgres_version }} + env: + POSTGRES_USER: odoo + POSTGRES_PASSWORD: odoo + ports: + - 5432:5432 + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "${{ inputs.python_version }}" + - name: Install required packages + run: | + sudo apt update + sudo apt install \ + expect \ + expect-dev \ + libevent-dev \ + libldap2-dev \ + libsasl2-dev \ + libxml2-dev \ + libxslt1-dev \ + nodejs \ + python3-lxml \ + python3-passlib \ + python3-psycopg2 \ + python3-serial \ + python3-simplejson \ + python3-werkzeug \ + python3-yaml \ + unixodbc-dev + - name: Checkout previous OpenUpgrade + uses: actions/checkout@v4 + with: + ref: "${{ env.FROM_VERSION }}" + path: openupgrade-${{ env.FROM_VERSION }} + - name: Checkout OpenUpgrade from current branch + if: github.event_name != 'schedule' && startsWith(github.ref_name, env.TO_VERSION) + uses: actions/checkout@v4 + with: + path: openupgrade-${{ env.TO_VERSION }} + - name: Checkout current OpenUpgrade + if: github.event_name == 'schedule' || !startsWith(github.ref_name, env.TO_VERSION) + uses: actions/checkout@v4 + with: + ref: "${{ env.TO_VERSION }}" + path: openupgrade-${{ env.TO_VERSION }} + - name: Install previous Odoo + run: | + wget -q -O- https://github.com/oca/ocb/archive/refs/heads/$FROM_VERSION.tar.gz | tar -xz + wget -q -O- https://github.com/oca/server-tools/archive/refs/heads/$FROM_VERSION.tar.gz | tar -xz + python -mvenv env-$FROM_VERSION + . env-$FROM_VERSION/bin/activate + pip install ${{ inputs.from_version_requirements }} + sed -iE '${{ inputs.from_version_requirements_sed }}' OCB-$FROM_VERSION/requirements.txt + pip install -r OCB-$FROM_VERSION/requirements.txt + pip install ./OCB-$FROM_VERSION + pip install -r server-tools-$FROM_VERSION/requirements.txt + pip install -r openupgrade-$FROM_VERSION/requirements.txt + # this is for l10n_eg_edi_eta which crashes without it + pip install asn1crypto + odoo -s -c odoo-$FROM_VERSION.cfg --addons-path server-tools-$FROM_VERSION,openupgrade-$FROM_VERSION --stop-after-init + - name: Install current Odoo + run: | + wget -q -O- https://github.com/oca/ocb/archive/refs/heads/$TO_VERSION.tar.gz | tar -xz + wget -q -O- https://github.com/oca/server-tools/archive/refs/heads/$TO_VERSION.tar.gz | tar -xz + python -mvenv env-$TO_VERSION + . env-$TO_VERSION/bin/activate + pip install ${{ inputs.to_version_requirements }} + sed -iE '${{ inputs.to_version_requirements_sed }}' OCB-$TO_VERSION/requirements.txt + pip install -r OCB-$TO_VERSION/requirements.txt + pip install ./OCB-$TO_VERSION + pip install -r server-tools-$TO_VERSION/requirements.txt + pip install -r openupgrade-$TO_VERSION/requirements.txt + # this is for l10n_eg_edi_eta which crashes without it + pip install asn1crypto + odoo -s -c odoo-$TO_VERSION.cfg --addons-path server-tools-$TO_VERSION,openupgrade-$TO_VERSION --stop-after-init + - name: Create previous Odoo database + env: + ODOO: env-${{ env.FROM_VERSION }}/bin/odoo -c odoo-${{ env.FROM_VERSION }}.cfg -d ${{ env.FROM_VERSION }} + ODOO_SHELL: env-${{ env.FROM_VERSION }}/bin/odoo shell -c odoo-${{ env.FROM_VERSION }}.cfg -d ${{ env.FROM_VERSION }} + run: | + $ODOO --without-demo=all -i upgrade_analysis --stop-after-init + $ODOO_SHELL < "$PR_BODY" + + for module in $( + git -C $OPENUPGRADE_DIR status --short | awk '{print $2}' | awk -F/ '{print $3}' | sort -u + ); do + if grep -qE "\<$module\>.*\<((Done)|(No))" $OPENUPGRADE_DIR/docsource/modules*; then + echo Done module $module was updated + echo "- $module" >> $PR_BODY + else + echo $module is not done yet + fi + done + + if ! grep -q - $PR_BODY; then + echo None >> $PR_BODY + fi + + echo "body-file=$PR_BODY" >> "$GITHUB_OUTPUT" + - name: Get number of milestone + id: get_milestone + run: | + echo "number=$( + wget -qO- https://api.github.com/repos/${{ github.repository }}/milestones|jq '.[] | select(.title=="${{ env.TO_VERSION }}") .number' + )" >> "$GITHUB_OUTPUT" + - name: Create PR + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e #v7 + with: + base: "${{ env.TO_VERSION }}" + body-path: "${{ steps.generate_body.outputs.body-file }}" + branch: "${{ env.TO_VERSION }}-update-analysis-bot" + commit-message: "[${{ env.TO_VERSION }}][UPD] analysis files" + delete-branch: true + milestone: "${{ steps.get_milestone.outputs.number }}" + path: "openupgrade-${{ env.TO_VERSION }}" + title: "[${{ env.TO_VERSION }}][UPD] analysis files" From bd33e354b587e6ce1d2dd7864ae1da5c52713080 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 31 Mar 2025 13:28:52 +0200 Subject: [PATCH 20/92] [IMP] pin random github actions to their SHA commit --- .github/workflows/documentation-commit.yml | 2 +- .github/workflows/test-migration.yml | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/documentation-commit.yml b/.github/workflows/documentation-commit.yml index 2d5a5f5cf677..b5d71569f153 100644 --- a/.github/workflows/documentation-commit.yml +++ b/.github/workflows/documentation-commit.yml @@ -60,7 +60,7 @@ jobs: # try to build the documentation sh ./build_openupgrade_docs - name: Commit changes - uses: EndBug/add-and-commit@v9 + uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 #v9 with: add: "docs" default_author: github_actions diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 3837bc650a03..1fdfca1bb3bc 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -25,21 +25,22 @@ jobs: PGPASSWORD: "odoo" PGUSER: "odoo" OPENUPGRADE_USE_DEMO: "yes" + services: + postgres: + image: postgres:14.0 + env: + POSTGRES_USER: odoo + POSTGRES_PASSWORD: odoo + POSTGRES_DB: odoo + ports: + - 5432:5432 steps: - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.10' - - name: Configure Postgres - uses: harmon758/postgresql-action@v1 - with: - postgresql version: "14" - postgresql user: ${DB_USERNAME} - postgresql password: ${DB_PASSWORD} - - name: Wait / Sleep - uses: jakejarvis/wait-action@v0.1.0 - with: - time: "10s" + - name: Sleep for 10 seconds + run: sleep 10s - name: DB Creation run: createdb $DB - name: DB Restore From 56a1195ea462b9a2a52ef819ab5feb2ec2db430f Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 26 May 2025 08:29:19 +0200 Subject: [PATCH 21/92] [IMP] analysis bot: reword commit message and PR title --- .github/workflows/generate-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generate-analysis.yml b/.github/workflows/generate-analysis.yml index 3978fd8bed57..00268e00c571 100644 --- a/.github/workflows/generate-analysis.yml +++ b/.github/workflows/generate-analysis.yml @@ -219,8 +219,8 @@ jobs: base: "${{ env.TO_VERSION }}" body-path: "${{ steps.generate_body.outputs.body-file }}" branch: "${{ env.TO_VERSION }}-update-analysis-bot" - commit-message: "[${{ env.TO_VERSION }}][UPD] analysis files" + commit-message: "[IMP] Update analysis files" delete-branch: true milestone: "${{ steps.get_milestone.outputs.number }}" path: "openupgrade-${{ env.TO_VERSION }}" - title: "[${{ env.TO_VERSION }}][UPD] analysis files" + title: "[${{ env.TO_VERSION }}][IMP] Update analysis files" From b7442a819e9ce3bf6328c01b0d1724e21968a36d Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 26 May 2025 13:25:40 +0200 Subject: [PATCH 22/92] [ADD] install phonenumbers in ci as it's a hard dependency for account_peppol by now --- .github/workflows/test-migration.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 1fdfca1bb3bc..fa1f086b5de1 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -94,6 +94,8 @@ jobs: # this is for v16 l10n_eg_edi_eta which crashes without it pip install asn1crypto pip install coverage + # this is for account_peppol + pip install phonenumbers - name: Test data run: | if test -n "$(ls openupgrade/openupgrade_scripts/scripts/*/tests/data*.py 2> /dev/null)"; then From 2762bdd11b6a723bb271fa6af8999fe5a95133d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Ra=C3=AFch?= Date: Mon, 11 Aug 2025 13:38:22 +0200 Subject: [PATCH 23/92] [FIX] github workflow: cbor2 issues --- .github/workflows/test-migration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index fa1f086b5de1..33d4348861b0 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -88,6 +88,7 @@ jobs: - name: Requirements Installation run: | sed -i -E "s/(gevent==)21\.8\.0( ; sys_platform != 'win32' and python_version == '3.10')/\122.10.2\2/;s/(greenlet==)1.1.2( ; sys_platform != 'win32' and python_version == '3.10')/\12.0.2\2/" odoo/requirements.txt + sed -i -E "s/(^cbor2==)5\.4\.2/\15.6.2/" odoo/requirements.txt pip install -q -r odoo/requirements.txt pip install -r ./openupgrade/requirements.txt pip install -U git+https://github.com/oca/openupgradelib From 18e86905a4c39522b03174f2c5b029de61b24b38 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Oct 2025 07:23:18 +0200 Subject: [PATCH 24/92] [UPD] 19.0: Replace version numbers. --- .github/workflows/documentation-commit.yml | 6 +++--- .github/workflows/test-migration.yml | 12 ++++++------ openupgrade_scripts/__manifest__.py | 2 +- openupgrade_scripts/readme/DESCRIPTION.md | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/documentation-commit.yml b/.github/workflows/documentation-commit.yml index b5d71569f153..8b69cde83dcf 100644 --- a/.github/workflows/documentation-commit.yml +++ b/.github/workflows/documentation-commit.yml @@ -1,4 +1,4 @@ -# On each push in 18.0 branch, +# On each push in 19.0 branch, # AND if the coverage file changed, # build documentation branch and commit the changes # so that the changes are visible on the website @@ -8,7 +8,7 @@ name: Build and commit documentation on: push: - paths: ["docsource/modules170-180.rst"] + paths: ["docsource/modules180-190.rst"] jobs: documentation-commit: @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v2 with: repository: odoo/odoo - ref: "18.0" + ref: "19.0" fetch-depth: 1 path: odoo - name: Configuration diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 33d4348861b0..43217c9e8cc2 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -7,7 +7,7 @@ name: Test OpenUpgrade migration on: push: - branches: ["18.0*"] + branches: ["19.0*"] pull_request: jobs: @@ -45,20 +45,20 @@ jobs: run: createdb $DB - name: DB Restore run: | - wget -q -O- $DOWNLOADS/17.0.psql | pg_restore -d $DB --no-owner + wget -q -O- $DOWNLOADS/18.0.psql | pg_restore -d $DB --no-owner psql $DB -c "UPDATE ir_module_module SET demo=False" - name: Check out Odoo uses: actions/checkout@v4 with: repository: odoo/odoo - ref: "18.0" + ref: "19.0" fetch-depth: 1 path: odoo - name: Check out previous Odoo uses: actions/checkout@v4 with: repository: odoo/odoo - ref: "17.0" + ref: "18.0" fetch-depth: 1 path: odoo-old - name: Check out OpenUpgrade @@ -109,7 +109,7 @@ jobs: # select modules and perform the upgrade MODULES_OLD=$(\ sed -n '/^+========/,$p' \ - openupgrade/docsource/modules170-180.rst \ + openupgrade/docsource/modules180-190.rst \ | grep "Done\|Partial\|Nothing" \ | grep -v "theme_" \ | sed -rn 's/((^\| *\|del\| *)|^\| *)([0-9a-z_]*)[ \|].*/\3/g p' \ @@ -117,7 +117,7 @@ jobs: | paste -d, -s) MODULES_NEW=$(\ sed -n '/^+========/,$p' \ - openupgrade/docsource/modules170-180.rst \ + openupgrade/docsource/modules180-190.rst \ | grep "Done\|Partial\|Nothing" \ | grep -v "theme_" \ | sed -rn 's/((^\| *\|new\| *)|^\| *)([0-9a-z_]*)[ \|].*/\3/g p' \ diff --git a/openupgrade_scripts/__manifest__.py b/openupgrade_scripts/__manifest__.py index d08da8e19c9e..0d588aa418cd 100644 --- a/openupgrade_scripts/__manifest__.py +++ b/openupgrade_scripts/__manifest__.py @@ -7,7 +7,7 @@ "author": "Odoo Community Association (OCA)", "website": "https://github.com/OCA/OpenUpgrade", "category": "Migration", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "license": "AGPL-3", "depends": ["base"], "images": ["static/description/banner.jpg"], diff --git a/openupgrade_scripts/readme/DESCRIPTION.md b/openupgrade_scripts/readme/DESCRIPTION.md index 79779e0698e4..23d24b62fdff 100644 --- a/openupgrade_scripts/readme/DESCRIPTION.md +++ b/openupgrade_scripts/readme/DESCRIPTION.md @@ -1,2 +1,2 @@ -This module is a containers of migration script to migrate from 17.0 to -18.0 version. +This module is a containers of migration script to migrate from 18.0 to +19.0 version. From 1385720a882e0fc7159523964e957ab37e33a4b0 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Oct 2025 07:23:18 +0200 Subject: [PATCH 25/92] [INIT] 19.0: Initialize apriori.py file. --- openupgrade_scripts/apriori.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 openupgrade_scripts/apriori.py diff --git a/openupgrade_scripts/apriori.py b/openupgrade_scripts/apriori.py new file mode 100644 index 000000000000..3820b4a12005 --- /dev/null +++ b/openupgrade_scripts/apriori.py @@ -0,0 +1,31 @@ +""" Encode any known changes to the database here +to help the matching process +""" + +# Renamed modules is a mapping from old module name to new module name +renamed_modules = { + # odoo + # odoo/enterprise + # OCA/... +} + +# Merged modules contain a mapping from old module names to other, +# preexisting module names +merged_modules = { + # odoo + # odoo/enterprise + # OCA/... +} + +# only used here for upgrade_analysis +renamed_models = { + # odoo + # OCA/... +} + +# only used here for upgrade_analysis +merged_models = { + # odoo + # OCA/... +} + From f427442cafbe4f5b3f85d4ae9b9699a78d71aa89 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Oct 2025 07:23:31 +0200 Subject: [PATCH 26/92] [FIX] 19.0: pre-commit --- openupgrade_scripts/README.rst | 26 ++++++++------- openupgrade_scripts/apriori.py | 3 +- .../static/description/index.html | 32 +++++++++++-------- requirements.txt | 2 ++ 4 files changed, 37 insertions(+), 26 deletions(-) create mode 100644 requirements.txt diff --git a/openupgrade_scripts/README.rst b/openupgrade_scripts/README.rst index 5c8965120a34..3ab5666ceaf2 100644 --- a/openupgrade_scripts/README.rst +++ b/openupgrade_scripts/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + =================== Openupgrade Scripts =================== @@ -13,23 +17,23 @@ Openupgrade Scripts .. |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/licence-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-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%2FOpenUpgrade-lightgray.png?logo=github - :target: https://github.com/OCA/OpenUpgrade/tree/18.0/openupgrade_scripts + :target: https://github.com/OCA/OpenUpgrade/tree/19.0/openupgrade_scripts :alt: OCA/OpenUpgrade .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/OpenUpgrade-18-0/OpenUpgrade-18-0-openupgrade_scripts + :target: https://translation.odoo-community.org/projects/OpenUpgrade-19-0/OpenUpgrade-19-0-openupgrade_scripts :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/OpenUpgrade&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/OpenUpgrade&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| -This module is a containers of migration script to migrate from 17.0 to -18.0 version. +This module is a containers of migration script to migrate from 18.0 to +19.0 version. **Table of contents** @@ -45,12 +49,12 @@ to be available via your ``addons-path``. Configuration ============= -- call your odoo instance with the option - ``--upgrade-path=/PATH_TO_openupgrade_scripts_MODULE/scripts/`` +- call your odoo instance with the option + ``--upgrade-path=/PATH_TO_openupgrade_scripts_MODULE/scripts/`` or -- add the key to your configuration file: +- add the key to your configuration file: .. code:: shell @@ -63,7 +67,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -83,6 +87,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/OpenUpgrade `_ project on GitHub. +This module is part of the `OCA/OpenUpgrade `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/openupgrade_scripts/apriori.py b/openupgrade_scripts/apriori.py index 3820b4a12005..ad2269309acc 100644 --- a/openupgrade_scripts/apriori.py +++ b/openupgrade_scripts/apriori.py @@ -1,4 +1,4 @@ -""" Encode any known changes to the database here +"""Encode any known changes to the database here to help the matching process """ @@ -28,4 +28,3 @@ # odoo # OCA/... } - diff --git a/openupgrade_scripts/static/description/index.html b/openupgrade_scripts/static/description/index.html index 329d42ef0587..bcf94d1af849 100644 --- a/openupgrade_scripts/static/description/index.html +++ b/openupgrade_scripts/static/description/index.html @@ -3,7 +3,7 @@ -Openupgrade Scripts +README.rst -
-

Openupgrade Scripts

+
+ + +Odoo Community Association + +
+

Openupgrade Scripts

-

Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

-

This module is a containers of migration script to migrate from 17.0 to -18.0 version.

+

Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

+

This module is a containers of migration script to migrate from 18.0 to +19.0 version.

Table of contents

    @@ -385,12 +390,12 @@

    Openupgrade Scripts

-

Installation

+

Installation

This module does not need to be installed on a database. It simply needs to be available via your addons-path.

-

Configuration

+

Configuration

  • call your odoo instance with the option --upgrade-path=/PATH_TO_openupgrade_scripts_MODULE/scripts/
  • @@ -405,17 +410,17 @@

    Configuration

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. 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 -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -423,10 +428,11 @@

Maintainers

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

-

This module is part of the OCA/OpenUpgrade project on GitHub.

+

This module is part of the OCA/OpenUpgrade project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000000..180fc49789ba --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# generated from manifests external_dependencies +openupgradelib From aa6e403b0da723517f71d95ed0531368a34736db Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Oct 2025 12:28:31 +0200 Subject: [PATCH 27/92] [IMP] run test migration in the same cases as standard tests --- .github/workflows/test-migration.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 43217c9e8cc2..5eabb1032fe6 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -6,9 +6,13 @@ name: Test OpenUpgrade migration on: - push: - branches: ["19.0*"] pull_request: + branches: + - "19.0*" + push: + branches: + - "19.0" + - "19.0-ocabot-*" jobs: test: From d6bddd8e2a1207a1d365085a8ec59adbedb570c0 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Oct 2025 12:32:04 +0200 Subject: [PATCH 28/92] [IMP] download test database from current repository --- .github/workflows/test-migration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index 5eabb1032fe6..a00244599594 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -23,7 +23,7 @@ jobs: DB_PASSWORD: "odoo" DB_PORT: 5432 DB_USERNAME: "odoo" - DOWNLOADS: https://github.com/OCA/OpenUpgrade/releases/download/databases + DOWNLOADS: https://github.com/${{github.repository}}/releases/download/databases ODOO: "./odoo/odoo-bin" PGHOST: "localhost" PGPASSWORD: "odoo" From abf28ba175a4bd837a2a2e31926d70cdd3538f2c Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Oct 2025 13:36:20 +0200 Subject: [PATCH 29/92] [ADD] .prettierignore from v18 --- .prettierignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000000..22a8861c737d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +openupgrade_scripts/scripts/*/*/noupdate_changes*.xml +docsource/*.rst +docs From e891be0752b3a16fa8e1a102d68b4ab4539799ae Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Sun, 1 Nov 2020 23:15:59 +0100 Subject: [PATCH 30/92] [INIT] Full refactor of the OpenUpgrade project --- openupgrade_framework/README.rst | 8 + openupgrade_framework/__init__.py | 2 + openupgrade_framework/__manifest__.py | 14 + openupgrade_framework/odoo_patch/__init__.py | 3 + .../odoo_patch/addons/__init__.py | 3 + .../odoo_patch/addons/mrp/__init__.py | 20 + .../addons/point_of_sale/__init__.py | 1 + .../addons/point_of_sale/models/__init__.py | 1 + .../addons/point_of_sale/models/pos_config.py | 21 + .../odoo_patch/addons/stock/__init__.py | 17 + .../odoo_patch/odoo/__init__.py | 10 + openupgrade_framework/odoo_patch/odoo/http.py | 32 + .../odoo_patch/odoo/models.py | 179 ++++++ .../odoo_patch/odoo/modules/__init__.py | 12 + .../odoo_patch/odoo/modules/graph.py | 108 ++++ .../odoo_patch/odoo/modules/loading.py | 556 ++++++++++++++++++ .../odoo_patch/odoo/modules/migration.py | 118 ++++ .../odoo_patch/odoo/modules/registry.py | 58 ++ .../odoo_patch/odoo/service/__init__.py | 4 + .../odoo_patch/odoo/service/server.py | 71 +++ .../odoo_patch/odoo/tools/__init__.py | 2 + .../odoo_patch/odoo/tools/convert.py | 23 + .../odoo_patch/odoo/tools/view_validation.py | 29 + openupgrade_framework/openupgrade/__init__.py | 0 .../openupgrade/openupgrade_loading.py | 318 ++++++++++ .../openupgrade/openupgrade_log.py | 60 ++ openupgrade_framework/readme/CONFIGURE.rst | 7 + openupgrade_framework/readme/CONTRIBUTORS.rst | 2 + openupgrade_framework/readme/DESCRIPTION.rst | 2 + openupgrade_framework/readme/DEVELOP.rst | 60 ++ 30 files changed, 1741 insertions(+) create mode 100644 openupgrade_framework/README.rst create mode 100644 openupgrade_framework/__init__.py create mode 100644 openupgrade_framework/__manifest__.py create mode 100644 openupgrade_framework/odoo_patch/__init__.py create mode 100644 openupgrade_framework/odoo_patch/addons/__init__.py create mode 100644 openupgrade_framework/odoo_patch/addons/mrp/__init__.py create mode 100644 openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py create mode 100644 openupgrade_framework/odoo_patch/addons/point_of_sale/models/__init__.py create mode 100644 openupgrade_framework/odoo_patch/addons/point_of_sale/models/pos_config.py create mode 100644 openupgrade_framework/odoo_patch/addons/stock/__init__.py create mode 100644 openupgrade_framework/odoo_patch/odoo/__init__.py create mode 100644 openupgrade_framework/odoo_patch/odoo/http.py create mode 100644 openupgrade_framework/odoo_patch/odoo/models.py create mode 100644 openupgrade_framework/odoo_patch/odoo/modules/__init__.py create mode 100644 openupgrade_framework/odoo_patch/odoo/modules/graph.py create mode 100644 openupgrade_framework/odoo_patch/odoo/modules/loading.py create mode 100644 openupgrade_framework/odoo_patch/odoo/modules/migration.py create mode 100644 openupgrade_framework/odoo_patch/odoo/modules/registry.py create mode 100644 openupgrade_framework/odoo_patch/odoo/service/__init__.py create mode 100644 openupgrade_framework/odoo_patch/odoo/service/server.py create mode 100644 openupgrade_framework/odoo_patch/odoo/tools/__init__.py create mode 100644 openupgrade_framework/odoo_patch/odoo/tools/convert.py create mode 100644 openupgrade_framework/odoo_patch/odoo/tools/view_validation.py create mode 100644 openupgrade_framework/openupgrade/__init__.py create mode 100644 openupgrade_framework/openupgrade/openupgrade_loading.py create mode 100644 openupgrade_framework/openupgrade/openupgrade_log.py create mode 100644 openupgrade_framework/readme/CONFIGURE.rst create mode 100644 openupgrade_framework/readme/CONTRIBUTORS.rst create mode 100644 openupgrade_framework/readme/DESCRIPTION.rst create mode 100644 openupgrade_framework/readme/DEVELOP.rst diff --git a/openupgrade_framework/README.rst b/openupgrade_framework/README.rst new file mode 100644 index 000000000000..3ed54188c923 --- /dev/null +++ b/openupgrade_framework/README.rst @@ -0,0 +1,8 @@ +===================== +Openupgrade Framework +===================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/openupgrade_framework/__init__.py b/openupgrade_framework/__init__.py new file mode 100644 index 000000000000..94ba6ffa9f2f --- /dev/null +++ b/openupgrade_framework/__init__.py @@ -0,0 +1,2 @@ +from . import odoo_patch +from . import openupgrade diff --git a/openupgrade_framework/__manifest__.py b/openupgrade_framework/__manifest__.py new file mode 100644 index 000000000000..fd016de4ca7a --- /dev/null +++ b/openupgrade_framework/__manifest__.py @@ -0,0 +1,14 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Openupgrade Framework", + "summary": """Module to integrate in the server_wide_modules + option to make upgrades between two major revisions.""", + "author": "Odoo Community Association (OCA)," " Therp BV, Opener B.V., GRAP", + "website": "https://github.com/OCA/openupgrade", + "category": "Migration", + "version": "14.0.1.0.0", + "license": "AGPL-3", + "depends": ["base"], + "installable": False, +} diff --git a/openupgrade_framework/odoo_patch/__init__.py b/openupgrade_framework/odoo_patch/__init__.py new file mode 100644 index 000000000000..1fd6e167cd12 --- /dev/null +++ b/openupgrade_framework/odoo_patch/__init__.py @@ -0,0 +1,3 @@ +from . import odoo +from . import addons + diff --git a/openupgrade_framework/odoo_patch/addons/__init__.py b/openupgrade_framework/odoo_patch/addons/__init__.py new file mode 100644 index 000000000000..e5aa886bacb6 --- /dev/null +++ b/openupgrade_framework/odoo_patch/addons/__init__.py @@ -0,0 +1,3 @@ +from . import mrp +from . import stock +from . import point_of_sale diff --git a/openupgrade_framework/odoo_patch/addons/mrp/__init__.py b/openupgrade_framework/odoo_patch/addons/mrp/__init__.py new file mode 100644 index 000000000000..f7b8b869472b --- /dev/null +++ b/openupgrade_framework/odoo_patch/addons/mrp/__init__.py @@ -0,0 +1,20 @@ +from odoo.addons import mrp + + +def _pre_init_mrp(cr): + """ Allow installing MRP in databases with large stock.move table (>1M records) + - Creating the computed+stored field stock_move.is_done is terribly slow with the ORM and + leads to "Out of Memory" crashes + """ + # + # don't try to add 'is_done' column, because it will fail + # when executing the generation of records, in the openupgrade_records + # module. + # cr.execute("""ALTER TABLE "stock_move" ADD COLUMN "is_done" bool;""") + # cr.execute("""UPDATE stock_move + # SET is_done=COALESCE(state in ('done', 'cancel'), FALSE);""") + pass + # + + +mrp._pre_init_mrp = _pre_init_mrp diff --git a/openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py b/openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/openupgrade_framework/odoo_patch/addons/point_of_sale/models/__init__.py b/openupgrade_framework/odoo_patch/addons/point_of_sale/models/__init__.py new file mode 100644 index 000000000000..db8634ade1f7 --- /dev/null +++ b/openupgrade_framework/odoo_patch/addons/point_of_sale/models/__init__.py @@ -0,0 +1 @@ +from . import pos_config diff --git a/openupgrade_framework/odoo_patch/addons/point_of_sale/models/pos_config.py b/openupgrade_framework/odoo_patch/addons/point_of_sale/models/pos_config.py new file mode 100644 index 000000000000..ac0f5dc5a49e --- /dev/null +++ b/openupgrade_framework/odoo_patch/addons/point_of_sale/models/pos_config.py @@ -0,0 +1,21 @@ +from odoo import api +from odoo.addons.point_of_sale.models.pos_config import PosConfig + +if True: + + @api.model + def post_install_pos_localisation(self, companies=False): + # + # don't try to setup_defaults, because it will fail + # when executing the generation of records, in the openupgrade_records + # module. + # self = self.sudo() + # if not companies: + # companies = self.env['res.company'].search([]) + # for company in companies.filtered('chart_template_id'): + # pos_configs = self.search([('company_id', '=', company.id)]) + # pos_configs.setup_defaults(company) + pass + # + +PosConfig.post_install_pos_localisation = post_install_pos_localisation diff --git a/openupgrade_framework/odoo_patch/addons/stock/__init__.py b/openupgrade_framework/odoo_patch/addons/stock/__init__.py new file mode 100644 index 000000000000..b66d7f484cb1 --- /dev/null +++ b/openupgrade_framework/odoo_patch/addons/stock/__init__.py @@ -0,0 +1,17 @@ +from odoo.addons import stock + + +def pre_init_hook(cr): + # + # don't uninstall data as this breaks the analysis + # Origin of this code is https://github.com/odoo/odoo/issues/22243 + # env = api.Environment(cr, SUPERUSER_ID, {}) + # env['ir.model.data'].search([ + # ('model', 'like', '%stock%'), + # ('module', '=', 'stock') + # ]).unlink() + pass + # + + +stock.pre_init_hook = pre_init_hook diff --git a/openupgrade_framework/odoo_patch/odoo/__init__.py b/openupgrade_framework/odoo_patch/odoo/__init__.py new file mode 100644 index 000000000000..f5065ae34593 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/__init__.py @@ -0,0 +1,10 @@ +from . import modules +from . import service +from . import tools + +# Nothing todo the function, the function check_security didn't changed +from . import http + +# adapted to V14 +# TODO, OpenUpgrade maintainers : check if it's OK +from . import models diff --git a/openupgrade_framework/odoo_patch/odoo/http.py b/openupgrade_framework/odoo_patch/odoo/http.py new file mode 100644 index 000000000000..e11c558fb905 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/http.py @@ -0,0 +1,32 @@ +# flake8: noqa +# pylint: skip-file + +import odoo +from odoo.service import security +from odoo.http import SessionExpiredException, request, OpenERPSession + +if True: + def _check_security(self): + """ + Check the current authentication parameters to know if those are still + valid. This method should be called at each request. If the + authentication fails, a :exc:`SessionExpiredException` is raised. + """ + if not self.db or not self.uid: + raise SessionExpiredException("Session expired") + # We create our own environment instead of the request's one. + # to avoid creating it without the uid since request.uid isn't set yet + env = odoo.api.Environment(request.cr, self.uid, self.context) + # here we check if the session is still valid + if not security.check_session(self, env): + # + # When asking openupgrade_records to generate records + # over jsonrpc, a query on res_users in the call above locks this + # table for the sql operations that are triggered by the + # reinstallation of the base module + env.cr.rollback() + # + raise SessionExpiredException("Session expired") + + +OpenERPSession.check_security = _check_security diff --git a/openupgrade_framework/odoo_patch/odoo/models.py b/openupgrade_framework/odoo_patch/odoo/models.py new file mode 100644 index 000000000000..ee09595fb103 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/models.py @@ -0,0 +1,179 @@ +# flake8: noqa +# pylint: skip-file + +import odoo +import psycopg2 +from odoo import _ +from odoo.models import fix_import_export_id_paths, BaseModel, _logger +from odoo.addons.openupgrade_framework.openupgrade import openupgrade_log + + +if True: + def _load(self, fields, data): + """ + Attempts to load the data matrix, and returns a list of ids (or + ``False`` if there was an error and no id could be generated) and a + list of messages. + + The ids are those of the records created and saved (in database), in + the same order they were extracted from the file. They can be passed + directly to :meth:`~read` + + :param fields: list of fields to import, at the same index as the corresponding data + :type fields: list(str) + :param data: row-major matrix of data to import + :type data: list(list(str)) + :returns: {ids: list(int)|False, messages: [Message][, lastrow: int]} + """ + self.flush() + + # determine values of mode, current_module and noupdate + mode = self._context.get('mode', 'init') + current_module = self._context.get('module', '__import__') + noupdate = self._context.get('noupdate', False) + # add current module in context for the conversion of xml ids + self = self.with_context(_import_current_module=current_module) + + cr = self._cr + cr.execute('SAVEPOINT model_load') + + fields = [fix_import_export_id_paths(f) for f in fields] + fg = self.fields_get() + + ids = [] + messages = [] + ModelData = self.env['ir.model.data'] + + # list of (xid, vals, info) for records to be created in batch + batch = [] + batch_xml_ids = set() + # models in which we may have created / modified data, therefore might + # require flushing in order to name_search: the root model and any + # o2m + creatable_models = {self._name} + for field_path in fields: + if field_path[0] in (None, 'id', '.id'): + continue + model_fields = self._fields + if isinstance(model_fields[field_path[0]], odoo.fields.Many2one): + # this only applies for toplevel m2o (?) fields + if field_path[0] in (self.env.context.get('name_create_enabled_fieds') or {}): + creatable_models.add(model_fields[field_path[0]].comodel_name) + for field_name in field_path: + if field_name in (None, 'id', '.id'): + break + + if isinstance(model_fields[field_name], odoo.fields.One2many): + comodel = model_fields[field_name].comodel_name + creatable_models.add(comodel) + model_fields = self.env[comodel]._fields + + def flush(*, xml_id=None, model=None): + if not batch: + return + + assert not (xml_id and model), \ + "flush can specify *either* an external id or a model, not both" + + if xml_id and xml_id not in batch_xml_ids: + if xml_id not in self.env: + return + if model and model not in creatable_models: + return + + data_list = [ + dict(xml_id=xid, values=vals, info=info, noupdate=noupdate) + for xid, vals, info in batch + ] + batch.clear() + batch_xml_ids.clear() + + # try to create in batch + try: + with cr.savepoint(): + recs = self._load_records(data_list, mode == 'update') + ids.extend(recs.ids) + return + except psycopg2.InternalError as e: + # broken transaction, exit and hope the source error was already logged + if not any(message['type'] == 'error' for message in messages): + info = data_list[0]['info'] + messages.append(dict(info, type='error', message=_(u"Unknown database error: '%s'", e))) + return + except Exception: + pass + + errors = 0 + # try again, this time record by record + for i, rec_data in enumerate(data_list, 1): + try: + with cr.savepoint(): + rec = self._load_records([rec_data], mode == 'update') + ids.append(rec.id) + except psycopg2.Warning as e: + info = rec_data['info'] + messages.append(dict(info, type='warning', message=str(e))) + except psycopg2.Error as e: + info = rec_data['info'] + messages.append(dict(info, type='error', **PGERROR_TO_OE[e.pgcode](self, fg, info, e))) + # Failed to write, log to messages, rollback savepoint (to + # avoid broken transaction) and keep going + errors += 1 + except Exception as e: + _logger.debug("Error while loading record", exc_info=True) + info = rec_data['info'] + message = (_(u'Unknown error during import:') + u' %s: %s' % (type(e), e)) + moreinfo = _('Resolve other errors first') + messages.append(dict(info, type='error', message=message, moreinfo=moreinfo)) + # Failed for some reason, perhaps due to invalid data supplied, + # rollback savepoint and keep going + errors += 1 + if errors >= 10 and (errors >= i / 10): + messages.append({ + 'type': 'warning', + 'message': _(u"Found more than 10 errors and more than one error per 10 records, interrupted to avoid showing too many errors.") + }) + break + + # make 'flush' available to the methods below, in the case where XMLID + # resolution fails, for instance + flush_self = self.with_context(import_flush=flush) + + # TODO: break load's API instead of smuggling via context? + limit = self._context.get('_import_limit') + if limit is None: + limit = float('inf') + extracted = flush_self._extract_records(fields, data, log=messages.append, limit=limit) + + converted = flush_self._convert_records(extracted, log=messages.append) + + info = {'rows': {'to': -1}} + for id, xid, record, info in converted: + if xid: + xid = xid if '.' in xid else "%s.%s" % (current_module, xid) + batch_xml_ids.add(xid) + # + # log csv records + openupgrade_log.log_xml_id(self.env.cr, current_module, xid) + # + elif id: + record['id'] = id + batch.append((xid, record, info)) + + flush() + if any(message['type'] == 'error' for message in messages): + cr.execute('ROLLBACK TO SAVEPOINT model_load') + ids = False + # cancel all changes done to the registry/ormcache + self.pool.reset_changes() + + nextrow = info['rows']['to'] + 1 + if nextrow < limit: + nextrow = 0 + return { + 'ids': ids, + 'messages': messages, + 'nextrow': nextrow, + } + +BaseModel.load = _load diff --git a/openupgrade_framework/odoo_patch/odoo/modules/__init__.py b/openupgrade_framework/odoo_patch/odoo/modules/__init__.py new file mode 100644 index 000000000000..90de5b4ff4e7 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/modules/__init__.py @@ -0,0 +1,12 @@ +# Minor changes. (call to safe_eval changed) +# otherwise : adapted to V14 +from . import graph + +# A lot of changes in the core functions. +from . import loading + +# Adapted to V14 +from . import migration + +# Adapted to V14 +from . import registry diff --git a/openupgrade_framework/odoo_patch/odoo/modules/graph.py b/openupgrade_framework/odoo_patch/odoo/modules/graph.py new file mode 100644 index 000000000000..b0bedef3ea62 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/modules/graph.py @@ -0,0 +1,108 @@ +# flake8: noqa +# pylint: skip-file + +import logging +import odoo +import odoo.tools as tools +from odoo.tools.safe_eval import safe_eval + +from odoo.modules.graph import Graph + +_logger = logging.getLogger(__name__) + + +if True: + + def _update_from_db(self, cr): + if not len(self): + return + # update the graph with values from the database (if exist) + ## First, we set the default values for each package in graph + additional_data = {key: {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None} for key in self.keys()} + ## Then we get the values from the database + cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version' + ' FROM ir_module_module' + ' WHERE name IN %s',(tuple(additional_data),) + ) + + ## and we update the default values with values from the database + additional_data.update((x['name'], x) for x in cr.dictfetchall()) + + # + # Prevent reloading of demo data from the new version on major upgrade + if ('base' in self and additional_data['base']['dbdemo'] and + additional_data['base']['installed_version'] < + odoo.release.major_version): + cr.execute("UPDATE ir_module_module SET demo = false") + for data in additional_data.values(): + data['dbdemo'] = False + # + + for package in self.values(): + for k, v in additional_data[package.name].items(): + setattr(package, k, v) + + + def _add_modules(self, cr, module_list, force=None): + if force is None: + force = [] + packages = [] + len_graph = len(self) + + # + # force additional dependencies for the upgrade process if given + # in config file + forced_deps = tools.config.get_misc('openupgrade', 'force_deps', '{}') + forced_deps = tools.config.get_misc('openupgrade', + 'force_deps_' + odoo.release.version, + forced_deps) + forced_deps = safe_eval(forced_deps) + # + + for module in module_list: + # This will raise an exception if no/unreadable descriptor file. + # NOTE The call to load_information_from_description_file is already + # done by db.initialize, so it is possible to not do it again here. + info = odoo.modules.module.load_information_from_description_file(module) + if info and info['installable']: + # + info['depends'].extend(forced_deps.get(module, [])) + # + packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version + elif module != 'studio_customization': + _logger.warning('module %s: not installable, skipped', module) + + dependencies = dict([(p, info['depends']) for p, info in packages]) + current, later = set([p for p, info in packages]), set() + + while packages and current > later: + package, info = packages[0] + deps = info['depends'] + + # if all dependencies of 'package' are already in the graph, add 'package' in the graph + if all(dep in self for dep in deps): + if not package in current: + packages.pop(0) + continue + later.clear() + current.remove(package) + node = self.add_node(package, info) + for kind in ('init', 'demo', 'update'): + if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force: + setattr(node, kind, True) + else: + later.add(package) + packages.append((package, info)) + packages.pop(0) + + self.update_from_db(cr) + + for package in later: + unmet_deps = [p for p in dependencies[package] if p not in self] + _logger.info('module %s: Unmet dependencies: %s', package, ', '.join(unmet_deps)) + + return len(self) - len_graph + + +Graph.update_from_db = _update_from_db +Graph.add_modules = _add_modules diff --git a/openupgrade_framework/odoo_patch/odoo/modules/loading.py b/openupgrade_framework/odoo_patch/odoo/modules/loading.py new file mode 100644 index 000000000000..eb25c80ade5e --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/modules/loading.py @@ -0,0 +1,556 @@ +# flake8: noqa +# pylint: skip-file + +import itertools +import logging +import sys +import time + +import odoo +import odoo.tools as tools +from odoo import api, SUPERUSER_ID +from odoo.modules import loading +from odoo.modules.module import adapt_version, load_openerp_module, initialize_sys_path + +from odoo.modules.loading import load_data, load_demo, _check_module_names +from odoo.addons.openupgrade_framework.openupgrade import openupgrade_loading + +import os + +_logger = logging.getLogger(__name__) +_test_logger = logging.getLogger('odoo.tests') + + +def _load_module_graph(cr, graph, status=None, perform_checks=True, + skip_modules=None, report=None, models_to_check=None, upg_registry=None): + # + """Migrates+Updates or Installs all module nodes from ``graph`` + :param graph: graph of module nodes to load + :param status: deprecated parameter, unused, left to avoid changing signature in 8.0 + :param perform_checks: whether module descriptors should be checked for validity (prints warnings + for same cases) + :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped + :return: list of modules that were installed or updated + """ + if skip_modules is None: + skip_modules = [] + + if models_to_check is None: + models_to_check = set() + + processed_modules = [] + loaded_modules = [] + registry = odoo.registry(cr.dbname) + migrations = odoo.modules.migration.MigrationManager(cr, graph) + module_count = len(graph) + _logger.info('loading %d modules...', module_count) + + # + # suppress commits to have the upgrade of one module in just one transaction + cr.commit_org = cr.commit + cr.commit = lambda *args: None + cr.rollback_org = cr.rollback + cr.rollback = lambda *args: None + # + + # register, instantiate and initialize models for each modules + t0 = time.time() + loading_extra_query_count = odoo.sql_db.sql_counter + loading_cursor_query_count = cr.sql_log_count + + models_updated = set() + + for index, package in enumerate(graph, 1): + module_name = package.name + module_id = package.id + + # + if module_name in skip_modules or module_name in loaded_modules: + # + continue + + module_t0 = time.time() + module_cursor_query_count = cr.sql_log_count + module_extra_query_count = odoo.sql_db.sql_counter + + needs_update = ( + hasattr(package, "init") + or hasattr(package, "update") + or package.state in ("to install", "to upgrade") + ) + module_log_level = logging.DEBUG + if needs_update: + module_log_level = logging.INFO + _logger.log(module_log_level, 'Loading module %s (%d/%d)', module_name, index, module_count) + + if needs_update: + if package.name != 'base': + registry.setup_models(cr) + migrations.migrate_module(package, 'pre') + if package.name != 'base': + env = api.Environment(cr, SUPERUSER_ID, {}) + env['base'].flush() + + load_openerp_module(package.name) + + new_install = package.state == 'to install' + if new_install: + py_module = sys.modules['odoo.addons.%s' % (module_name,)] + pre_init = package.info.get('pre_init_hook') + if pre_init: + getattr(py_module, pre_init)(cr) + + model_names = registry.load(cr, package) + + mode = 'update' + if hasattr(package, 'init') or package.state == 'to install': + mode = 'init' + + loaded_modules.append(package.name) + if needs_update: + models_updated |= set(model_names) + models_to_check -= set(model_names) + registry.setup_models(cr) + # + # rebuild the local registry based on the loaded models + local_registry = {} + env = api.Environment(cr, SUPERUSER_ID, {}) + for model in env.values(): + if not model._auto: + continue + openupgrade_loading.log_model(model, local_registry) + openupgrade_loading.compare_registries( + cr, package.name, upg_registry, local_registry) + # + + registry.init_models(cr, model_names, {'module': package.name}, new_install) + elif package.state != 'to remove': + # The current module has simply been loaded. The models extended by this module + # and for which we updated the schema, must have their schema checked again. + # This is because the extension may have changed the model, + # e.g. adding required=True to an existing field, but the schema has not been + # updated by this module because it's not marked as 'to upgrade/to install'. + models_to_check |= set(model_names) & models_updated + + idref = {} + + if needs_update: + env = api.Environment(cr, SUPERUSER_ID, {}) + # Can't put this line out of the loop: ir.module.module will be + # registered by init_models() above. + module = env['ir.module.module'].browse(module_id) + + if perform_checks: + module._check() + + if package.state == 'to upgrade': + # upgrading the module information + module.write(module.get_values_from_terp(package.data)) + load_data(cr, idref, mode, kind='data', package=package) + demo_loaded = package.dbdemo = load_demo(cr, package, idref, mode) + cr.execute('update ir_module_module set demo=%s where id=%s', (demo_loaded, module_id)) + module.invalidate_cache(['demo']) + + # + # add 'try' block for logging exceptions + # as errors in post scripts seem to be dropped + try: + migrations.migrate_module(package, 'post') + except Exception as exc: + _logger.error('Error executing post migration script for module %s: %s', + package, exc) + raise + # + + # Update translations for all installed languages + overwrite = odoo.tools.config["overwrite_existing_translations"] + module.with_context(overwrite=overwrite)._update_translations() + + if package.name is not None: + registry._init_modules.add(package.name) + + if needs_update: + if new_install: + post_init = package.info.get('post_init_hook') + if post_init: + getattr(py_module, post_init)(cr, registry) + + if mode == 'update': + # validate the views that have not been checked yet + env['ir.ui.view']._validate_module_views(module_name) + + # need to commit any modification the module's installation or + # update made to the schema or data so the tests can run + # (separately in their own transaction) + # + # commit after processing every module as well, for + # easier debugging and continuing an interrupted migration + cr.commit_org() + # + # run tests + if os.environ.get('OPENUPGRADE_TESTS') and package.name is not None: + prefix = '.migrations' + registry.openupgrade_test_prefixes[package.name] = prefix + report.record_result(odoo.modules.module.run_unit_tests(module_name, openupgrade_prefix=prefix)) + # + # commit module_n state and version immediatly + # to avoid invalid database state if module_n+1 raises an + # exception + cr.commit_org() + # + + package.load_state = package.state + package.load_version = package.installed_version + package.state = 'installed' + for kind in ('init', 'demo', 'update'): + if hasattr(package, kind): + delattr(package, kind) + module.flush() + + extra_queries = odoo.sql_db.sql_counter - module_extra_query_count - test_queries + extras = [] + if test_queries: + extras.append(f'+{test_queries} test') + if extra_queries: + extras.append(f'+{extra_queries} other') + _logger.log( + module_log_level, "Module %s loaded in %.2fs%s, %s queries%s", + module_name, time.time() - module_t0, + f' (incl. {test_time:.2f}s test)' if test_time else '', + cr.sql_log_count - module_cursor_query_count, + f' ({", ".join(extras)})' if extras else '' + ) + if test_results and not test_results.wasSuccessful(): + _logger.error( + "Module %s: %d failures, %d errors of %d tests", + module_name, len(test_results.failures), len(test_results.errors), + test_results.testsRun + ) + + _logger.runbot("%s modules loaded in %.2fs, %s queries (+%s extra)", + len(graph), + time.time() - t0, + cr.sql_log_count - loading_cursor_query_count, + odoo.sql_db.sql_counter - loading_extra_query_count) # extra queries: testes, notify, any other closed cursor + + # + # restore commit method + cr.commit = cr.commit_org + cr.commit() + # + + return loaded_modules, processed_modules + + +def _load_marked_modules(cr, graph, states, force, progressdict, report, + loaded_modules, perform_checks, models_to_check=None, upg_registry=None): + # + """Loads modules marked with ``states``, adding them to ``graph`` and + ``loaded_modules`` and returns a list of installed/upgraded modules.""" + + if models_to_check is None: + models_to_check = set() + + processed_modules = [] + while True: + cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),)) + module_list = [name for (name,) in cr.fetchall() if name not in graph] + # + module_list = openupgrade_loading.add_module_dependencies(cr, module_list) + # + if not module_list: + break + graph.add_modules(cr, module_list, force) + _logger.debug('Updating graph with %d more modules', len(module_list)) + # + # add upg_registry + loaded, processed = _load_module_graph( + cr, graph, progressdict, report=report, skip_modules=loaded_modules, + perform_checks=perform_checks, models_to_check=models_to_check, + upg_registry=upg_registry, + ) + # + processed_modules.extend(processed) + loaded_modules.extend(loaded) + if not processed: + break + return processed_modules + + +def _load_modules(db, force_demo=False, status=None, update_module=False): + initialize_sys_path() + + force = [] + if force_demo: + force.append('demo') + + # + upg_registry = {} + # + + models_to_check = set() + + with db.cursor() as cr: + if not odoo.modules.db.is_initialized(cr): + if not update_module: + _logger.error("Database %s not initialized, you can force it with `-i base`", cr.dbname) + return + _logger.info("init db") + odoo.modules.db.initialize(cr) + update_module = True # process auto-installed modules + tools.config["init"]["all"] = 1 + if not tools.config['without_demo']: + tools.config["demo"]['all'] = 1 + + # This is a brand new registry, just created in + # odoo.modules.registry.Registry.new(). + registry = odoo.registry(cr.dbname) + + if 'base' in tools.config['update'] or 'all' in tools.config['update']: + cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed')) + + # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) + graph = odoo.modules.graph.Graph() + graph.add_module(cr, 'base', force) + if not graph: + _logger.critical('module base cannot be loaded! (hint: verify addons-path)') + raise ImportError('Module `base` cannot be loaded! (hint: verify addons-path)') + + # processed_modules: for cleanup step after install + # loaded_modules: to avoid double loading + report = registry._assertion_report + # + # add upg_registry + loaded_modules, processed_modules = _load_module_graph( + cr, graph, status, perform_checks=update_module, + report=report, models_to_check=models_to_check, upg_registry=upg_registry) + + # + load_lang = tools.config.pop('load_language') + if load_lang or update_module: + # some base models are used below, so make sure they are set up + registry.setup_models(cr) + + if load_lang: + for lang in load_lang.split(','): + tools.load_language(cr, lang) + + # STEP 2: Mark other modules to be loaded/updated + if update_module: + env = api.Environment(cr, SUPERUSER_ID, {}) + Module = env['ir.module.module'] + _logger.info('updating modules list') + Module.update_list() + + _check_module_names(cr, itertools.chain(tools.config['init'], tools.config['update'])) + + module_names = [k for k, v in tools.config['init'].items() if v] + if module_names: + modules = Module.search([('state', '=', 'uninstalled'), ('name', 'in', module_names)]) + if modules: + modules.button_install() + + module_names = [k for k, v in tools.config['update'].items() if v] + if module_names: + # + # in standard Odoo, '--update all' just means: + # '--update base + upward (installed) dependencies. This breaks + # the chain when new glue modules are encountered. + # E.g. purchase in 8.0 depends on stock_account and report, + # both of which are new. They may be installed, but purchase as + # an upward dependency is not selected for upgrade. + # Therefore, explicitely select all installed modules for + # upgrading in OpenUpgrade in that case. + domain = [('state', '=', 'installed')] + if 'all' not in module_names: + domain.append(('name', 'in', module_names)) + modules = Module.search(domain) + # + if modules: + modules.button_upgrade() + + cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base')) + Module.invalidate_cache(['state']) + Module.flush() + + # STEP 3: Load marked modules (skipping base which was done in STEP 1) + # IMPORTANT: this is done in two parts, first loading all installed or + # partially installed modules (i.e. installed/to upgrade), to + # offer a consistent system to the second part: installing + # newly selected modules. + # We include the modules 'to remove' in the first step, because + # they are part of the "currently installed" modules. They will + # be dropped in STEP 6 later, before restarting the loading + # process. + # IMPORTANT 2: We have to loop here until all relevant modules have been + # processed, because in some rare cases the dependencies have + # changed, and modules that depend on an uninstalled module + # will not be processed on the first pass. + # It's especially useful for migrations. + previously_processed = -1 + while previously_processed < len(processed_modules): + previously_processed = len(processed_modules) + # + # add upg_registry + processed_modules += _load_marked_modules(cr, graph, + ['installed', 'to upgrade', 'to remove'], + force, status, report, loaded_modules, update_module, models_to_check, upg_registry) + # + if update_module: + # + # add upg_registry + processed_modules += _load_marked_modules(cr, graph, + ['to install'], force, status, report, + loaded_modules, update_module, models_to_check, upg_registry) + # + # check that new module dependencies have been properly installed after a migration/upgrade + cr.execute("SELECT name from ir_module_module WHERE state IN ('to install', 'to upgrade')") + module_list = [name for (name,) in cr.fetchall()] + if module_list: + _logger.error("Some modules have inconsistent states, some dependencies may be missing: %s", sorted(module_list)) + + # check that all installed modules have been loaded by the registry after a migration/upgrade + cr.execute("SELECT name from ir_module_module WHERE state = 'installed' and name != 'studio_customization'") + module_list = [name for (name,) in cr.fetchall() if name not in graph] + if module_list: + _logger.error("Some modules are not loaded, some dependencies or manifest may be missing: %s", sorted(module_list)) + + registry.loaded = True + registry.setup_models(cr) + + # STEP 3.5: execute migration end-scripts + migrations = odoo.modules.migration.MigrationManager(cr, graph) + for package in graph: + migrations.migrate_module(package, 'end') + + # STEP 3.6: apply remaining constraints in case of an upgrade + registry.finalize_constraints() + + # STEP 4: Finish and cleanup installations + if processed_modules: + env = api.Environment(cr, SUPERUSER_ID, {}) + cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""") + for (model, name) in cr.fetchall(): + if model in registry and not registry[model]._abstract: + _logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,base.group_user,1,0,0,0', + model, model.replace('.', '_'), model.replace('.', '_'), model.replace('.', '_')) + + cr.execute("SELECT model from ir_model") + for (model,) in cr.fetchall(): + if model in registry: + env[model]._check_removed_columns(log=True) + elif _logger.isEnabledFor(logging.INFO): # more an info that a warning... + _logger.runbot("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model) + + # Cleanup orphan records + env['ir.model.data']._process_end(processed_modules) + env['base'].flush() + + for kind in ('init', 'demo', 'update'): + tools.config[kind] = {} + + # STEP 5: Uninstall modules to remove + if update_module: + # Remove records referenced from ir_model_data for modules to be + # removed (and removed the references from ir_model_data). + cr.execute("SELECT name, id FROM ir_module_module WHERE state=%s", ('to remove',)) + modules_to_remove = dict(cr.fetchall()) + if modules_to_remove: + env = api.Environment(cr, SUPERUSER_ID, {}) + pkgs = reversed([p for p in graph if p.name in modules_to_remove]) + for pkg in pkgs: + uninstall_hook = pkg.info.get('uninstall_hook') + if uninstall_hook: + py_module = sys.modules['odoo.addons.%s' % (pkg.name,)] + getattr(py_module, uninstall_hook)(cr, registry) + + Module = env['ir.module.module'] + Module.browse(modules_to_remove.values()).module_uninstall() + # Recursive reload, should only happen once, because there should be no + # modules to remove next time + cr.commit() + _logger.info('Reloading registry once more after uninstalling modules') + api.Environment.reset() + registry = odoo.modules.registry.Registry.new( + cr.dbname, force_demo, status, update_module + ) + registry.check_tables_exist(cr) + cr.commit() + return registry + + # STEP 5.5: Verify extended fields on every model + # This will fix the schema of all models in a situation such as: + # - module A is loaded and defines model M; + # - module B is installed/upgraded and extends model M; + # - module C is loaded and extends model M; + # - module B and C depend on A but not on each other; + # The changes introduced by module C are not taken into account by the upgrade of B. + if models_to_check: + registry.init_models(cr, list(models_to_check), {'models_to_check': True}) + + # STEP 6: verify custom views on every model + if update_module: + env = api.Environment(cr, SUPERUSER_ID, {}) + env['res.groups']._update_user_groups_view() + View = env['ir.ui.view'] + for model in registry: + try: + View._validate_custom_views(model) + except Exception as e: + _logger.warning('invalid custom view(s) for model %s: %s', model, tools.ustr(e)) + + if report.wasSuccessful(): + _logger.info('Modules loaded.') + else: + _logger.error('At least one test failed when loading the modules.') + + # STEP 8: call _register_hook on every model + # This is done *exactly once* when the registry is being loaded. See the + # management of those hooks in `Registry.setup_models`: all the calls to + # setup_models() done here do not mess up with hooks, as registry.ready + # is False. + env = api.Environment(cr, SUPERUSER_ID, {}) + for model in env.values(): + model._register_hook() + env['base'].flush() + + # STEP 9: save installed/updated modules for post-install tests + registry.updated_modules += processed_modules + +loading.load_module_graph = _load_module_graph +loading.load_marked_modules = _load_marked_modules +loading.load_modules = _load_modules +odoo.modules.load_modules = _load_modules diff --git a/openupgrade_framework/odoo_patch/odoo/modules/migration.py b/openupgrade_framework/odoo_patch/odoo/modules/migration.py new file mode 100644 index 000000000000..0346c2b8c559 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/modules/migration.py @@ -0,0 +1,118 @@ +# flake8: noqa +# pylint: skip-file + +import logging +import os +from os.path import join as opj +import odoo.release as release +from odoo.tools.parse_version import parse_version + +import odoo +from odoo.modules.migration import load_script +from odoo.modules import migration + +_logger = logging.getLogger(__name__) + + +if True: + def _migrate_module(self, pkg, stage): + assert stage in ('pre', 'post', 'end') + stageformat = { + 'pre': '[>%s]', + 'post': '[%s>]', + 'end': '[$%s]', + } + state = pkg.state if stage in ('pre', 'post') else getattr(pkg, 'load_state', None) + + # + # In openupgrade, also run migration scripts upon installation. + # We want to always pass in pre and post migration files and use a new + # argument in the migrate decorator (explained in the docstring) + # to decide if we want to do something if a new module is installed + # during the migration. + if not (hasattr(pkg, 'update') or state in ('to upgrade', 'to install')): + # + return + + def convert_version(version): + if version.count('.') >= 2: + return version # the version number already containt the server version + return "%s.%s" % (release.major_version, version) + + def _get_migration_versions(pkg, stage): + versions = sorted({ + ver + for lv in self.migrations[pkg.name].values() + for ver, lf in lv.items() + if lf + }, key=lambda k: parse_version(convert_version(k))) + if "0.0.0" in versions: + # reorder versions + versions.remove("0.0.0") + if stage == "pre": + versions.insert(0, "0.0.0") + else: + versions.append("0.0.0") + return versions + + def _get_migration_files(pkg, version, stage): + """ return a list of migration script files + """ + m = self.migrations[pkg.name] + lst = [] + + mapping = { + 'module': opj(pkg.name, 'migrations'), + 'module_upgrades': opj(pkg.name, 'upgrades'), + } + + for path in odoo.upgrade.__path__: + if os.path.exists(opj(path, pkg.name)): + mapping['upgrade'] = opj(path, pkg.name) + break + + for x in mapping: + if version in m.get(x): + for f in m[x][version]: + if not f.startswith(stage + '-'): + continue + lst.append(opj(mapping[x], version, f)) + lst.sort() + return lst + + installed_version = getattr(pkg, 'load_version', pkg.installed_version) or '' + parsed_installed_version = parse_version(installed_version) + current_version = parse_version(convert_version(pkg.data['version'])) + + versions = _get_migration_versions(pkg, stage) + + for version in versions: + if ((version == "0.0.0" and parsed_installed_version < current_version) + or parsed_installed_version < parse_version(convert_version(version)) <= current_version): + + strfmt = {'addon': pkg.name, + 'stage': stage, + 'version': stageformat[stage] % version, + } + + for pyfile in _get_migration_files(pkg, version, stage): + name, ext = os.path.splitext(os.path.basename(pyfile)) + if ext.lower() != '.py': + continue + mod = None + try: + mod = load_script(pyfile, name) + _logger.info('module %(addon)s: Running migration %(version)s %(name)s' % dict(strfmt, name=mod.__name__)) + migrate = mod.migrate + except ImportError: + _logger.exception('module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % dict(strfmt, file=pyfile)) + raise + except AttributeError: + _logger.error('module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt) + else: + migrate(self.cr, installed_version) + finally: + if mod: + del mod + +migration.migrate_module = _migrate_module diff --git a/openupgrade_framework/odoo_patch/odoo/modules/registry.py b/openupgrade_framework/odoo_patch/odoo/modules/registry.py new file mode 100644 index 000000000000..4c5f50d4e714 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/modules/registry.py @@ -0,0 +1,58 @@ +# flake8: noqa +# pylint: skip-file + +from collections import deque +from contextlib import closing +import odoo +from odoo.tools.lru import LRU + +from odoo.modules import registry + + +if True: + + def _init(self, db_name): + self.models = {} # model name/model instance mapping + self._sql_constraints = set() + self._init = True + self._assertion_report = odoo.tests.runner.OdooTestResult() + self._fields_by_model = None + self._ordinary_tables = None + self._constraint_queue = deque() + self.__cache = LRU(8192) + + # modules fully loaded (maintained during init phase by `loading` module) + self._init_modules = set() + self.updated_modules = [] # installed/updated modules + # + self.openupgrade_test_prefixes = {} + # + self.loaded_xmlids = set() + + self.db_name = db_name + self._db = odoo.sql_db.db_connect(db_name) + + # cursor for test mode; None means "normal" mode + self.test_cr = None + self.test_lock = None + + # Indicates that the registry is + self.loaded = False # whether all modules are loaded + self.ready = False # whether everything is set up + + # Inter-process signaling: + # The `base_registry_signaling` sequence indicates the whole registry + # must be reloaded. + # The `base_cache_signaling sequence` indicates all caches must be + # invalidated (i.e. cleared). + self.registry_sequence = None + self.cache_sequence = None + + # Flags indicating invalidation of the registry or the cache. + self.registry_invalidated = False + self.cache_invalidated = False + + with closing(self.cursor()) as cr: + self.has_unaccent = odoo.modules.db.has_unaccent(cr) + +registry.init = _init diff --git a/openupgrade_framework/odoo_patch/odoo/service/__init__.py b/openupgrade_framework/odoo_patch/odoo/service/__init__.py new file mode 100644 index 000000000000..a96314d0f684 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/service/__init__.py @@ -0,0 +1,4 @@ +# Import disabled, because the function run_unit_tests() +# disappeared in V14. +# TODO: OpenUpgrade Core maintainers : FIXME. +# from . import server diff --git a/openupgrade_framework/odoo_patch/odoo/service/server.py b/openupgrade_framework/odoo_patch/odoo/service/server.py new file mode 100644 index 000000000000..a2a998e69df3 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/service/server.py @@ -0,0 +1,71 @@ +# flake8: noqa +# pylint: skip-file + +import logging +import os +import time + +import odoo +from odoo.tools import config +from odoo.modules.registry import Registry + +from odoo.service import server +from odoo.service.server import load_test_file_py + +_logger = logging.getLogger(__name__) + + +def preload_registries(dbnames): + """ Preload a registries, possibly run a test file.""" + # TODO: move all config checks to args dont check tools.config here + dbnames = dbnames or [] + rc = 0 + for dbname in dbnames: + try: + update_module = config['init'] or config['update'] + registry = Registry.new(dbname, update_module=update_module) + + # run test_file if provided + if config['test_file']: + test_file = config['test_file'] + if not os.path.isfile(test_file): + _logger.warning('test file %s cannot be found', test_file) + elif not test_file.endswith('py'): + _logger.warning('test file %s is not a python file', test_file) + else: + _logger.info('loading test file %s', test_file) + with odoo.api.Environment.manage(): + load_test_file_py(registry, test_file) + + # run post-install tests + if config['test_enable']: + t0 = time.time() + t0_sql = odoo.sql_db.sql_counter + module_names = (registry.updated_modules if update_module else + sorted(registry._init_modules)) + _logger.info("Starting post tests") + tests_before = registry._assertion_report.testsRun + with odoo.api.Environment.manage(): + for module_name in module_names: + result = loader.run_suite(loader.make_suite(module_name, 'post_install'), module_name) + registry._assertion_report.update(result) + # + # run deferred unit tests + for module_name, prefix in registry.openupgrade_test_prefixes: + result = run_unit_tests(module_name, position='post_install', openupgrade_prefix=prefix) + registry._assertion_report.record_result(result) + # + _logger.info("%d post-tests in %.2fs, %s queries", + registry._assertion_report.testsRun - tests_before, + time.time() - t0, + odoo.sql_db.sql_counter - t0_sql) + + if not registry._assertion_report.wasSuccessful(): + rc += 1 + except Exception: + _logger.critical('Failed to initialize database `%s`.', dbname, exc_info=True) + return -1 + return rc + + +server.preload_registries = preload_registries diff --git a/openupgrade_framework/odoo_patch/odoo/tools/__init__.py b/openupgrade_framework/odoo_patch/odoo/tools/__init__.py new file mode 100644 index 000000000000..6ad156515dc3 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/tools/__init__.py @@ -0,0 +1,2 @@ +from . import convert +from . import view_validation diff --git a/openupgrade_framework/odoo_patch/odoo/tools/convert.py b/openupgrade_framework/odoo_patch/odoo/tools/convert.py new file mode 100644 index 000000000000..49531bfc429e --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/tools/convert.py @@ -0,0 +1,23 @@ +# flake8: noqa +# pylint: skip-file + +from odoo.addons.openupgrade_framework.openupgrade import openupgrade_log + +from odoo.tools.convert import xml_import + +if True: + + def __test_xml_id(self, xml_id): + if '.' in xml_id: + module, id = xml_id.split('.', 1) + assert '.' not in id, """The ID reference "%s" must contain +maximum one dot. They are used to refer to other modules ID, in the +form: module.record_id""" % (xml_id,) + if module != self.module: + modcnt = self.env['ir.module.module'].search_count([('name', '=', module), ('state', '=', 'installed')]) + assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,) + + # OpenUpgrade: log entry of XML imports + openupgrade_log.log_xml_id(self.env.cr, self.module, xml_id) + +xml_import._test_xml_id = __test_xml_id diff --git a/openupgrade_framework/odoo_patch/odoo/tools/view_validation.py b/openupgrade_framework/odoo_patch/odoo/tools/view_validation.py new file mode 100644 index 000000000000..e6c8243241af --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/tools/view_validation.py @@ -0,0 +1,29 @@ +# flake8: noqa +# pylint: skip-file + +# from odoo.addons.openupgrade_framework.openupgrade import openupgrade_log + +from odoo.tools import view_validation +from odoo.tools.view_validation import _validators, _logger + + +def _valid_view(arch, **kwargs): + for pred in _validators[arch.tag]: + # + # Do not raise blocking error, because it's normal to + # have inconsistent views in an openupgrade process + check = pred(arch, **kwargs) or 'Warning' + # + if not check: + _logger.error("Invalid XML: %s", pred.__doc__) + return False + if check == "Warning": + # + # Don't show this warning as useless and too much verbose + # _logger.warning("Invalid XML: %s", pred.__doc__) + # + return "Warning" + return True + + +view_validation.valid_view = _valid_view diff --git a/openupgrade_framework/openupgrade/__init__.py b/openupgrade_framework/openupgrade/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openupgrade_framework/openupgrade/openupgrade_loading.py b/openupgrade_framework/openupgrade/openupgrade_loading.py new file mode 100644 index 000000000000..ca3e1d43067d --- /dev/null +++ b/openupgrade_framework/openupgrade/openupgrade_loading.py @@ -0,0 +1,318 @@ +# Copyright 2011-2015 Therp BV +# Copyright 2016-2019 Opener B.V. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +# flake8: noqa: C901 + +import logging + +from openupgradelib.openupgrade_tools import table_exists + +from odoo import release +from odoo.modules.module import get_module_path +from odoo.tools.safe_eval import safe_eval +from odoo.tools.config import config + +# A collection of functions used in +# odoo/modules/loading.py + +logger = logging.getLogger("OpenUpgrade") + + +def add_module_dependencies(cr, module_list): + """ + Select (new) dependencies from the modules in the list + so that we can inject them into the graph at upgrade + time. Used in the modified OpenUpgrade Server, + not to be called from migration scripts + + Also take the OpenUpgrade configuration directives 'forced_deps' + and 'autoinstall' into account. From any additional modules + that these directives can add, the dependencies are added as + well (but these directives are not checked for the occurrence + of any of the dependencies). + """ + if not module_list: + return module_list + + modules_in = list(module_list) + forced_deps = safe_eval( + config.get_misc( + "openupgrade", + "forced_deps_" + release.version, + config.get_misc("openupgrade", "forced_deps", "{}"), + ) + ) + + autoinstall = safe_eval( + config.get_misc( + "openupgrade", + "autoinstall_" + release.version, + config.get_misc("openupgrade", "autoinstall", "{}"), + ) + ) + + for module in list(module_list): + module_list += forced_deps.get(module, []) + module_list += autoinstall.get(module, []) + + module_list = list(set(module_list)) + + dependencies = module_list + while dependencies: + cr.execute( + """ + SELECT DISTINCT dep.name + FROM + ir_module_module, + ir_module_module_dependency dep + WHERE + module_id = ir_module_module.id + AND ir_module_module.name in %s + AND dep.name not in %s + """, + ( + tuple(dependencies), + tuple(module_list), + ), + ) + + dependencies = [x[0] for x in cr.fetchall()] + module_list += dependencies + + # Select auto_install modules of which all dependencies + # are fulfilled based on the modules we know are to be + # installed + cr.execute( + """ + SELECT name from ir_module_module WHERE state IN %s + """, + (("installed", "to install", "to upgrade"),), + ) + modules = list(set(module_list + [row[0] for row in cr.fetchall()])) + cr.execute( + """ + SELECT name from ir_module_module m + WHERE auto_install IS TRUE + AND state = 'uninstalled' + AND NOT EXISTS( + SELECT id FROM ir_module_module_dependency d + WHERE d.module_id = m.id + AND name NOT IN %s) + """, + (tuple(modules),), + ) + auto_modules = [row[0] for row in cr.fetchall() if get_module_path(row[0])] + if auto_modules: + logger.info("Selecting autoinstallable modules %s", ",".join(auto_modules)) + module_list += auto_modules + + # Set proper state for new dependencies so that any init scripts are run + cr.execute( + """ + UPDATE ir_module_module SET state = 'to install' + WHERE name IN %s AND name NOT IN %s AND state = 'uninstalled' + """, + (tuple(module_list), tuple(modules_in)), + ) + return module_list + + +def log_model(model, local_registry): + """ + OpenUpgrade: Store the characteristics of the BaseModel and its fields + in the local registry, so that we can compare changes with the + main registry + """ + + if not model._name: + return + + typemap = {"monetary": "float"} + + # Deferred import to prevent import loop + from odoo import models + + # persistent models only + if isinstance(model, models.TransientModel): + return + + def isfunction(model, k): + if ( + model._fields[k].compute + and not model._fields[k].related + and not model._fields[k].company_dependent + ): + return "function" + return "" + + def isproperty(model, k): + if model._fields[k].company_dependent: + return "property" + return "" + + def isrelated(model, k): + if model._fields[k].related: + return "related" + return "" + + def _get_relation(v): + if v.type in ("many2many", "many2one", "one2many"): + return v.comodel_name + elif v.type == "many2one_reference": + return v.model_field + else: + return "" + + model_registry = local_registry.setdefault(model._name, {}) + if model._inherits: + model_registry["_inherits"] = {"_inherits": str(model._inherits)} + for k, v in model._fields.items(): + properties = { + "type": typemap.get(v.type, v.type), + "isfunction": isfunction(model, k), + "isproperty": isproperty(model, k), + "isrelated": isrelated(model, k), + "relation": _get_relation(v), + "table": v.relation if v.type == "many2many" else "", + "required": v.required and "required" or "", + "stored": v.store and "stored" or "", + "selection_keys": "", + "req_default": "", + "hasdefault": model._fields[k].default and "hasdefault" or "", + "inherits": "", + } + if v.type == "selection": + if isinstance(v.selection, (tuple, list)): + properties["selection_keys"] = str(sorted([x[0] for x in v.selection])) + else: + properties["selection_keys"] = "function" + elif v.type == "binary": + properties["attachment"] = str(getattr(v, "attachment", False)) + default = model._fields[k].default + if v.required and default: + if ( + callable(default) + or isinstance(default, str) + and getattr(model._fields[k], default, False) + and callable(getattr(model._fields[k], default)) + ): + # todo: in OpenERP 5 (and in 6 as well), + # literals are wrapped in a lambda function + properties["req_default"] = "function" + else: + properties["req_default"] = str(default) + for key, value in properties.items(): + if value: + model_registry.setdefault(k, {})[key] = value + + +def get_record_id(cr, module, model, field, mode): + """ + OpenUpgrade: get or create the id from the record table matching + the key parameter values + """ + cr.execute( + "SELECT id FROM openupgrade_record " + "WHERE module = %s AND model = %s AND " + "field = %s AND mode = %s AND type = %s", + (module, model, field, mode, "field"), + ) + record = cr.fetchone() + if record: + return record[0] + cr.execute( + "INSERT INTO openupgrade_record " + "(module, model, field, mode, type) " + "VALUES (%s, %s, %s, %s, %s)", + (module, model, field, mode, "field"), + ) + cr.execute( + "SELECT id FROM openupgrade_record " + "WHERE module = %s AND model = %s AND " + "field = %s AND mode = %s AND type = %s", + (module, model, field, mode, "field"), + ) + return cr.fetchone()[0] + + +def compare_registries(cr, module, registry, local_registry): + """ + OpenUpgrade: Compare the local registry with the global registry, + log any differences and merge the local registry with + the global one. + """ + if not table_exists(cr, "openupgrade_record"): + return + for model, flds in local_registry.items(): + registry.setdefault(model, {}) + for field, attributes in flds.items(): + old_field = registry[model].setdefault(field, {}) + mode = old_field and "modify" or "create" + record_id = False + for key, value in attributes.items(): + if key not in old_field or old_field[key] != value: + if not record_id: + record_id = get_record_id(cr, module, model, field, mode) + cr.execute( + "SELECT id FROM openupgrade_attribute " + "WHERE name = %s AND value = %s AND " + "record_id = %s", + (key, value, record_id), + ) + if not cr.fetchone(): + cr.execute( + "INSERT INTO openupgrade_attribute " + "(name, value, record_id) VALUES (%s, %s, %s)", + (key, value, record_id), + ) + old_field[key] = value + + +def update_field_xmlid(model, field): + """OpenUpgrade edit start: In rare cases, an old module defined a field + on a model that is not defined in another module earlier in the + chain of inheritance. Then we need to assign the ir.model.fields' + xmlid to this other module, otherwise the column would be dropped + when uninstalling the first module. + An example is res.partner#display_name defined in 7.0 by + account_report_company, but now the field belongs to the base + module + Given that we arrive here in order of inheritance, we simply check + if the field's xmlid belongs to a module already loaded, and if not, + update the record with the correct module name.""" + model.env.cr.execute( + "SELECT f.*, d.module, d.id as xmlid_id, d.name as xmlid " + "FROM ir_model_fields f LEFT JOIN ir_model_data d " + "ON f.id=d.res_id and d.model='ir.model.fields' WHERE f.model=%s", + (model._name,), + ) + for rec in model.env.cr.dictfetchall(): + if ( + "module" in model.env.context + and rec["module"] + and rec["name"] in model._fields.keys() + and rec["module"] != model.env.context["module"] + and rec["module"] not in model.env.registry._init_modules + ): + logging.getLogger(__name__).info( + "Moving XMLID for ir.model.fields record of %s#%s " "from %s to %s", + model._name, + rec["name"], + rec["module"], + model.env.context["module"], + ) + model.env.cr.execute( + "SELECT id FROM ir_model_data WHERE module=%(module)s " + "AND name=%(xmlid)s", + dict(rec, module=model.env.context["module"]), + ) + if model.env.cr.fetchone(): + logging.getLogger(__name__).info( + "Aborting, an XMLID for this module already exists." + ) + continue + model.env.cr.execute( + "UPDATE ir_model_data SET module=%(module)s " "WHERE id=%(xmlid_id)s", + dict(rec, module=model.env.context["module"]), + ) diff --git a/openupgrade_framework/openupgrade/openupgrade_log.py b/openupgrade_framework/openupgrade/openupgrade_log.py new file mode 100644 index 000000000000..81c8916738fb --- /dev/null +++ b/openupgrade_framework/openupgrade/openupgrade_log.py @@ -0,0 +1,60 @@ +# coding: utf-8 +# Copyright 2011-2015 Therp BV +# Copyright 2016 Opener B.V. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openupgradelib.openupgrade_tools import table_exists + + +def log_xml_id(cr, module, xml_id): + """ + Log xml_ids at load time in the records table. + Called from tools/convert.py:xml_import._test_xml_id() + + # Catcha's + - The module needs to be loaded with 'init', or the calling method + won't be called. This can be brought about by installing the + module or updating the 'state' field of the module to 'to install' + or call the server with '--init ' and the database argument. + + - Do you get the right results immediately when installing the module? + No, sorry. This method retrieves the model from the ir_model_table, but + when the xml id is encountered for the first time, this method is called + before the item is present in this table. Therefore, you will not + get any meaningful results until the *second* time that you 'init' + the module. + + - The good news is that the openupgrade_records module that comes + with this distribution allows you to deal with all of this with + one click on the menu item Settings -> Customizations -> + Database Structure -> OpenUpgrade -> Generate Records + + - You cannot reinitialize the modules in your production database + and expect to keep working on it happily ever after. Do not perform + this routine on your production database. + + :param module: The module that contains the xml_id + :param xml_id: the xml_id, with or without 'module.' prefix + """ + if not table_exists(cr, 'openupgrade_record'): + return + if '.' not in xml_id: + xml_id = '%s.%s' % (module, xml_id) + cr.execute( + "SELECT model FROM ir_model_data " + "WHERE module = %s AND name = %s", + xml_id.split('.')) + record = cr.fetchone() + if not record: + print("Cannot find xml_id %s" % xml_id) + return + else: + cr.execute( + "SELECT id FROM openupgrade_record " + "WHERE module=%s AND model=%s AND name=%s AND type=%s", + (module, record[0], xml_id, 'xmlid')) + if not cr.fetchone(): + cr.execute( + "INSERT INTO openupgrade_record " + "(module, model, name, type) values(%s, %s, %s, %s)", + (module, record[0], xml_id, 'xmlid')) diff --git a/openupgrade_framework/readme/CONFIGURE.rst b/openupgrade_framework/readme/CONFIGURE.rst new file mode 100644 index 000000000000..bb245fb3b72a --- /dev/null +++ b/openupgrade_framework/readme/CONFIGURE.rst @@ -0,0 +1,7 @@ +To use this module, do not install it. Instead, you should add the name in your +``odoo.cfg`` module : + +.. code-block:: shell + + [options] + server_wide_modules = web,openupgrade_framework diff --git a/openupgrade_framework/readme/CONTRIBUTORS.rst b/openupgrade_framework/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..d693b699fbb2 --- /dev/null +++ b/openupgrade_framework/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Stefan Rijnhart +* Sylvain LE GAL diff --git a/openupgrade_framework/readme/DESCRIPTION.rst b/openupgrade_framework/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..efceae7e621b --- /dev/null +++ b/openupgrade_framework/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module is a technical module, to allow to make migrations between +major versions of Odoo. diff --git a/openupgrade_framework/readme/DEVELOP.rst b/openupgrade_framework/readme/DEVELOP.rst new file mode 100644 index 000000000000..44c17e65d41d --- /dev/null +++ b/openupgrade_framework/readme/DEVELOP.rst @@ -0,0 +1,60 @@ +This module contains two folders: + + +odoo_patch +---------- + +This folder contains python files, that correspond to python files present +in the folder ``odoo`` of the Odoo project. + +it contains a lot of monkey patches, to make working an upgrade +between two major versions. +To see the patches added, you can use ``meld`` tools: + +``meld PATH_TO_ODOO_FOLDER/odoo/ PATH_TO_OPENUPGRADE_FRAMEWORK_MODULE/odoo_patch`` + + +To make more easy the diff analysis : + +* Make sure the python files has the same path as the original one. + +* Keep the same indentation as the original file. (using ``if True:`` if required) + +* Add the following two lines at the beginning of your file, to avoid flake8 / pylint + errors + +.. code-block:: python + + # flake8: noqa + # pylint: skip-file + +* When you want to change the code. add the following tags: + + * For an addition: + +.. code-block:: python + + # + some code... + # + + * For a change: + +.. code-block:: python + + # + some code... + # + + * For a removal: + +.. code-block:: python + + # + # Comment the code, instead of removing it. + # + +openupgrade +----------- + +Contains extra functions, called by the patches introduced in the first folder. From d2220705a06264d23b9189325d5b84a45e105834 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Mon, 30 Nov 2020 00:38:49 +0100 Subject: [PATCH 31/92] [DEL] openupgrade_records -> server-tools/14.0 --- openupgrade_framework/odoo_patch/__init__.py | 1 - .../odoo_patch/addons/__init__.py | 3 - .../odoo_patch/addons/mrp/__init__.py | 20 -- .../addons/point_of_sale/__init__.py | 1 - .../addons/point_of_sale/models/__init__.py | 1 - .../addons/point_of_sale/models/pos_config.py | 21 -- .../odoo_patch/addons/stock/__init__.py | 17 -- .../odoo_patch/odoo/__init__.py | 5 - .../odoo_patch/odoo/models.py | 179 ------------------ .../odoo_patch/odoo/modules/loading.py | 39 +--- .../odoo_patch/odoo/tools/__init__.py | 2 - .../odoo_patch/odoo/tools/convert.py | 23 --- .../openupgrade/openupgrade_log.py | 60 ------ 13 files changed, 5 insertions(+), 367 deletions(-) delete mode 100644 openupgrade_framework/odoo_patch/addons/mrp/__init__.py delete mode 100644 openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py delete mode 100644 openupgrade_framework/odoo_patch/addons/point_of_sale/models/__init__.py delete mode 100644 openupgrade_framework/odoo_patch/addons/point_of_sale/models/pos_config.py delete mode 100644 openupgrade_framework/odoo_patch/addons/stock/__init__.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/models.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/tools/__init__.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/tools/convert.py delete mode 100644 openupgrade_framework/openupgrade/openupgrade_log.py diff --git a/openupgrade_framework/odoo_patch/__init__.py b/openupgrade_framework/odoo_patch/__init__.py index 1fd6e167cd12..56a70dbc176e 100644 --- a/openupgrade_framework/odoo_patch/__init__.py +++ b/openupgrade_framework/odoo_patch/__init__.py @@ -1,3 +1,2 @@ from . import odoo from . import addons - diff --git a/openupgrade_framework/odoo_patch/addons/__init__.py b/openupgrade_framework/odoo_patch/addons/__init__.py index e5aa886bacb6..e69de29bb2d1 100644 --- a/openupgrade_framework/odoo_patch/addons/__init__.py +++ b/openupgrade_framework/odoo_patch/addons/__init__.py @@ -1,3 +0,0 @@ -from . import mrp -from . import stock -from . import point_of_sale diff --git a/openupgrade_framework/odoo_patch/addons/mrp/__init__.py b/openupgrade_framework/odoo_patch/addons/mrp/__init__.py deleted file mode 100644 index f7b8b869472b..000000000000 --- a/openupgrade_framework/odoo_patch/addons/mrp/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -from odoo.addons import mrp - - -def _pre_init_mrp(cr): - """ Allow installing MRP in databases with large stock.move table (>1M records) - - Creating the computed+stored field stock_move.is_done is terribly slow with the ORM and - leads to "Out of Memory" crashes - """ - # - # don't try to add 'is_done' column, because it will fail - # when executing the generation of records, in the openupgrade_records - # module. - # cr.execute("""ALTER TABLE "stock_move" ADD COLUMN "is_done" bool;""") - # cr.execute("""UPDATE stock_move - # SET is_done=COALESCE(state in ('done', 'cancel'), FALSE);""") - pass - # - - -mrp._pre_init_mrp = _pre_init_mrp diff --git a/openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py b/openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py deleted file mode 100644 index 0650744f6bc6..000000000000 --- a/openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import models diff --git a/openupgrade_framework/odoo_patch/addons/point_of_sale/models/__init__.py b/openupgrade_framework/odoo_patch/addons/point_of_sale/models/__init__.py deleted file mode 100644 index db8634ade1f7..000000000000 --- a/openupgrade_framework/odoo_patch/addons/point_of_sale/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import pos_config diff --git a/openupgrade_framework/odoo_patch/addons/point_of_sale/models/pos_config.py b/openupgrade_framework/odoo_patch/addons/point_of_sale/models/pos_config.py deleted file mode 100644 index ac0f5dc5a49e..000000000000 --- a/openupgrade_framework/odoo_patch/addons/point_of_sale/models/pos_config.py +++ /dev/null @@ -1,21 +0,0 @@ -from odoo import api -from odoo.addons.point_of_sale.models.pos_config import PosConfig - -if True: - - @api.model - def post_install_pos_localisation(self, companies=False): - # - # don't try to setup_defaults, because it will fail - # when executing the generation of records, in the openupgrade_records - # module. - # self = self.sudo() - # if not companies: - # companies = self.env['res.company'].search([]) - # for company in companies.filtered('chart_template_id'): - # pos_configs = self.search([('company_id', '=', company.id)]) - # pos_configs.setup_defaults(company) - pass - # - -PosConfig.post_install_pos_localisation = post_install_pos_localisation diff --git a/openupgrade_framework/odoo_patch/addons/stock/__init__.py b/openupgrade_framework/odoo_patch/addons/stock/__init__.py deleted file mode 100644 index b66d7f484cb1..000000000000 --- a/openupgrade_framework/odoo_patch/addons/stock/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from odoo.addons import stock - - -def pre_init_hook(cr): - # - # don't uninstall data as this breaks the analysis - # Origin of this code is https://github.com/odoo/odoo/issues/22243 - # env = api.Environment(cr, SUPERUSER_ID, {}) - # env['ir.model.data'].search([ - # ('model', 'like', '%stock%'), - # ('module', '=', 'stock') - # ]).unlink() - pass - # - - -stock.pre_init_hook = pre_init_hook diff --git a/openupgrade_framework/odoo_patch/odoo/__init__.py b/openupgrade_framework/odoo_patch/odoo/__init__.py index f5065ae34593..f13459fa7a9f 100644 --- a/openupgrade_framework/odoo_patch/odoo/__init__.py +++ b/openupgrade_framework/odoo_patch/odoo/__init__.py @@ -1,10 +1,5 @@ from . import modules from . import service -from . import tools # Nothing todo the function, the function check_security didn't changed from . import http - -# adapted to V14 -# TODO, OpenUpgrade maintainers : check if it's OK -from . import models diff --git a/openupgrade_framework/odoo_patch/odoo/models.py b/openupgrade_framework/odoo_patch/odoo/models.py deleted file mode 100644 index ee09595fb103..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/models.py +++ /dev/null @@ -1,179 +0,0 @@ -# flake8: noqa -# pylint: skip-file - -import odoo -import psycopg2 -from odoo import _ -from odoo.models import fix_import_export_id_paths, BaseModel, _logger -from odoo.addons.openupgrade_framework.openupgrade import openupgrade_log - - -if True: - def _load(self, fields, data): - """ - Attempts to load the data matrix, and returns a list of ids (or - ``False`` if there was an error and no id could be generated) and a - list of messages. - - The ids are those of the records created and saved (in database), in - the same order they were extracted from the file. They can be passed - directly to :meth:`~read` - - :param fields: list of fields to import, at the same index as the corresponding data - :type fields: list(str) - :param data: row-major matrix of data to import - :type data: list(list(str)) - :returns: {ids: list(int)|False, messages: [Message][, lastrow: int]} - """ - self.flush() - - # determine values of mode, current_module and noupdate - mode = self._context.get('mode', 'init') - current_module = self._context.get('module', '__import__') - noupdate = self._context.get('noupdate', False) - # add current module in context for the conversion of xml ids - self = self.with_context(_import_current_module=current_module) - - cr = self._cr - cr.execute('SAVEPOINT model_load') - - fields = [fix_import_export_id_paths(f) for f in fields] - fg = self.fields_get() - - ids = [] - messages = [] - ModelData = self.env['ir.model.data'] - - # list of (xid, vals, info) for records to be created in batch - batch = [] - batch_xml_ids = set() - # models in which we may have created / modified data, therefore might - # require flushing in order to name_search: the root model and any - # o2m - creatable_models = {self._name} - for field_path in fields: - if field_path[0] in (None, 'id', '.id'): - continue - model_fields = self._fields - if isinstance(model_fields[field_path[0]], odoo.fields.Many2one): - # this only applies for toplevel m2o (?) fields - if field_path[0] in (self.env.context.get('name_create_enabled_fieds') or {}): - creatable_models.add(model_fields[field_path[0]].comodel_name) - for field_name in field_path: - if field_name in (None, 'id', '.id'): - break - - if isinstance(model_fields[field_name], odoo.fields.One2many): - comodel = model_fields[field_name].comodel_name - creatable_models.add(comodel) - model_fields = self.env[comodel]._fields - - def flush(*, xml_id=None, model=None): - if not batch: - return - - assert not (xml_id and model), \ - "flush can specify *either* an external id or a model, not both" - - if xml_id and xml_id not in batch_xml_ids: - if xml_id not in self.env: - return - if model and model not in creatable_models: - return - - data_list = [ - dict(xml_id=xid, values=vals, info=info, noupdate=noupdate) - for xid, vals, info in batch - ] - batch.clear() - batch_xml_ids.clear() - - # try to create in batch - try: - with cr.savepoint(): - recs = self._load_records(data_list, mode == 'update') - ids.extend(recs.ids) - return - except psycopg2.InternalError as e: - # broken transaction, exit and hope the source error was already logged - if not any(message['type'] == 'error' for message in messages): - info = data_list[0]['info'] - messages.append(dict(info, type='error', message=_(u"Unknown database error: '%s'", e))) - return - except Exception: - pass - - errors = 0 - # try again, this time record by record - for i, rec_data in enumerate(data_list, 1): - try: - with cr.savepoint(): - rec = self._load_records([rec_data], mode == 'update') - ids.append(rec.id) - except psycopg2.Warning as e: - info = rec_data['info'] - messages.append(dict(info, type='warning', message=str(e))) - except psycopg2.Error as e: - info = rec_data['info'] - messages.append(dict(info, type='error', **PGERROR_TO_OE[e.pgcode](self, fg, info, e))) - # Failed to write, log to messages, rollback savepoint (to - # avoid broken transaction) and keep going - errors += 1 - except Exception as e: - _logger.debug("Error while loading record", exc_info=True) - info = rec_data['info'] - message = (_(u'Unknown error during import:') + u' %s: %s' % (type(e), e)) - moreinfo = _('Resolve other errors first') - messages.append(dict(info, type='error', message=message, moreinfo=moreinfo)) - # Failed for some reason, perhaps due to invalid data supplied, - # rollback savepoint and keep going - errors += 1 - if errors >= 10 and (errors >= i / 10): - messages.append({ - 'type': 'warning', - 'message': _(u"Found more than 10 errors and more than one error per 10 records, interrupted to avoid showing too many errors.") - }) - break - - # make 'flush' available to the methods below, in the case where XMLID - # resolution fails, for instance - flush_self = self.with_context(import_flush=flush) - - # TODO: break load's API instead of smuggling via context? - limit = self._context.get('_import_limit') - if limit is None: - limit = float('inf') - extracted = flush_self._extract_records(fields, data, log=messages.append, limit=limit) - - converted = flush_self._convert_records(extracted, log=messages.append) - - info = {'rows': {'to': -1}} - for id, xid, record, info in converted: - if xid: - xid = xid if '.' in xid else "%s.%s" % (current_module, xid) - batch_xml_ids.add(xid) - # - # log csv records - openupgrade_log.log_xml_id(self.env.cr, current_module, xid) - # - elif id: - record['id'] = id - batch.append((xid, record, info)) - - flush() - if any(message['type'] == 'error' for message in messages): - cr.execute('ROLLBACK TO SAVEPOINT model_load') - ids = False - # cancel all changes done to the registry/ormcache - self.pool.reset_changes() - - nextrow = info['rows']['to'] + 1 - if nextrow < limit: - nextrow = 0 - return { - 'ids': ids, - 'messages': messages, - 'nextrow': nextrow, - } - -BaseModel.load = _load diff --git a/openupgrade_framework/odoo_patch/odoo/modules/loading.py b/openupgrade_framework/odoo_patch/odoo/modules/loading.py index eb25c80ade5e..638656760622 100644 --- a/openupgrade_framework/odoo_patch/odoo/modules/loading.py +++ b/openupgrade_framework/odoo_patch/odoo/modules/loading.py @@ -22,8 +22,7 @@ def _load_module_graph(cr, graph, status=None, perform_checks=True, - skip_modules=None, report=None, models_to_check=None, upg_registry=None): - # + skip_modules=None, report=None, models_to_check=None): """Migrates+Updates or Installs all module nodes from ``graph`` :param graph: graph of module nodes to load :param status: deprecated parameter, unused, left to avoid changing signature in 8.0 @@ -111,17 +110,6 @@ def _load_module_graph(cr, graph, status=None, perform_checks=True, models_updated |= set(model_names) models_to_check -= set(model_names) registry.setup_models(cr) - # - # rebuild the local registry based on the loaded models - local_registry = {} - env = api.Environment(cr, SUPERUSER_ID, {}) - for model in env.values(): - if not model._auto: - continue - openupgrade_loading.log_model(model, local_registry) - openupgrade_loading.compare_registries( - cr, package.name, upg_registry, local_registry) - # registry.init_models(cr, model_names, {'module': package.name}, new_install) elif package.state != 'to remove': @@ -276,8 +264,7 @@ def _load_module_graph(cr, graph, status=None, perform_checks=True, def _load_marked_modules(cr, graph, states, force, progressdict, report, - loaded_modules, perform_checks, models_to_check=None, upg_registry=None): - # + loaded_modules, perform_checks, models_to_check=None): """Loads modules marked with ``states``, adding them to ``graph`` and ``loaded_modules`` and returns a list of installed/upgraded modules.""" @@ -295,14 +282,10 @@ def _load_marked_modules(cr, graph, states, force, progressdict, report, break graph.add_modules(cr, module_list, force) _logger.debug('Updating graph with %d more modules', len(module_list)) - # - # add upg_registry loaded, processed = _load_module_graph( cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks, models_to_check=models_to_check, - upg_registry=upg_registry, ) - # processed_modules.extend(processed) loaded_modules.extend(loaded) if not processed: @@ -317,10 +300,6 @@ def _load_modules(db, force_demo=False, status=None, update_module=False): if force_demo: force.append('demo') - # - upg_registry = {} - # - models_to_check = set() with db.cursor() as cr: @@ -352,11 +331,9 @@ def _load_modules(db, force_demo=False, status=None, update_module=False): # processed_modules: for cleanup step after install # loaded_modules: to avoid double loading report = registry._assertion_report - # - # add upg_registry loaded_modules, processed_modules = _load_module_graph( cr, graph, status, perform_checks=update_module, - report=report, models_to_check=models_to_check, upg_registry=upg_registry) + report=report, models_to_check=models_to_check) # load_lang = tools.config.pop('load_language') @@ -423,19 +400,13 @@ def _load_modules(db, force_demo=False, status=None, update_module=False): previously_processed = -1 while previously_processed < len(processed_modules): previously_processed = len(processed_modules) - # - # add upg_registry processed_modules += _load_marked_modules(cr, graph, ['installed', 'to upgrade', 'to remove'], - force, status, report, loaded_modules, update_module, models_to_check, upg_registry) - # + force, status, report, loaded_modules, update_module, models_to_check) if update_module: - # - # add upg_registry processed_modules += _load_marked_modules(cr, graph, ['to install'], force, status, report, - loaded_modules, update_module, models_to_check, upg_registry) - # + loaded_modules, update_module, models_to_check) # check that new module dependencies have been properly installed after a migration/upgrade cr.execute("SELECT name from ir_module_module WHERE state IN ('to install', 'to upgrade')") module_list = [name for (name,) in cr.fetchall()] diff --git a/openupgrade_framework/odoo_patch/odoo/tools/__init__.py b/openupgrade_framework/odoo_patch/odoo/tools/__init__.py deleted file mode 100644 index 6ad156515dc3..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/tools/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import convert -from . import view_validation diff --git a/openupgrade_framework/odoo_patch/odoo/tools/convert.py b/openupgrade_framework/odoo_patch/odoo/tools/convert.py deleted file mode 100644 index 49531bfc429e..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/tools/convert.py +++ /dev/null @@ -1,23 +0,0 @@ -# flake8: noqa -# pylint: skip-file - -from odoo.addons.openupgrade_framework.openupgrade import openupgrade_log - -from odoo.tools.convert import xml_import - -if True: - - def __test_xml_id(self, xml_id): - if '.' in xml_id: - module, id = xml_id.split('.', 1) - assert '.' not in id, """The ID reference "%s" must contain -maximum one dot. They are used to refer to other modules ID, in the -form: module.record_id""" % (xml_id,) - if module != self.module: - modcnt = self.env['ir.module.module'].search_count([('name', '=', module), ('state', '=', 'installed')]) - assert modcnt == 1, """The ID "%s" refers to an uninstalled module""" % (xml_id,) - - # OpenUpgrade: log entry of XML imports - openupgrade_log.log_xml_id(self.env.cr, self.module, xml_id) - -xml_import._test_xml_id = __test_xml_id diff --git a/openupgrade_framework/openupgrade/openupgrade_log.py b/openupgrade_framework/openupgrade/openupgrade_log.py deleted file mode 100644 index 81c8916738fb..000000000000 --- a/openupgrade_framework/openupgrade/openupgrade_log.py +++ /dev/null @@ -1,60 +0,0 @@ -# coding: utf-8 -# Copyright 2011-2015 Therp BV -# Copyright 2016 Opener B.V. -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from openupgradelib.openupgrade_tools import table_exists - - -def log_xml_id(cr, module, xml_id): - """ - Log xml_ids at load time in the records table. - Called from tools/convert.py:xml_import._test_xml_id() - - # Catcha's - - The module needs to be loaded with 'init', or the calling method - won't be called. This can be brought about by installing the - module or updating the 'state' field of the module to 'to install' - or call the server with '--init ' and the database argument. - - - Do you get the right results immediately when installing the module? - No, sorry. This method retrieves the model from the ir_model_table, but - when the xml id is encountered for the first time, this method is called - before the item is present in this table. Therefore, you will not - get any meaningful results until the *second* time that you 'init' - the module. - - - The good news is that the openupgrade_records module that comes - with this distribution allows you to deal with all of this with - one click on the menu item Settings -> Customizations -> - Database Structure -> OpenUpgrade -> Generate Records - - - You cannot reinitialize the modules in your production database - and expect to keep working on it happily ever after. Do not perform - this routine on your production database. - - :param module: The module that contains the xml_id - :param xml_id: the xml_id, with or without 'module.' prefix - """ - if not table_exists(cr, 'openupgrade_record'): - return - if '.' not in xml_id: - xml_id = '%s.%s' % (module, xml_id) - cr.execute( - "SELECT model FROM ir_model_data " - "WHERE module = %s AND name = %s", - xml_id.split('.')) - record = cr.fetchone() - if not record: - print("Cannot find xml_id %s" % xml_id) - return - else: - cr.execute( - "SELECT id FROM openupgrade_record " - "WHERE module=%s AND model=%s AND name=%s AND type=%s", - (module, record[0], xml_id, 'xmlid')) - if not cr.fetchone(): - cr.execute( - "INSERT INTO openupgrade_record " - "(module, model, name, type) values(%s, %s, %s, %s)", - (module, record[0], xml_id, 'xmlid')) From f72ead21d7aea3c8e482586de37aae7d574f9ded Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sun, 6 Dec 2020 13:55:18 +0100 Subject: [PATCH 32/92] [RFR] OpenUpgrade framework patches --- openupgrade_framework/README.rst | 174 ++++++ openupgrade_framework/__init__.py | 16 +- openupgrade_framework/__manifest__.py | 4 +- openupgrade_framework/odoo_patch/__init__.py | 1 - .../odoo_patch/addons/__init__.py | 0 .../odoo_patch/odoo/__init__.py | 6 +- .../odoo_patch/odoo/addons/__init__.py | 1 + .../odoo_patch/odoo/addons/base/__init__.py | 1 + .../odoo/addons/base/models/__init__.py | 2 + .../odoo/addons/base/models/ir_model.py | 66 +++ .../odoo/addons/base/models/ir_ui_view.py | 53 ++ openupgrade_framework/odoo_patch/odoo/http.py | 32 -- .../odoo_patch/odoo/models.py | 42 ++ .../odoo_patch/odoo/modules/__init__.py | 13 +- .../odoo_patch/odoo/modules/graph.py | 115 +--- .../odoo_patch/odoo/modules/loading.py | 527 ------------------ .../odoo_patch/odoo/modules/migration.py | 141 +---- .../odoo_patch/odoo/modules/registry.py | 58 -- .../odoo_patch/odoo/service/__init__.py | 4 - .../odoo_patch/odoo/service/server.py | 71 --- .../odoo_patch/odoo/tools/view_validation.py | 29 - openupgrade_framework/openupgrade/__init__.py | 0 .../openupgrade/openupgrade_loading.py | 318 ----------- openupgrade_framework/readme/CONFIGURE.rst | 12 +- openupgrade_framework/readme/CREDITS.rst | 4 + openupgrade_framework/readme/DESCRIPTION.rst | 21 +- openupgrade_framework/readme/DEVELOP.rst | 20 +- openupgrade_framework/readme/INSTALL.rst | 2 + .../static/description/index.html | 527 ++++++++++++++++++ 29 files changed, 963 insertions(+), 1297 deletions(-) delete mode 100644 openupgrade_framework/odoo_patch/addons/__init__.py create mode 100644 openupgrade_framework/odoo_patch/odoo/addons/__init__.py create mode 100644 openupgrade_framework/odoo_patch/odoo/addons/base/__init__.py create mode 100644 openupgrade_framework/odoo_patch/odoo/addons/base/models/__init__.py create mode 100644 openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py create mode 100644 openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/http.py create mode 100644 openupgrade_framework/odoo_patch/odoo/models.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/modules/loading.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/modules/registry.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/service/__init__.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/service/server.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/tools/view_validation.py delete mode 100644 openupgrade_framework/openupgrade/__init__.py delete mode 100644 openupgrade_framework/openupgrade/openupgrade_loading.py create mode 100644 openupgrade_framework/readme/CREDITS.rst create mode 100644 openupgrade_framework/readme/INSTALL.rst create mode 100644 openupgrade_framework/static/description/index.html diff --git a/openupgrade_framework/README.rst b/openupgrade_framework/README.rst index 3ed54188c923..e7e759ed6c63 100644 --- a/openupgrade_framework/README.rst +++ b/openupgrade_framework/README.rst @@ -6,3 +6,177 @@ Openupgrade Framework !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/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%2Fopenupgrade-lightgray.png?logo=github + :target: https://github.com/OCA/openupgrade/tree/14.0/openupgrade_framework + :alt: OCA/openupgrade +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/openupgrade-14-0/openupgrade-14-0-openupgrade_framework + :alt: Translate me on Weblate + +|badge1| |badge2| |badge3| |badge4| + +This module is a technical module that contains a number of monkeypatches +to improve the behaviour of Odoo when migrating your database using the +OpenUpgrade migration scripts: + +* Prevent dropping columns or tables in the database when fields or models + are obsoleted in the Odoo data model of the target release. After the + migration, you can review and delete unused database tables and columns + using `database_cleanup`. See + https://odoo-community.org/shop/product/database-cleanup-918 +* When data records are deleted during the migration (such as views or other + system records), this is done in a secure mode. If the deletion fails because + of some unforeseen dependency, the deletion will be cancelled and a message + is logged, after which the migration continues. +* Prevent a number of log messages that do not apply when using OpenUpgrade. +* Suppress log messages containing instructions for developers of Odoo S.A. + with regards to their own set of closed source migration scripts. +* Suppress log messages about failed view validation, which are to be expected + during a migration. +* Run migration scripts for modules that are installed as new dependencies + of upgraded modules (when there are such scripts for those particular + modules) + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +This module does not need to be installed on a database. +It simply needs to be available via your ``addons-path``. + +Configuration +============= + +* call your odoo instance with the option ``--load=web,openupgrade_framework`` + +or + +* add the key to your configuration file: + +.. code-block:: shell + + [options] + server_wide_modules = web,openupgrade_framework + +When you load the module in either way of these ways, and you have the +`openupgrade_scripts` module in your addons path available, the +`--upgrade-path` option of Odoo will be set automatically to the location +of the OpenUpgrade migration scripts. + +Development +=========== + +The `odoo_patch` folder contains python files in a tree that mimicks the +folter tree of the Odoo project. It contains a number of monkey patches +to improve the migration of an Odoo database between two major versions. + +So far, we are able to make everything work without overwriting large blocks +of code, but if larger patches need to be added, please use the method +described below: + +To see the patches added, you can use ``meld`` tools: + +``meld PATH_TO_ODOO_FOLDER/odoo/ PATH_TO_OPENUPGRADE_FRAMEWORK_MODULE/odoo_patch`` + + +To make more easy the diff analysis : + +* Make sure the python files has the same path as the original one. + +* Keep the same indentation as the original file. (using ``if True:`` if required) + +* Add the following two lines at the beginning of your file, to avoid flake8 / pylint + errors + +.. code-block:: python + + # flake8: noqa + # pylint: skip-file + +* When you want to change the code. add the following tags: + + * For an addition: + +.. code-block:: python + + # + some code... + # + + * For a change: + +.. code-block:: python + + # + some code... + # + + * For a removal: + +.. code-block:: python + + # + # Comment the code, instead of removing it. + # + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Therp BV +* Opener B.V. +* GRAP + +Contributors +~~~~~~~~~~~~ + +* Stefan Rijnhart +* Sylvain LE GAL + +Other credits +~~~~~~~~~~~~~ + +Many developers have contributed to the OpenUpgrade framework in its previous +incarnation. Their original contributions may no longer needed, or they are +no longer recognizable in their current form but OpenUpgrade would not have +existed at this point without them. + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/openupgrade `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/openupgrade_framework/__init__.py b/openupgrade_framework/__init__.py index 94ba6ffa9f2f..e3b93cdf5f45 100644 --- a/openupgrade_framework/__init__.py +++ b/openupgrade_framework/__init__.py @@ -1,2 +1,16 @@ +import logging +import os + +from odoo.modules import get_module_path +from odoo.tools import config + from . import odoo_patch -from . import openupgrade + +if not config.get("upgrade_path"): + path = get_module_path("openupgrade_scripts", display_warning=False) + if path: + logging.getLogger(__name__).info( + "Setting upgrade_path to the scripts directory inside the module " + "location of openupgrade_scripts" + ) + config["upgrade_path"] = os.path.join(path, "scripts") diff --git a/openupgrade_framework/__manifest__.py b/openupgrade_framework/__manifest__.py index fd016de4ca7a..cc76dfcbbf8e 100644 --- a/openupgrade_framework/__manifest__.py +++ b/openupgrade_framework/__manifest__.py @@ -1,5 +1,5 @@ +# Copyright Odoo Community Association (OCA) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - { "name": "Openupgrade Framework", "summary": """Module to integrate in the server_wide_modules @@ -10,5 +10,5 @@ "version": "14.0.1.0.0", "license": "AGPL-3", "depends": ["base"], - "installable": False, + "installable": True, } diff --git a/openupgrade_framework/odoo_patch/__init__.py b/openupgrade_framework/odoo_patch/__init__.py index 56a70dbc176e..3c691cd11703 100644 --- a/openupgrade_framework/odoo_patch/__init__.py +++ b/openupgrade_framework/odoo_patch/__init__.py @@ -1,2 +1 @@ from . import odoo -from . import addons diff --git a/openupgrade_framework/odoo_patch/addons/__init__.py b/openupgrade_framework/odoo_patch/addons/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/openupgrade_framework/odoo_patch/odoo/__init__.py b/openupgrade_framework/odoo_patch/odoo/__init__.py index f13459fa7a9f..c969456c0055 100644 --- a/openupgrade_framework/odoo_patch/odoo/__init__.py +++ b/openupgrade_framework/odoo_patch/odoo/__init__.py @@ -1,5 +1 @@ -from . import modules -from . import service - -# Nothing todo the function, the function check_security didn't changed -from . import http +from . import addons, models, modules diff --git a/openupgrade_framework/odoo_patch/odoo/addons/__init__.py b/openupgrade_framework/odoo_patch/odoo/addons/__init__.py new file mode 100644 index 000000000000..0e44449338cf --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/addons/__init__.py @@ -0,0 +1 @@ +from . import base diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/__init__.py b/openupgrade_framework/odoo_patch/odoo/addons/base/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/__init__.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/__init__.py new file mode 100644 index 000000000000..9368777a039d --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/__init__.py @@ -0,0 +1,2 @@ +from . import ir_model +from . import ir_ui_view diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py new file mode 100644 index 000000000000..41a45c636b96 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py @@ -0,0 +1,66 @@ +# Copyright Odoo Community Association (OCA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from openupgradelib import openupgrade + +from odoo import api, models +from odoo.tools import mute_logger + +from odoo.addons.base.models.ir_model import IrModel, IrModelData, IrModelRelation + + +def _drop_table(self): + """ Never drop tables """ + for model in self: + if self.env.get(model.model) is not None: + openupgrade.message( + self.env.cr, + "Unknown", + False, + False, + "Not dropping the table or view of model %s", + model.model, + ) + + +def _drop_column(self): + """ Never drop columns """ + for field in self: + if field.name in models.MAGIC_COLUMNS: + continue + openupgrade.message( + self.env.cr, + "Unknown", + False, + False, + "Not dropping the column of field %s of model %s", + field.name, + field.model, + ) + continue + + +IrModel._drop_column = _drop_column +IrModel._drop_table = _drop_table + + +@api.model +def _process_end(self, modules): + """Don't warn about upgrade conventions from Odoo + ('fields should be explicitely removed by an upgrade script') + """ + with mute_logger("odoo.addons.base.models.ir_model"): + return IrModelData._process_end._original_method(self, modules) + + +_process_end._original_method = IrModelData._process_end +IrModelData._process_end = _process_end + + +def _module_data_uninstall(self): + """Don't delete many2many relation tables. Only unlink the + ir.model.relation record itself. + """ + self.unlink() + + +IrModelRelation._module_data_uninstall = _module_data_uninstall diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py new file mode 100644 index 000000000000..8e880db17188 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py @@ -0,0 +1,53 @@ +# Copyright Odoo Community Association (OCA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from odoo import api +from odoo.tools import mute_logger + +from odoo.addons.base.models.ir_ui_view import View + +_logger = logging.getLogger(__name__) + + +@api.constrains("arch_db") +def _check_xml(self): + """ Mute warnings about views which are common during migration """ + with mute_logger("odoo.addons.base.models.ir_ui_view"): + return View._check_xml._original_method(self) + + +def handle_view_error( + self, message, *args, raise_exception=True, from_exception=None, from_traceback=None +): + """Don't raise or log exceptions in view validation unless explicitely + requested + """ + raise_exception = self.env.context.get("raise_view_error") + to_mute = "odoo.addons.base.models.ir_ui_view" if raise_exception else "not_muted" + with mute_logger(to_mute): + try: + return View.handle_view_error._original_method( + self, + message, + *args, + raise_exception=False, + from_exception=from_exception, + from_traceback=from_traceback + ) + except ValueError: + _logger.warn( + "Can't render custom view %s for model %s. " + "Assuming you are migrating between major versions of " + "Odoo, this view is now set to inactive. Please " + "review the view contents manually after the migration.", + self.xml_id, + self.model, + ) + self.write({"active": False}) + + +_check_xml._original_method = View._check_xml +View._check_xml = _check_xml +handle_view_error._original_method = View.handle_view_error +View.handle_view_error = handle_view_error diff --git a/openupgrade_framework/odoo_patch/odoo/http.py b/openupgrade_framework/odoo_patch/odoo/http.py deleted file mode 100644 index e11c558fb905..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/http.py +++ /dev/null @@ -1,32 +0,0 @@ -# flake8: noqa -# pylint: skip-file - -import odoo -from odoo.service import security -from odoo.http import SessionExpiredException, request, OpenERPSession - -if True: - def _check_security(self): - """ - Check the current authentication parameters to know if those are still - valid. This method should be called at each request. If the - authentication fails, a :exc:`SessionExpiredException` is raised. - """ - if not self.db or not self.uid: - raise SessionExpiredException("Session expired") - # We create our own environment instead of the request's one. - # to avoid creating it without the uid since request.uid isn't set yet - env = odoo.api.Environment(request.cr, self.uid, self.context) - # here we check if the session is still valid - if not security.check_session(self, env): - # - # When asking openupgrade_records to generate records - # over jsonrpc, a query on res_users in the call above locks this - # table for the sql operations that are triggered by the - # reinstallation of the base module - env.cr.rollback() - # - raise SessionExpiredException("Session expired") - - -OpenERPSession.check_security = _check_security diff --git a/openupgrade_framework/odoo_patch/odoo/models.py b/openupgrade_framework/odoo_patch/odoo/models.py new file mode 100644 index 000000000000..12ec270e2f4c --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/models.py @@ -0,0 +1,42 @@ +# Copyright Odoo Community Association (OCA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging +from uuid import uuid4 + +from odoo.models import BaseModel + +from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG + +_logger = logging.getLogger(__name__) + + +def unlink(self): + """Don't break on unlink of obsolete records + when called from ir.model::_process_end() + + This only adapts the base unlink method. If overrides of this method + on individual models give problems, add patches for those as well. + """ + if not self.env.context.get(MODULE_UNINSTALL_FLAG): + return BaseModel.unlink._original_method(self) + savepoint = str(uuid4) + try: + self.env.cr.execute( # pylint: disable=sql-injection + 'SAVEPOINT "%s"' % savepoint + ) + return BaseModel.unlink._original_method(self) + except Exception as e: + self.env.cr.execute( # pylint: disable=sql-injection + 'ROLLBACK TO SAVEPOINT "%s"' % savepoint + ) + _logger.warning( + "Could not delete obsolete record with ids %s of model %s: %s", + self.ids, + self._name, + e, + ) + return False + + +unlink._original_method = BaseModel.unlink +BaseModel.unlink = unlink diff --git a/openupgrade_framework/odoo_patch/odoo/modules/__init__.py b/openupgrade_framework/odoo_patch/odoo/modules/__init__.py index 90de5b4ff4e7..614d7053c827 100644 --- a/openupgrade_framework/odoo_patch/odoo/modules/__init__.py +++ b/openupgrade_framework/odoo_patch/odoo/modules/__init__.py @@ -1,12 +1 @@ -# Minor changes. (call to safe_eval changed) -# otherwise : adapted to V14 -from . import graph - -# A lot of changes in the core functions. -from . import loading - -# Adapted to V14 -from . import migration - -# Adapted to V14 -from . import registry +from . import graph, migration diff --git a/openupgrade_framework/odoo_patch/odoo/modules/graph.py b/openupgrade_framework/odoo_patch/odoo/modules/graph.py index b0bedef3ea62..5e454e5c5aa8 100644 --- a/openupgrade_framework/odoo_patch/odoo/modules/graph.py +++ b/openupgrade_framework/odoo_patch/odoo/modules/graph.py @@ -1,108 +1,21 @@ -# flake8: noqa -# pylint: skip-file - -import logging +# Copyright Odoo Community Association (OCA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import odoo -import odoo.tools as tools -from odoo.tools.safe_eval import safe_eval - from odoo.modules.graph import Graph -_logger = logging.getLogger(__name__) - - -if True: - - def _update_from_db(self, cr): - if not len(self): - return - # update the graph with values from the database (if exist) - ## First, we set the default values for each package in graph - additional_data = {key: {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None} for key in self.keys()} - ## Then we get the values from the database - cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version' - ' FROM ir_module_module' - ' WHERE name IN %s',(tuple(additional_data),) - ) - - ## and we update the default values with values from the database - additional_data.update((x['name'], x) for x in cr.dictfetchall()) - - # - # Prevent reloading of demo data from the new version on major upgrade - if ('base' in self and additional_data['base']['dbdemo'] and - additional_data['base']['installed_version'] < - odoo.release.major_version): - cr.execute("UPDATE ir_module_module SET demo = false") - for data in additional_data.values(): - data['dbdemo'] = False - # +def update_from_db(self, cr): + """ Prevent reloading of demo data from the new version on major upgrade """ + Graph.update_from_db._original_method(self, cr) + if ( + "base" in self + and self["base"].dbdemo + and self["base"].installed_version < odoo.release.major_version + ): + cr.execute("UPDATE ir_module_module SET demo = false") for package in self.values(): - for k, v in additional_data[package.name].items(): - setattr(package, k, v) - - - def _add_modules(self, cr, module_list, force=None): - if force is None: - force = [] - packages = [] - len_graph = len(self) - - # - # force additional dependencies for the upgrade process if given - # in config file - forced_deps = tools.config.get_misc('openupgrade', 'force_deps', '{}') - forced_deps = tools.config.get_misc('openupgrade', - 'force_deps_' + odoo.release.version, - forced_deps) - forced_deps = safe_eval(forced_deps) - # - - for module in module_list: - # This will raise an exception if no/unreadable descriptor file. - # NOTE The call to load_information_from_description_file is already - # done by db.initialize, so it is possible to not do it again here. - info = odoo.modules.module.load_information_from_description_file(module) - if info and info['installable']: - # - info['depends'].extend(forced_deps.get(module, [])) - # - packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version - elif module != 'studio_customization': - _logger.warning('module %s: not installable, skipped', module) - - dependencies = dict([(p, info['depends']) for p, info in packages]) - current, later = set([p for p, info in packages]), set() - - while packages and current > later: - package, info = packages[0] - deps = info['depends'] - - # if all dependencies of 'package' are already in the graph, add 'package' in the graph - if all(dep in self for dep in deps): - if not package in current: - packages.pop(0) - continue - later.clear() - current.remove(package) - node = self.add_node(package, info) - for kind in ('init', 'demo', 'update'): - if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force: - setattr(node, kind, True) - else: - later.add(package) - packages.append((package, info)) - packages.pop(0) - - self.update_from_db(cr) - - for package in later: - unmet_deps = [p for p in dependencies[package] if p not in self] - _logger.info('module %s: Unmet dependencies: %s', package, ', '.join(unmet_deps)) - - return len(self) - len_graph + package.dbdemo = False -Graph.update_from_db = _update_from_db -Graph.add_modules = _add_modules +update_from_db._original_method = Graph.update_from_db +Graph.update_from_db = update_from_db diff --git a/openupgrade_framework/odoo_patch/odoo/modules/loading.py b/openupgrade_framework/odoo_patch/odoo/modules/loading.py deleted file mode 100644 index 638656760622..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/modules/loading.py +++ /dev/null @@ -1,527 +0,0 @@ -# flake8: noqa -# pylint: skip-file - -import itertools -import logging -import sys -import time - -import odoo -import odoo.tools as tools -from odoo import api, SUPERUSER_ID -from odoo.modules import loading -from odoo.modules.module import adapt_version, load_openerp_module, initialize_sys_path - -from odoo.modules.loading import load_data, load_demo, _check_module_names -from odoo.addons.openupgrade_framework.openupgrade import openupgrade_loading - -import os - -_logger = logging.getLogger(__name__) -_test_logger = logging.getLogger('odoo.tests') - - -def _load_module_graph(cr, graph, status=None, perform_checks=True, - skip_modules=None, report=None, models_to_check=None): - """Migrates+Updates or Installs all module nodes from ``graph`` - :param graph: graph of module nodes to load - :param status: deprecated parameter, unused, left to avoid changing signature in 8.0 - :param perform_checks: whether module descriptors should be checked for validity (prints warnings - for same cases) - :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped - :return: list of modules that were installed or updated - """ - if skip_modules is None: - skip_modules = [] - - if models_to_check is None: - models_to_check = set() - - processed_modules = [] - loaded_modules = [] - registry = odoo.registry(cr.dbname) - migrations = odoo.modules.migration.MigrationManager(cr, graph) - module_count = len(graph) - _logger.info('loading %d modules...', module_count) - - # - # suppress commits to have the upgrade of one module in just one transaction - cr.commit_org = cr.commit - cr.commit = lambda *args: None - cr.rollback_org = cr.rollback - cr.rollback = lambda *args: None - # - - # register, instantiate and initialize models for each modules - t0 = time.time() - loading_extra_query_count = odoo.sql_db.sql_counter - loading_cursor_query_count = cr.sql_log_count - - models_updated = set() - - for index, package in enumerate(graph, 1): - module_name = package.name - module_id = package.id - - # - if module_name in skip_modules or module_name in loaded_modules: - # - continue - - module_t0 = time.time() - module_cursor_query_count = cr.sql_log_count - module_extra_query_count = odoo.sql_db.sql_counter - - needs_update = ( - hasattr(package, "init") - or hasattr(package, "update") - or package.state in ("to install", "to upgrade") - ) - module_log_level = logging.DEBUG - if needs_update: - module_log_level = logging.INFO - _logger.log(module_log_level, 'Loading module %s (%d/%d)', module_name, index, module_count) - - if needs_update: - if package.name != 'base': - registry.setup_models(cr) - migrations.migrate_module(package, 'pre') - if package.name != 'base': - env = api.Environment(cr, SUPERUSER_ID, {}) - env['base'].flush() - - load_openerp_module(package.name) - - new_install = package.state == 'to install' - if new_install: - py_module = sys.modules['odoo.addons.%s' % (module_name,)] - pre_init = package.info.get('pre_init_hook') - if pre_init: - getattr(py_module, pre_init)(cr) - - model_names = registry.load(cr, package) - - mode = 'update' - if hasattr(package, 'init') or package.state == 'to install': - mode = 'init' - - loaded_modules.append(package.name) - if needs_update: - models_updated |= set(model_names) - models_to_check -= set(model_names) - registry.setup_models(cr) - - registry.init_models(cr, model_names, {'module': package.name}, new_install) - elif package.state != 'to remove': - # The current module has simply been loaded. The models extended by this module - # and for which we updated the schema, must have their schema checked again. - # This is because the extension may have changed the model, - # e.g. adding required=True to an existing field, but the schema has not been - # updated by this module because it's not marked as 'to upgrade/to install'. - models_to_check |= set(model_names) & models_updated - - idref = {} - - if needs_update: - env = api.Environment(cr, SUPERUSER_ID, {}) - # Can't put this line out of the loop: ir.module.module will be - # registered by init_models() above. - module = env['ir.module.module'].browse(module_id) - - if perform_checks: - module._check() - - if package.state == 'to upgrade': - # upgrading the module information - module.write(module.get_values_from_terp(package.data)) - load_data(cr, idref, mode, kind='data', package=package) - demo_loaded = package.dbdemo = load_demo(cr, package, idref, mode) - cr.execute('update ir_module_module set demo=%s where id=%s', (demo_loaded, module_id)) - module.invalidate_cache(['demo']) - - # - # add 'try' block for logging exceptions - # as errors in post scripts seem to be dropped - try: - migrations.migrate_module(package, 'post') - except Exception as exc: - _logger.error('Error executing post migration script for module %s: %s', - package, exc) - raise - # - - # Update translations for all installed languages - overwrite = odoo.tools.config["overwrite_existing_translations"] - module.with_context(overwrite=overwrite)._update_translations() - - if package.name is not None: - registry._init_modules.add(package.name) - - if needs_update: - if new_install: - post_init = package.info.get('post_init_hook') - if post_init: - getattr(py_module, post_init)(cr, registry) - - if mode == 'update': - # validate the views that have not been checked yet - env['ir.ui.view']._validate_module_views(module_name) - - # need to commit any modification the module's installation or - # update made to the schema or data so the tests can run - # (separately in their own transaction) - # - # commit after processing every module as well, for - # easier debugging and continuing an interrupted migration - cr.commit_org() - # - # run tests - if os.environ.get('OPENUPGRADE_TESTS') and package.name is not None: - prefix = '.migrations' - registry.openupgrade_test_prefixes[package.name] = prefix - report.record_result(odoo.modules.module.run_unit_tests(module_name, openupgrade_prefix=prefix)) - # - # commit module_n state and version immediatly - # to avoid invalid database state if module_n+1 raises an - # exception - cr.commit_org() - # - - package.load_state = package.state - package.load_version = package.installed_version - package.state = 'installed' - for kind in ('init', 'demo', 'update'): - if hasattr(package, kind): - delattr(package, kind) - module.flush() - - extra_queries = odoo.sql_db.sql_counter - module_extra_query_count - test_queries - extras = [] - if test_queries: - extras.append(f'+{test_queries} test') - if extra_queries: - extras.append(f'+{extra_queries} other') - _logger.log( - module_log_level, "Module %s loaded in %.2fs%s, %s queries%s", - module_name, time.time() - module_t0, - f' (incl. {test_time:.2f}s test)' if test_time else '', - cr.sql_log_count - module_cursor_query_count, - f' ({", ".join(extras)})' if extras else '' - ) - if test_results and not test_results.wasSuccessful(): - _logger.error( - "Module %s: %d failures, %d errors of %d tests", - module_name, len(test_results.failures), len(test_results.errors), - test_results.testsRun - ) - - _logger.runbot("%s modules loaded in %.2fs, %s queries (+%s extra)", - len(graph), - time.time() - t0, - cr.sql_log_count - loading_cursor_query_count, - odoo.sql_db.sql_counter - loading_extra_query_count) # extra queries: testes, notify, any other closed cursor - - # - # restore commit method - cr.commit = cr.commit_org - cr.commit() - # - - return loaded_modules, processed_modules - - -def _load_marked_modules(cr, graph, states, force, progressdict, report, - loaded_modules, perform_checks, models_to_check=None): - """Loads modules marked with ``states``, adding them to ``graph`` and - ``loaded_modules`` and returns a list of installed/upgraded modules.""" - - if models_to_check is None: - models_to_check = set() - - processed_modules = [] - while True: - cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),)) - module_list = [name for (name,) in cr.fetchall() if name not in graph] - # - module_list = openupgrade_loading.add_module_dependencies(cr, module_list) - # - if not module_list: - break - graph.add_modules(cr, module_list, force) - _logger.debug('Updating graph with %d more modules', len(module_list)) - loaded, processed = _load_module_graph( - cr, graph, progressdict, report=report, skip_modules=loaded_modules, - perform_checks=perform_checks, models_to_check=models_to_check, - ) - processed_modules.extend(processed) - loaded_modules.extend(loaded) - if not processed: - break - return processed_modules - - -def _load_modules(db, force_demo=False, status=None, update_module=False): - initialize_sys_path() - - force = [] - if force_demo: - force.append('demo') - - models_to_check = set() - - with db.cursor() as cr: - if not odoo.modules.db.is_initialized(cr): - if not update_module: - _logger.error("Database %s not initialized, you can force it with `-i base`", cr.dbname) - return - _logger.info("init db") - odoo.modules.db.initialize(cr) - update_module = True # process auto-installed modules - tools.config["init"]["all"] = 1 - if not tools.config['without_demo']: - tools.config["demo"]['all'] = 1 - - # This is a brand new registry, just created in - # odoo.modules.registry.Registry.new(). - registry = odoo.registry(cr.dbname) - - if 'base' in tools.config['update'] or 'all' in tools.config['update']: - cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed')) - - # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) - graph = odoo.modules.graph.Graph() - graph.add_module(cr, 'base', force) - if not graph: - _logger.critical('module base cannot be loaded! (hint: verify addons-path)') - raise ImportError('Module `base` cannot be loaded! (hint: verify addons-path)') - - # processed_modules: for cleanup step after install - # loaded_modules: to avoid double loading - report = registry._assertion_report - loaded_modules, processed_modules = _load_module_graph( - cr, graph, status, perform_checks=update_module, - report=report, models_to_check=models_to_check) - - # - load_lang = tools.config.pop('load_language') - if load_lang or update_module: - # some base models are used below, so make sure they are set up - registry.setup_models(cr) - - if load_lang: - for lang in load_lang.split(','): - tools.load_language(cr, lang) - - # STEP 2: Mark other modules to be loaded/updated - if update_module: - env = api.Environment(cr, SUPERUSER_ID, {}) - Module = env['ir.module.module'] - _logger.info('updating modules list') - Module.update_list() - - _check_module_names(cr, itertools.chain(tools.config['init'], tools.config['update'])) - - module_names = [k for k, v in tools.config['init'].items() if v] - if module_names: - modules = Module.search([('state', '=', 'uninstalled'), ('name', 'in', module_names)]) - if modules: - modules.button_install() - - module_names = [k for k, v in tools.config['update'].items() if v] - if module_names: - # - # in standard Odoo, '--update all' just means: - # '--update base + upward (installed) dependencies. This breaks - # the chain when new glue modules are encountered. - # E.g. purchase in 8.0 depends on stock_account and report, - # both of which are new. They may be installed, but purchase as - # an upward dependency is not selected for upgrade. - # Therefore, explicitely select all installed modules for - # upgrading in OpenUpgrade in that case. - domain = [('state', '=', 'installed')] - if 'all' not in module_names: - domain.append(('name', 'in', module_names)) - modules = Module.search(domain) - # - if modules: - modules.button_upgrade() - - cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base')) - Module.invalidate_cache(['state']) - Module.flush() - - # STEP 3: Load marked modules (skipping base which was done in STEP 1) - # IMPORTANT: this is done in two parts, first loading all installed or - # partially installed modules (i.e. installed/to upgrade), to - # offer a consistent system to the second part: installing - # newly selected modules. - # We include the modules 'to remove' in the first step, because - # they are part of the "currently installed" modules. They will - # be dropped in STEP 6 later, before restarting the loading - # process. - # IMPORTANT 2: We have to loop here until all relevant modules have been - # processed, because in some rare cases the dependencies have - # changed, and modules that depend on an uninstalled module - # will not be processed on the first pass. - # It's especially useful for migrations. - previously_processed = -1 - while previously_processed < len(processed_modules): - previously_processed = len(processed_modules) - processed_modules += _load_marked_modules(cr, graph, - ['installed', 'to upgrade', 'to remove'], - force, status, report, loaded_modules, update_module, models_to_check) - if update_module: - processed_modules += _load_marked_modules(cr, graph, - ['to install'], force, status, report, - loaded_modules, update_module, models_to_check) - # check that new module dependencies have been properly installed after a migration/upgrade - cr.execute("SELECT name from ir_module_module WHERE state IN ('to install', 'to upgrade')") - module_list = [name for (name,) in cr.fetchall()] - if module_list: - _logger.error("Some modules have inconsistent states, some dependencies may be missing: %s", sorted(module_list)) - - # check that all installed modules have been loaded by the registry after a migration/upgrade - cr.execute("SELECT name from ir_module_module WHERE state = 'installed' and name != 'studio_customization'") - module_list = [name for (name,) in cr.fetchall() if name not in graph] - if module_list: - _logger.error("Some modules are not loaded, some dependencies or manifest may be missing: %s", sorted(module_list)) - - registry.loaded = True - registry.setup_models(cr) - - # STEP 3.5: execute migration end-scripts - migrations = odoo.modules.migration.MigrationManager(cr, graph) - for package in graph: - migrations.migrate_module(package, 'end') - - # STEP 3.6: apply remaining constraints in case of an upgrade - registry.finalize_constraints() - - # STEP 4: Finish and cleanup installations - if processed_modules: - env = api.Environment(cr, SUPERUSER_ID, {}) - cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""") - for (model, name) in cr.fetchall(): - if model in registry and not registry[model]._abstract: - _logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,base.group_user,1,0,0,0', - model, model.replace('.', '_'), model.replace('.', '_'), model.replace('.', '_')) - - cr.execute("SELECT model from ir_model") - for (model,) in cr.fetchall(): - if model in registry: - env[model]._check_removed_columns(log=True) - elif _logger.isEnabledFor(logging.INFO): # more an info that a warning... - _logger.runbot("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model) - - # Cleanup orphan records - env['ir.model.data']._process_end(processed_modules) - env['base'].flush() - - for kind in ('init', 'demo', 'update'): - tools.config[kind] = {} - - # STEP 5: Uninstall modules to remove - if update_module: - # Remove records referenced from ir_model_data for modules to be - # removed (and removed the references from ir_model_data). - cr.execute("SELECT name, id FROM ir_module_module WHERE state=%s", ('to remove',)) - modules_to_remove = dict(cr.fetchall()) - if modules_to_remove: - env = api.Environment(cr, SUPERUSER_ID, {}) - pkgs = reversed([p for p in graph if p.name in modules_to_remove]) - for pkg in pkgs: - uninstall_hook = pkg.info.get('uninstall_hook') - if uninstall_hook: - py_module = sys.modules['odoo.addons.%s' % (pkg.name,)] - getattr(py_module, uninstall_hook)(cr, registry) - - Module = env['ir.module.module'] - Module.browse(modules_to_remove.values()).module_uninstall() - # Recursive reload, should only happen once, because there should be no - # modules to remove next time - cr.commit() - _logger.info('Reloading registry once more after uninstalling modules') - api.Environment.reset() - registry = odoo.modules.registry.Registry.new( - cr.dbname, force_demo, status, update_module - ) - registry.check_tables_exist(cr) - cr.commit() - return registry - - # STEP 5.5: Verify extended fields on every model - # This will fix the schema of all models in a situation such as: - # - module A is loaded and defines model M; - # - module B is installed/upgraded and extends model M; - # - module C is loaded and extends model M; - # - module B and C depend on A but not on each other; - # The changes introduced by module C are not taken into account by the upgrade of B. - if models_to_check: - registry.init_models(cr, list(models_to_check), {'models_to_check': True}) - - # STEP 6: verify custom views on every model - if update_module: - env = api.Environment(cr, SUPERUSER_ID, {}) - env['res.groups']._update_user_groups_view() - View = env['ir.ui.view'] - for model in registry: - try: - View._validate_custom_views(model) - except Exception as e: - _logger.warning('invalid custom view(s) for model %s: %s', model, tools.ustr(e)) - - if report.wasSuccessful(): - _logger.info('Modules loaded.') - else: - _logger.error('At least one test failed when loading the modules.') - - # STEP 8: call _register_hook on every model - # This is done *exactly once* when the registry is being loaded. See the - # management of those hooks in `Registry.setup_models`: all the calls to - # setup_models() done here do not mess up with hooks, as registry.ready - # is False. - env = api.Environment(cr, SUPERUSER_ID, {}) - for model in env.values(): - model._register_hook() - env['base'].flush() - - # STEP 9: save installed/updated modules for post-install tests - registry.updated_modules += processed_modules - -loading.load_module_graph = _load_module_graph -loading.load_marked_modules = _load_marked_modules -loading.load_modules = _load_modules -odoo.modules.load_modules = _load_modules diff --git a/openupgrade_framework/odoo_patch/odoo/modules/migration.py b/openupgrade_framework/odoo_patch/odoo/modules/migration.py index 0346c2b8c559..65b495388b6c 100644 --- a/openupgrade_framework/odoo_patch/odoo/modules/migration.py +++ b/openupgrade_framework/odoo_patch/odoo/modules/migration.py @@ -1,118 +1,23 @@ -# flake8: noqa -# pylint: skip-file - -import logging -import os -from os.path import join as opj -import odoo.release as release -from odoo.tools.parse_version import parse_version - -import odoo -from odoo.modules.migration import load_script -from odoo.modules import migration - -_logger = logging.getLogger(__name__) - - -if True: - def _migrate_module(self, pkg, stage): - assert stage in ('pre', 'post', 'end') - stageformat = { - 'pre': '[>%s]', - 'post': '[%s>]', - 'end': '[$%s]', - } - state = pkg.state if stage in ('pre', 'post') else getattr(pkg, 'load_state', None) - - # - # In openupgrade, also run migration scripts upon installation. - # We want to always pass in pre and post migration files and use a new - # argument in the migrate decorator (explained in the docstring) - # to decide if we want to do something if a new module is installed - # during the migration. - if not (hasattr(pkg, 'update') or state in ('to upgrade', 'to install')): - # - return - - def convert_version(version): - if version.count('.') >= 2: - return version # the version number already containt the server version - return "%s.%s" % (release.major_version, version) - - def _get_migration_versions(pkg, stage): - versions = sorted({ - ver - for lv in self.migrations[pkg.name].values() - for ver, lf in lv.items() - if lf - }, key=lambda k: parse_version(convert_version(k))) - if "0.0.0" in versions: - # reorder versions - versions.remove("0.0.0") - if stage == "pre": - versions.insert(0, "0.0.0") - else: - versions.append("0.0.0") - return versions - - def _get_migration_files(pkg, version, stage): - """ return a list of migration script files - """ - m = self.migrations[pkg.name] - lst = [] - - mapping = { - 'module': opj(pkg.name, 'migrations'), - 'module_upgrades': opj(pkg.name, 'upgrades'), - } - - for path in odoo.upgrade.__path__: - if os.path.exists(opj(path, pkg.name)): - mapping['upgrade'] = opj(path, pkg.name) - break - - for x in mapping: - if version in m.get(x): - for f in m[x][version]: - if not f.startswith(stage + '-'): - continue - lst.append(opj(mapping[x], version, f)) - lst.sort() - return lst - - installed_version = getattr(pkg, 'load_version', pkg.installed_version) or '' - parsed_installed_version = parse_version(installed_version) - current_version = parse_version(convert_version(pkg.data['version'])) - - versions = _get_migration_versions(pkg, stage) - - for version in versions: - if ((version == "0.0.0" and parsed_installed_version < current_version) - or parsed_installed_version < parse_version(convert_version(version)) <= current_version): - - strfmt = {'addon': pkg.name, - 'stage': stage, - 'version': stageformat[stage] % version, - } - - for pyfile in _get_migration_files(pkg, version, stage): - name, ext = os.path.splitext(os.path.basename(pyfile)) - if ext.lower() != '.py': - continue - mod = None - try: - mod = load_script(pyfile, name) - _logger.info('module %(addon)s: Running migration %(version)s %(name)s' % dict(strfmt, name=mod.__name__)) - migrate = mod.migrate - except ImportError: - _logger.exception('module %(addon)s: Unable to load %(stage)s-migration file %(file)s' % dict(strfmt, file=pyfile)) - raise - except AttributeError: - _logger.error('module %(addon)s: Each %(stage)s-migration file must have a "migrate(cr, installed_version)" function' % strfmt) - else: - migrate(self.cr, installed_version) - finally: - if mod: - del mod - -migration.migrate_module = _migrate_module +# Copyright Odoo Community Association (OCA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.modules.migration import MigrationManager + + +def migrate_module(self, pkg, stage): + """In openupgrade, also run migration scripts upon installation. + We want to always pass in pre and post migration files and use a new + argument in the migrate decorator (explained in the docstring) + to decide if we want to do something if a new module is installed + during the migration. + We trick Odoo into running the scripts by setting the update attribute if necessary. + """ + has_update = hasattr(pkg, "update") + if not has_update: + pkg.update = True + MigrationManager.migrate_module._original_method(self, pkg, stage) + if not has_update: + delattr(pkg, "update") + + +migrate_module._original_method = MigrationManager.migrate_module +MigrationManager.migrate_module = migrate_module diff --git a/openupgrade_framework/odoo_patch/odoo/modules/registry.py b/openupgrade_framework/odoo_patch/odoo/modules/registry.py deleted file mode 100644 index 4c5f50d4e714..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/modules/registry.py +++ /dev/null @@ -1,58 +0,0 @@ -# flake8: noqa -# pylint: skip-file - -from collections import deque -from contextlib import closing -import odoo -from odoo.tools.lru import LRU - -from odoo.modules import registry - - -if True: - - def _init(self, db_name): - self.models = {} # model name/model instance mapping - self._sql_constraints = set() - self._init = True - self._assertion_report = odoo.tests.runner.OdooTestResult() - self._fields_by_model = None - self._ordinary_tables = None - self._constraint_queue = deque() - self.__cache = LRU(8192) - - # modules fully loaded (maintained during init phase by `loading` module) - self._init_modules = set() - self.updated_modules = [] # installed/updated modules - # - self.openupgrade_test_prefixes = {} - # - self.loaded_xmlids = set() - - self.db_name = db_name - self._db = odoo.sql_db.db_connect(db_name) - - # cursor for test mode; None means "normal" mode - self.test_cr = None - self.test_lock = None - - # Indicates that the registry is - self.loaded = False # whether all modules are loaded - self.ready = False # whether everything is set up - - # Inter-process signaling: - # The `base_registry_signaling` sequence indicates the whole registry - # must be reloaded. - # The `base_cache_signaling sequence` indicates all caches must be - # invalidated (i.e. cleared). - self.registry_sequence = None - self.cache_sequence = None - - # Flags indicating invalidation of the registry or the cache. - self.registry_invalidated = False - self.cache_invalidated = False - - with closing(self.cursor()) as cr: - self.has_unaccent = odoo.modules.db.has_unaccent(cr) - -registry.init = _init diff --git a/openupgrade_framework/odoo_patch/odoo/service/__init__.py b/openupgrade_framework/odoo_patch/odoo/service/__init__.py deleted file mode 100644 index a96314d0f684..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/service/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Import disabled, because the function run_unit_tests() -# disappeared in V14. -# TODO: OpenUpgrade Core maintainers : FIXME. -# from . import server diff --git a/openupgrade_framework/odoo_patch/odoo/service/server.py b/openupgrade_framework/odoo_patch/odoo/service/server.py deleted file mode 100644 index a2a998e69df3..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/service/server.py +++ /dev/null @@ -1,71 +0,0 @@ -# flake8: noqa -# pylint: skip-file - -import logging -import os -import time - -import odoo -from odoo.tools import config -from odoo.modules.registry import Registry - -from odoo.service import server -from odoo.service.server import load_test_file_py - -_logger = logging.getLogger(__name__) - - -def preload_registries(dbnames): - """ Preload a registries, possibly run a test file.""" - # TODO: move all config checks to args dont check tools.config here - dbnames = dbnames or [] - rc = 0 - for dbname in dbnames: - try: - update_module = config['init'] or config['update'] - registry = Registry.new(dbname, update_module=update_module) - - # run test_file if provided - if config['test_file']: - test_file = config['test_file'] - if not os.path.isfile(test_file): - _logger.warning('test file %s cannot be found', test_file) - elif not test_file.endswith('py'): - _logger.warning('test file %s is not a python file', test_file) - else: - _logger.info('loading test file %s', test_file) - with odoo.api.Environment.manage(): - load_test_file_py(registry, test_file) - - # run post-install tests - if config['test_enable']: - t0 = time.time() - t0_sql = odoo.sql_db.sql_counter - module_names = (registry.updated_modules if update_module else - sorted(registry._init_modules)) - _logger.info("Starting post tests") - tests_before = registry._assertion_report.testsRun - with odoo.api.Environment.manage(): - for module_name in module_names: - result = loader.run_suite(loader.make_suite(module_name, 'post_install'), module_name) - registry._assertion_report.update(result) - # - # run deferred unit tests - for module_name, prefix in registry.openupgrade_test_prefixes: - result = run_unit_tests(module_name, position='post_install', openupgrade_prefix=prefix) - registry._assertion_report.record_result(result) - # - _logger.info("%d post-tests in %.2fs, %s queries", - registry._assertion_report.testsRun - tests_before, - time.time() - t0, - odoo.sql_db.sql_counter - t0_sql) - - if not registry._assertion_report.wasSuccessful(): - rc += 1 - except Exception: - _logger.critical('Failed to initialize database `%s`.', dbname, exc_info=True) - return -1 - return rc - - -server.preload_registries = preload_registries diff --git a/openupgrade_framework/odoo_patch/odoo/tools/view_validation.py b/openupgrade_framework/odoo_patch/odoo/tools/view_validation.py deleted file mode 100644 index e6c8243241af..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/tools/view_validation.py +++ /dev/null @@ -1,29 +0,0 @@ -# flake8: noqa -# pylint: skip-file - -# from odoo.addons.openupgrade_framework.openupgrade import openupgrade_log - -from odoo.tools import view_validation -from odoo.tools.view_validation import _validators, _logger - - -def _valid_view(arch, **kwargs): - for pred in _validators[arch.tag]: - # - # Do not raise blocking error, because it's normal to - # have inconsistent views in an openupgrade process - check = pred(arch, **kwargs) or 'Warning' - # - if not check: - _logger.error("Invalid XML: %s", pred.__doc__) - return False - if check == "Warning": - # - # Don't show this warning as useless and too much verbose - # _logger.warning("Invalid XML: %s", pred.__doc__) - # - return "Warning" - return True - - -view_validation.valid_view = _valid_view diff --git a/openupgrade_framework/openupgrade/__init__.py b/openupgrade_framework/openupgrade/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/openupgrade_framework/openupgrade/openupgrade_loading.py b/openupgrade_framework/openupgrade/openupgrade_loading.py deleted file mode 100644 index ca3e1d43067d..000000000000 --- a/openupgrade_framework/openupgrade/openupgrade_loading.py +++ /dev/null @@ -1,318 +0,0 @@ -# Copyright 2011-2015 Therp BV -# Copyright 2016-2019 Opener B.V. -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -# flake8: noqa: C901 - -import logging - -from openupgradelib.openupgrade_tools import table_exists - -from odoo import release -from odoo.modules.module import get_module_path -from odoo.tools.safe_eval import safe_eval -from odoo.tools.config import config - -# A collection of functions used in -# odoo/modules/loading.py - -logger = logging.getLogger("OpenUpgrade") - - -def add_module_dependencies(cr, module_list): - """ - Select (new) dependencies from the modules in the list - so that we can inject them into the graph at upgrade - time. Used in the modified OpenUpgrade Server, - not to be called from migration scripts - - Also take the OpenUpgrade configuration directives 'forced_deps' - and 'autoinstall' into account. From any additional modules - that these directives can add, the dependencies are added as - well (but these directives are not checked for the occurrence - of any of the dependencies). - """ - if not module_list: - return module_list - - modules_in = list(module_list) - forced_deps = safe_eval( - config.get_misc( - "openupgrade", - "forced_deps_" + release.version, - config.get_misc("openupgrade", "forced_deps", "{}"), - ) - ) - - autoinstall = safe_eval( - config.get_misc( - "openupgrade", - "autoinstall_" + release.version, - config.get_misc("openupgrade", "autoinstall", "{}"), - ) - ) - - for module in list(module_list): - module_list += forced_deps.get(module, []) - module_list += autoinstall.get(module, []) - - module_list = list(set(module_list)) - - dependencies = module_list - while dependencies: - cr.execute( - """ - SELECT DISTINCT dep.name - FROM - ir_module_module, - ir_module_module_dependency dep - WHERE - module_id = ir_module_module.id - AND ir_module_module.name in %s - AND dep.name not in %s - """, - ( - tuple(dependencies), - tuple(module_list), - ), - ) - - dependencies = [x[0] for x in cr.fetchall()] - module_list += dependencies - - # Select auto_install modules of which all dependencies - # are fulfilled based on the modules we know are to be - # installed - cr.execute( - """ - SELECT name from ir_module_module WHERE state IN %s - """, - (("installed", "to install", "to upgrade"),), - ) - modules = list(set(module_list + [row[0] for row in cr.fetchall()])) - cr.execute( - """ - SELECT name from ir_module_module m - WHERE auto_install IS TRUE - AND state = 'uninstalled' - AND NOT EXISTS( - SELECT id FROM ir_module_module_dependency d - WHERE d.module_id = m.id - AND name NOT IN %s) - """, - (tuple(modules),), - ) - auto_modules = [row[0] for row in cr.fetchall() if get_module_path(row[0])] - if auto_modules: - logger.info("Selecting autoinstallable modules %s", ",".join(auto_modules)) - module_list += auto_modules - - # Set proper state for new dependencies so that any init scripts are run - cr.execute( - """ - UPDATE ir_module_module SET state = 'to install' - WHERE name IN %s AND name NOT IN %s AND state = 'uninstalled' - """, - (tuple(module_list), tuple(modules_in)), - ) - return module_list - - -def log_model(model, local_registry): - """ - OpenUpgrade: Store the characteristics of the BaseModel and its fields - in the local registry, so that we can compare changes with the - main registry - """ - - if not model._name: - return - - typemap = {"monetary": "float"} - - # Deferred import to prevent import loop - from odoo import models - - # persistent models only - if isinstance(model, models.TransientModel): - return - - def isfunction(model, k): - if ( - model._fields[k].compute - and not model._fields[k].related - and not model._fields[k].company_dependent - ): - return "function" - return "" - - def isproperty(model, k): - if model._fields[k].company_dependent: - return "property" - return "" - - def isrelated(model, k): - if model._fields[k].related: - return "related" - return "" - - def _get_relation(v): - if v.type in ("many2many", "many2one", "one2many"): - return v.comodel_name - elif v.type == "many2one_reference": - return v.model_field - else: - return "" - - model_registry = local_registry.setdefault(model._name, {}) - if model._inherits: - model_registry["_inherits"] = {"_inherits": str(model._inherits)} - for k, v in model._fields.items(): - properties = { - "type": typemap.get(v.type, v.type), - "isfunction": isfunction(model, k), - "isproperty": isproperty(model, k), - "isrelated": isrelated(model, k), - "relation": _get_relation(v), - "table": v.relation if v.type == "many2many" else "", - "required": v.required and "required" or "", - "stored": v.store and "stored" or "", - "selection_keys": "", - "req_default": "", - "hasdefault": model._fields[k].default and "hasdefault" or "", - "inherits": "", - } - if v.type == "selection": - if isinstance(v.selection, (tuple, list)): - properties["selection_keys"] = str(sorted([x[0] for x in v.selection])) - else: - properties["selection_keys"] = "function" - elif v.type == "binary": - properties["attachment"] = str(getattr(v, "attachment", False)) - default = model._fields[k].default - if v.required and default: - if ( - callable(default) - or isinstance(default, str) - and getattr(model._fields[k], default, False) - and callable(getattr(model._fields[k], default)) - ): - # todo: in OpenERP 5 (and in 6 as well), - # literals are wrapped in a lambda function - properties["req_default"] = "function" - else: - properties["req_default"] = str(default) - for key, value in properties.items(): - if value: - model_registry.setdefault(k, {})[key] = value - - -def get_record_id(cr, module, model, field, mode): - """ - OpenUpgrade: get or create the id from the record table matching - the key parameter values - """ - cr.execute( - "SELECT id FROM openupgrade_record " - "WHERE module = %s AND model = %s AND " - "field = %s AND mode = %s AND type = %s", - (module, model, field, mode, "field"), - ) - record = cr.fetchone() - if record: - return record[0] - cr.execute( - "INSERT INTO openupgrade_record " - "(module, model, field, mode, type) " - "VALUES (%s, %s, %s, %s, %s)", - (module, model, field, mode, "field"), - ) - cr.execute( - "SELECT id FROM openupgrade_record " - "WHERE module = %s AND model = %s AND " - "field = %s AND mode = %s AND type = %s", - (module, model, field, mode, "field"), - ) - return cr.fetchone()[0] - - -def compare_registries(cr, module, registry, local_registry): - """ - OpenUpgrade: Compare the local registry with the global registry, - log any differences and merge the local registry with - the global one. - """ - if not table_exists(cr, "openupgrade_record"): - return - for model, flds in local_registry.items(): - registry.setdefault(model, {}) - for field, attributes in flds.items(): - old_field = registry[model].setdefault(field, {}) - mode = old_field and "modify" or "create" - record_id = False - for key, value in attributes.items(): - if key not in old_field or old_field[key] != value: - if not record_id: - record_id = get_record_id(cr, module, model, field, mode) - cr.execute( - "SELECT id FROM openupgrade_attribute " - "WHERE name = %s AND value = %s AND " - "record_id = %s", - (key, value, record_id), - ) - if not cr.fetchone(): - cr.execute( - "INSERT INTO openupgrade_attribute " - "(name, value, record_id) VALUES (%s, %s, %s)", - (key, value, record_id), - ) - old_field[key] = value - - -def update_field_xmlid(model, field): - """OpenUpgrade edit start: In rare cases, an old module defined a field - on a model that is not defined in another module earlier in the - chain of inheritance. Then we need to assign the ir.model.fields' - xmlid to this other module, otherwise the column would be dropped - when uninstalling the first module. - An example is res.partner#display_name defined in 7.0 by - account_report_company, but now the field belongs to the base - module - Given that we arrive here in order of inheritance, we simply check - if the field's xmlid belongs to a module already loaded, and if not, - update the record with the correct module name.""" - model.env.cr.execute( - "SELECT f.*, d.module, d.id as xmlid_id, d.name as xmlid " - "FROM ir_model_fields f LEFT JOIN ir_model_data d " - "ON f.id=d.res_id and d.model='ir.model.fields' WHERE f.model=%s", - (model._name,), - ) - for rec in model.env.cr.dictfetchall(): - if ( - "module" in model.env.context - and rec["module"] - and rec["name"] in model._fields.keys() - and rec["module"] != model.env.context["module"] - and rec["module"] not in model.env.registry._init_modules - ): - logging.getLogger(__name__).info( - "Moving XMLID for ir.model.fields record of %s#%s " "from %s to %s", - model._name, - rec["name"], - rec["module"], - model.env.context["module"], - ) - model.env.cr.execute( - "SELECT id FROM ir_model_data WHERE module=%(module)s " - "AND name=%(xmlid)s", - dict(rec, module=model.env.context["module"]), - ) - if model.env.cr.fetchone(): - logging.getLogger(__name__).info( - "Aborting, an XMLID for this module already exists." - ) - continue - model.env.cr.execute( - "UPDATE ir_model_data SET module=%(module)s " "WHERE id=%(xmlid_id)s", - dict(rec, module=model.env.context["module"]), - ) diff --git a/openupgrade_framework/readme/CONFIGURE.rst b/openupgrade_framework/readme/CONFIGURE.rst index bb245fb3b72a..74b01f07a727 100644 --- a/openupgrade_framework/readme/CONFIGURE.rst +++ b/openupgrade_framework/readme/CONFIGURE.rst @@ -1,7 +1,15 @@ -To use this module, do not install it. Instead, you should add the name in your -``odoo.cfg`` module : +* call your odoo instance with the option ``--load=web,openupgrade_framework`` + +or + +* add the key to your configuration file: .. code-block:: shell [options] server_wide_modules = web,openupgrade_framework + +When you load the module in either way of these ways, and you have the +`openupgrade_scripts` module in your addons path available, the +`--upgrade-path` option of Odoo will be set automatically to the location +of the OpenUpgrade migration scripts. diff --git a/openupgrade_framework/readme/CREDITS.rst b/openupgrade_framework/readme/CREDITS.rst new file mode 100644 index 000000000000..c57b7d4144c9 --- /dev/null +++ b/openupgrade_framework/readme/CREDITS.rst @@ -0,0 +1,4 @@ +Many developers have contributed to the OpenUpgrade framework in its previous +incarnation. Their original contributions may no longer needed, or they are +no longer recognizable in their current form but OpenUpgrade would not have +existed at this point without them. diff --git a/openupgrade_framework/readme/DESCRIPTION.rst b/openupgrade_framework/readme/DESCRIPTION.rst index efceae7e621b..dc04151fe07f 100644 --- a/openupgrade_framework/readme/DESCRIPTION.rst +++ b/openupgrade_framework/readme/DESCRIPTION.rst @@ -1,2 +1,19 @@ -This module is a technical module, to allow to make migrations between -major versions of Odoo. +This module is a technical module that contains a number of monkeypatches +to improve the behaviour of Odoo when migrating your database using the +OpenUpgrade migration scripts: + +* Prevent dropping columns or tables in the database when fields or models + are obsoleted in the Odoo data model of the target release. After the + migration, you can review and delete unused database tables and columns + using `database_cleanup`. See + https://odoo-community.org/shop/product/database-cleanup-918 +* When data records are deleted during the migration (such as views or other + system records), this is done in a secure mode. If the deletion fails because + of some unforeseen dependency, the deletion will be cancelled and a message + is logged, after which the migration continues. +* Prevent a number of log messages that do not apply when using OpenUpgrade. +* Suppress log messages about failed view validation, which are to be expected + during a migration. +* Run migration scripts for modules that are installed as new dependencies + of upgraded modules (when there are such scripts for those particular + modules) diff --git a/openupgrade_framework/readme/DEVELOP.rst b/openupgrade_framework/readme/DEVELOP.rst index 44c17e65d41d..e4f32305c623 100644 --- a/openupgrade_framework/readme/DEVELOP.rst +++ b/openupgrade_framework/readme/DEVELOP.rst @@ -1,14 +1,11 @@ -This module contains two folders: +The `odoo_patch` folder contains python files in a tree that mimicks the +folder tree of the Odoo project. It contains a number of monkey patches +to improve the migration of an Odoo database between two major versions. +So far, we are able to make everything work without overwriting large blocks +of code, but if larger patches need to be added, please use the method +described below: -odoo_patch ----------- - -This folder contains python files, that correspond to python files present -in the folder ``odoo`` of the Odoo project. - -it contains a lot of monkey patches, to make working an upgrade -between two major versions. To see the patches added, you can use ``meld`` tools: ``meld PATH_TO_ODOO_FOLDER/odoo/ PATH_TO_OPENUPGRADE_FRAMEWORK_MODULE/odoo_patch`` @@ -53,8 +50,3 @@ To make more easy the diff analysis : # # Comment the code, instead of removing it. # - -openupgrade ------------ - -Contains extra functions, called by the patches introduced in the first folder. diff --git a/openupgrade_framework/readme/INSTALL.rst b/openupgrade_framework/readme/INSTALL.rst new file mode 100644 index 000000000000..020d4947fb63 --- /dev/null +++ b/openupgrade_framework/readme/INSTALL.rst @@ -0,0 +1,2 @@ +This module does not need to be installed on a database. +It simply needs to be available via your ``addons-path``. diff --git a/openupgrade_framework/static/description/index.html b/openupgrade_framework/static/description/index.html new file mode 100644 index 000000000000..fe170612687d --- /dev/null +++ b/openupgrade_framework/static/description/index.html @@ -0,0 +1,527 @@ + + + + + + +Openupgrade Framework + + + +
+

Openupgrade Framework

+ + +

Beta License: AGPL-3 OCA/openupgrade Translate me on Weblate

+

This module is a technical module that contains a number of monkeypatches +to improve the behaviour of Odoo when migrating your database using the +OpenUpgrade migration scripts:

+
    +
  • Prevent dropping columns or tables in the database when fields or models +are obsoleted in the Odoo data model of the target release. After the +migration, you can review and delete unused database tables and columns +using database_cleanup. See +https://odoo-community.org/shop/product/database-cleanup-918
  • +
  • When data records are deleted during the migration (such as views or other +system records), this is done in a secure mode. If the deletion fails because +of some unforeseen dependency, the deletion will be cancelled and a message +is logged, after which the migration continues.
  • +
  • Prevent a number of log messages that do not apply when using OpenUpgrade.
  • +
  • Suppress log messages containing instructions for developers of Odoo S.A. +with regards to their own set of closed source migration scripts.
  • +
  • Suppress log messages about failed view validation, which are to be expected +during a migration.
  • +
  • Run migration scripts for modules that are installed as new dependencies +of upgraded modules (when there are such scripts for those particular +modules)
  • +
+

Table of contents

+ +
+

Installation

+

This module does not need to be installed on a database. +It simply needs to be available via your addons-path.

+
+
+

Configuration

+
    +
  • call your odoo instance with the option --load=web,openupgrade_framework
  • +
+

or

+
    +
  • add the key to your configuration file:
  • +
+
+[options]
+server_wide_modules =  web,openupgrade_framework
+
+

When you load the module in either way of these ways, and you have the +openupgrade_scripts module in your addons path available, the +–upgrade-path option of Odoo will be set automatically to the location +of the OpenUpgrade migration scripts.

+
+
+

Development

+

The odoo_patch folder contains python files in a tree that mimicks the +folter tree of the Odoo project. It contains a number of monkey patches +to improve the migration of an Odoo database between two major versions.

+

So far, we are able to make everything work without overwriting large blocks +of code, but if larger patches need to be added, please use the method +described below:

+

To see the patches added, you can use meld tools:

+

meld PATH_TO_ODOO_FOLDER/odoo/ PATH_TO_OPENUPGRADE_FRAMEWORK_MODULE/odoo_patch

+

To make more easy the diff analysis :

+
    +
  • Make sure the python files has the same path as the original one.
  • +
  • Keep the same indentation as the original file. (using if True: if required)
  • +
  • Add the following two lines at the beginning of your file, to avoid flake8 / pylint +errors
  • +
+
+# flake8: noqa
+# pylint: skip-file
+
+
    +
  • When you want to change the code. add the following tags:

    +
    +
      +
    • For an addition:
    • +
    +
    +
  • +
+
+# <OpenUpgrade:ADD>
+some code...
+# </OpenUpgrade>
+
+* For a change:
+
+
+# <OpenUpgrade:CHANGE>
+some code...
+# </OpenUpgrade>
+
+* For a removal:
+
+
+# <OpenUpgrade:REMOVE>
+# Comment the code, instead of removing it.
+# </OpenUpgrade>
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Therp BV
  • +
  • Opener B.V.
  • +
  • GRAP
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

Many developers have contributed to the OpenUpgrade framework in its previous +incarnation. Their original contributions may no longer needed, or they are +no longer recognizable in their current form but OpenUpgrade would not have +existed at this point without them.

+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/openupgrade project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + From c54c544db96075edb5281cbf35788b5cd2164f81 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 20 Jan 2021 10:17:28 +0000 Subject: [PATCH 33/92] [UPD] README.rst --- openupgrade_framework/README.rst | 20 +++++++++---------- .../static/description/index.html | 18 ++++++++--------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/openupgrade_framework/README.rst b/openupgrade_framework/README.rst index e7e759ed6c63..84fe15d368f4 100644 --- a/openupgrade_framework/README.rst +++ b/openupgrade_framework/README.rst @@ -13,11 +13,11 @@ Openupgrade Framework .. |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%2Fopenupgrade-lightgray.png?logo=github - :target: https://github.com/OCA/openupgrade/tree/14.0/openupgrade_framework - :alt: OCA/openupgrade +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2FOpenUpgrade-lightgray.png?logo=github + :target: https://github.com/OCA/OpenUpgrade/tree/14.0/openupgrade_framework + :alt: OCA/OpenUpgrade .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/openupgrade-14-0/openupgrade-14-0-openupgrade_framework + :target: https://translation.odoo-community.org/projects/OpenUpgrade-14-0/OpenUpgrade-14-0-openupgrade_framework :alt: Translate me on Weblate |badge1| |badge2| |badge3| |badge4| @@ -36,8 +36,6 @@ OpenUpgrade migration scripts: of some unforeseen dependency, the deletion will be cancelled and a message is logged, after which the migration continues. * Prevent a number of log messages that do not apply when using OpenUpgrade. -* Suppress log messages containing instructions for developers of Odoo S.A. - with regards to their own set of closed source migration scripts. * Suppress log messages about failed view validation, which are to be expected during a migration. * Run migration scripts for modules that are installed as new dependencies @@ -67,7 +65,7 @@ or .. code-block:: shell [options] - server_wide_modules = web,openupgrade_framework + server_wide_modules = web,openupgrade_framework When you load the module in either way of these ways, and you have the `openupgrade_scripts` module in your addons path available, the @@ -78,7 +76,7 @@ Development =========== The `odoo_patch` folder contains python files in a tree that mimicks the -folter tree of the Odoo project. It contains a number of monkey patches +folder tree of the Odoo project. It contains a number of monkey patches to improve the migration of an Odoo database between two major versions. So far, we are able to make everything work without overwriting large blocks @@ -133,10 +131,10 @@ To make more easy the diff analysis : Bug Tracker =========== -Bugs are tracked on `GitHub Issues `_. +Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -177,6 +175,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/openupgrade `_ project on GitHub. +This module is part of the `OCA/OpenUpgrade `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/openupgrade_framework/static/description/index.html b/openupgrade_framework/static/description/index.html index fe170612687d..e477deb7c34b 100644 --- a/openupgrade_framework/static/description/index.html +++ b/openupgrade_framework/static/description/index.html @@ -3,7 +3,7 @@ - + Openupgrade Framework -
+

Openupgrade Framework

-

Beta License: AGPL-3 OCA/openupgrade Translate me on Weblate

+

Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate

This module is a technical module that contains a number of monkeypatches to improve the behaviour of Odoo when migrating your database using the OpenUpgrade migration scripts:

@@ -382,8 +382,6 @@

Openupgrade Framework

of some unforeseen dependency, the deletion will be cancelled and a message is logged, after which the migration continues.
  • Prevent a number of log messages that do not apply when using OpenUpgrade.
  • -
  • Suppress log messages containing instructions for developers of Odoo S.A. -with regards to their own set of closed source migration scripts.
  • Suppress log messages about failed view validation, which are to be expected during a migration.
  • Run migration scripts for modules that are installed as new dependencies @@ -422,7 +420,7 @@

    Configuration

     [options]
    -server_wide_modules =  web,openupgrade_framework
    +server_wide_modules = web,openupgrade_framework
     

    When you load the module in either way of these ways, and you have the openupgrade_scripts module in your addons path available, the @@ -432,7 +430,7 @@

    Configuration

    Development

    The odoo_patch folder contains python files in a tree that mimicks the -folter tree of the Odoo project. It contains a number of monkey patches +folder tree of the Odoo project. It contains a number of monkey patches to improve the migration of an Odoo database between two major versions.

    So far, we are able to make everything work without overwriting large blocks of code, but if larger patches need to be added, please use the method @@ -481,10 +479,10 @@

    Development

    Bug Tracker

    -

    Bugs are tracked on GitHub Issues. +

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed -feedback.

    +feedback.

    Do not contact contributors directly about support or help with technical issues.

    @@ -518,7 +516,7 @@

    Maintainers

    OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

    -

    This module is part of the OCA/openupgrade project on GitHub.

    +

    This module is part of the OCA/OpenUpgrade project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

  • From 1bc01e432b53c14db62698d8cf605e043266f4a5 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 20 Jan 2021 10:17:28 +0000 Subject: [PATCH 34/92] [ADD] icon.png --- .../static/description/icon.png | Bin 0 -> 9455 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 openupgrade_framework/static/description/icon.png diff --git a/openupgrade_framework/static/description/icon.png b/openupgrade_framework/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 From 080d473629d6f46e74cd81d83f00e27baf10943f Mon Sep 17 00:00:00 2001 From: mreficent Date: Thu, 25 Mar 2021 02:40:23 +0100 Subject: [PATCH 35/92] [FIX] _logger.warn -> _logger.warning --- .../odoo_patch/odoo/addons/base/models/ir_ui_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py index 8e880db17188..eb65f0c0aada 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py @@ -36,7 +36,7 @@ def handle_view_error( from_traceback=from_traceback ) except ValueError: - _logger.warn( + _logger.warning( "Can't render custom view %s for model %s. " "Assuming you are migrating between major versions of " "Odoo, this view is now set to inactive. Please " From dfed61ff969dfaa57f0cd68121ed694afc052abb Mon Sep 17 00:00:00 2001 From: mreficent Date: Thu, 25 Mar 2021 02:44:57 +0100 Subject: [PATCH 36/92] [FIX] doc: base must be included in --load parameter If we don't include it, /jsonrpc route doesn't work. --- openupgrade_framework/readme/CONFIGURE.rst | 2 +- openupgrade_framework/static/description/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openupgrade_framework/readme/CONFIGURE.rst b/openupgrade_framework/readme/CONFIGURE.rst index 74b01f07a727..8fad427bf4ac 100644 --- a/openupgrade_framework/readme/CONFIGURE.rst +++ b/openupgrade_framework/readme/CONFIGURE.rst @@ -1,4 +1,4 @@ -* call your odoo instance with the option ``--load=web,openupgrade_framework`` +* call your odoo instance with the option ``--load=base,web,openupgrade_framework`` or diff --git a/openupgrade_framework/static/description/index.html b/openupgrade_framework/static/description/index.html index e477deb7c34b..b7d6f87383ea 100644 --- a/openupgrade_framework/static/description/index.html +++ b/openupgrade_framework/static/description/index.html @@ -412,7 +412,7 @@

    Installation

    Configuration

      -
    • call your odoo instance with the option --load=web,openupgrade_framework
    • +
    • call your odoo instance with the option --load=base,web,openupgrade_framework

    or

      From 79587a507db059be19b491a736cf8e9b7d371ff9 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Thu, 25 Mar 2021 18:30:16 +0000 Subject: [PATCH 37/92] [UPD] README.rst --- openupgrade_framework/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openupgrade_framework/README.rst b/openupgrade_framework/README.rst index 84fe15d368f4..12e60cd6510b 100644 --- a/openupgrade_framework/README.rst +++ b/openupgrade_framework/README.rst @@ -56,7 +56,7 @@ It simply needs to be available via your ``addons-path``. Configuration ============= -* call your odoo instance with the option ``--load=web,openupgrade_framework`` +* call your odoo instance with the option ``--load=base,web,openupgrade_framework`` or From a45d0147f8c81e4301d3115051af771170a38bb8 Mon Sep 17 00:00:00 2001 From: mreficent Date: Thu, 8 Apr 2021 14:05:50 +0200 Subject: [PATCH 38/92] [FIX] base: Don't fail uninstalling when missing models If you have a module in previous versions that adds data on a model, and such model is not loaded in the registry in current version because the module is absent in it, you can't uninstall such module, getting this error: File "odoo/odoo/addons/base/models/ir_model.py", line 1945, in _module_data_uninstall delete(self.env[model].browse(item[1] for item in items)) File "odoo/odoo/api.py", line 463, in __getitem__ return self.registry[model_name]._browse(self, (), ()) File "odoo/odoo/modules/registry.py", line 177, in __getitem__ return self.models[model_name] KeyError: 'model' With this patch, data cleanup of such model is skipped and there's no crash. --- .../odoo_patch/odoo/__init__.py | 2 +- .../odoo/addons/base/models/ir_model.py | 15 ++++++- openupgrade_framework/odoo_patch/odoo/api.py | 43 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 openupgrade_framework/odoo_patch/odoo/api.py diff --git a/openupgrade_framework/odoo_patch/odoo/__init__.py b/openupgrade_framework/odoo_patch/odoo/__init__.py index c969456c0055..64a54f17e628 100644 --- a/openupgrade_framework/odoo_patch/odoo/__init__.py +++ b/openupgrade_framework/odoo_patch/odoo/__init__.py @@ -1 +1 @@ -from . import addons, models, modules +from . import addons, api, models, modules diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py index 41a45c636b96..6ce0014af9cc 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py @@ -43,10 +43,23 @@ def _drop_column(self): IrModel._drop_table = _drop_table +@api.model +def _module_data_uninstall(self, modules_to_remove): + """To pass context, that the patch in __getitem__ of api.Environment uses""" + patched_self = self.with_context(**{"missing_model": True}) + return IrModelData._module_data_uninstall._original_method( + patched_self, modules_to_remove + ) + + +_module_data_uninstall._original_method = IrModelData._module_data_uninstall +IrModelData._module_data_uninstall = _module_data_uninstall + + @api.model def _process_end(self, modules): """Don't warn about upgrade conventions from Odoo - ('fields should be explicitely removed by an upgrade script') + ('fields should be explicitly removed by an upgrade script') """ with mute_logger("odoo.addons.base.models.ir_model"): return IrModelData._process_end._original_method(self, modules) diff --git a/openupgrade_framework/odoo_patch/odoo/api.py b/openupgrade_framework/odoo_patch/odoo/api.py new file mode 100644 index 000000000000..34a8d78de328 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/api.py @@ -0,0 +1,43 @@ +# Copyright Odoo Community Association (OCA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from odoo.api import Environment + +_logger = logging.getLogger(__name__) + + +class FakeRecord: + """Artificial construct to handle delete(records) submethod """ + + def __new__(cls): + return object.__new__(cls) + + def __init__(self): + self._name = "ir.model.data" + self.ids = [] + self.browse = lambda l: None + + def __isub__(self, other): + return None + + +def __getitem__(self, model_name): + """This is used to bypass the call self.env[model] + (and other posterior calls) from _module_data_uninstall method of ir.model.data + """ + if ( + hasattr(self, "context") + and isinstance(model_name, str) + and self.context.get("missing_model", False) + ): + if not self.registry.models.get(model_name, False): + new_env = lambda: None # noqa: E731 + new_env._fields = {} + new_env.browse = lambda i: FakeRecord() + return new_env + return Environment.__getitem__._original_method(self, model_name) + + +__getitem__._original_method = Environment.__getitem__ +Environment.__getitem__ = __getitem__ From 7d118ff2fcc09aa3007da848a70893d34a1837e4 Mon Sep 17 00:00:00 2001 From: mreficent Date: Tue, 13 Apr 2021 01:46:38 +0200 Subject: [PATCH 39/92] [FIX] openupgrade_framework: _drop_column was in wrong class --- .../odoo_patch/odoo/addons/base/models/ir_model.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py index 6ce0014af9cc..b3eff8124f4d 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py @@ -5,7 +5,12 @@ from odoo import api, models from odoo.tools import mute_logger -from odoo.addons.base.models.ir_model import IrModel, IrModelData, IrModelRelation +from odoo.addons.base.models.ir_model import ( + IrModel, + IrModelData, + IrModelFields, + IrModelRelation, +) def _drop_table(self): @@ -22,6 +27,9 @@ def _drop_table(self): ) +IrModel._drop_table = _drop_table + + def _drop_column(self): """ Never drop columns """ for field in self: @@ -39,8 +47,7 @@ def _drop_column(self): continue -IrModel._drop_column = _drop_column -IrModel._drop_table = _drop_table +IrModelFields._drop_column = _drop_column @api.model From e2787d8211add21a89984390c69b4e6380bb832f Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Thu, 10 Jun 2021 09:43:07 +0200 Subject: [PATCH 40/92] [DOC] add banner file, to make this module more visible on the app store ; add links to the new OpenUpgrade website --- openupgrade_framework/README.rst | 6 +++--- openupgrade_framework/__manifest__.py | 1 + openupgrade_framework/readme/DESCRIPTION.rst | 5 +++++ openupgrade_framework/readme/DEVELOP.rst | 6 +++--- .../static/description/banner.png | Bin 0 -> 26859 bytes .../static/description/index.html | 17 +++++------------ 6 files changed, 17 insertions(+), 18 deletions(-) create mode 100644 openupgrade_framework/static/description/banner.png diff --git a/openupgrade_framework/README.rst b/openupgrade_framework/README.rst index 12e60cd6510b..2fca2f07534d 100644 --- a/openupgrade_framework/README.rst +++ b/openupgrade_framework/README.rst @@ -104,7 +104,7 @@ To make more easy the diff analysis : * When you want to change the code. add the following tags: - * For an addition: +For an addition: .. code-block:: python @@ -112,7 +112,7 @@ To make more easy the diff analysis : some code... # - * For a change: +For a change: .. code-block:: python @@ -120,7 +120,7 @@ To make more easy the diff analysis : some code... # - * For a removal: +For a removal: .. code-block:: python diff --git a/openupgrade_framework/__manifest__.py b/openupgrade_framework/__manifest__.py index cc76dfcbbf8e..5b895570a64f 100644 --- a/openupgrade_framework/__manifest__.py +++ b/openupgrade_framework/__manifest__.py @@ -10,5 +10,6 @@ "version": "14.0.1.0.0", "license": "AGPL-3", "depends": ["base"], + "images": ["static/description/banner.jpg"], "installable": True, } diff --git a/openupgrade_framework/readme/DESCRIPTION.rst b/openupgrade_framework/readme/DESCRIPTION.rst index dc04151fe07f..677579a463b7 100644 --- a/openupgrade_framework/readme/DESCRIPTION.rst +++ b/openupgrade_framework/readme/DESCRIPTION.rst @@ -17,3 +17,8 @@ OpenUpgrade migration scripts: * Run migration scripts for modules that are installed as new dependencies of upgraded modules (when there are such scripts for those particular modules) + +For detailed documentation see: + +* https://github.com/OCA/OpenUpgrade/ +* https://oca.github.io/OpenUpgrade diff --git a/openupgrade_framework/readme/DEVELOP.rst b/openupgrade_framework/readme/DEVELOP.rst index e4f32305c623..c65d2cae05d7 100644 --- a/openupgrade_framework/readme/DEVELOP.rst +++ b/openupgrade_framework/readme/DEVELOP.rst @@ -27,7 +27,7 @@ To make more easy the diff analysis : * When you want to change the code. add the following tags: - * For an addition: +For an addition: .. code-block:: python @@ -35,7 +35,7 @@ To make more easy the diff analysis : some code... # - * For a change: +For a change: .. code-block:: python @@ -43,7 +43,7 @@ To make more easy the diff analysis : some code... # - * For a removal: +For a removal: .. code-block:: python diff --git a/openupgrade_framework/static/description/banner.png b/openupgrade_framework/static/description/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..9dd67ec04cf40822d9d976b548273f4472b83cea GIT binary patch literal 26859 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 - + Openupgrade Framework -
      -

      Openupgrade Framework

      +
      + + +Odoo Community Association + +
      +

      Openupgrade Framework

      -

      Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

      +

      Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

      This module is a technical module that contains a number of monkeypatches to improve the behaviour of Odoo when migrating your database using the OpenUpgrade migration scripts:

      @@ -417,12 +422,12 @@

      Openupgrade Framework

    -

    Installation

    +

    Installation

    This module does not need to be installed on a database. It simply needs to be available via your addons-path.

    -

    Configuration

    +

    Configuration

    • call your odoo instance with the option --load=base,web,openupgrade_framework
    • @@ -441,7 +446,7 @@

      Configuration

      of the OpenUpgrade migration scripts.

    -

    Development

    +

    Development

    The odoo_patch folder contains python files in a tree that mimicks the folder tree of the Odoo project. It contains a number of monkey patches to improve the migration of an Odoo database between two major versions.

    @@ -485,7 +490,7 @@

    Development

    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. 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 @@ -493,9 +498,9 @@

    Bug Tracker

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • Therp BV
    • Opener B.V.
    • @@ -504,21 +509,21 @@

      Authors

    -

    Other credits

    +

    Other credits

    Many developers have contributed to the OpenUpgrade framework in its previous incarnation. Their original contributions may no longer needed, or they are no longer recognizable in their current form but OpenUpgrade would not have existed at this point without them.

    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association @@ -533,5 +538,6 @@

    Maintainers

    +
    From 42108635b362be97bb899852546a065f1352994d Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Fri, 4 Jul 2025 16:43:23 +0200 Subject: [PATCH 81/92] Revert "[FIX] openupgrade_framework: manipulate versions to actually run scripts" This reverts commit 576daf1ff55c3010c6f400f7a5b3da5f5151c798. --- openupgrade_framework/odoo_patch/odoo/modules/migration.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openupgrade_framework/odoo_patch/odoo/modules/migration.py b/openupgrade_framework/odoo_patch/odoo/modules/migration.py index a7cda862d7df..66eb1379656f 100644 --- a/openupgrade_framework/odoo_patch/odoo/modules/migration.py +++ b/openupgrade_framework/odoo_patch/odoo/modules/migration.py @@ -15,11 +15,9 @@ def migrate_module(self, pkg, stage): to_install = pkg.state == "to install" if to_install: pkg.state = "to upgrade" - pkg.installed_version = "0.0" MigrationManager.migrate_module._original_method(self, pkg, stage) if to_install: pkg.state = "to install" - pkg.installed_version = None def _get_files(self): From 5c1a59049beb2ff7919d0376e92757131251538f Mon Sep 17 00:00:00 2001 From: Fai Date: Fri, 11 Jul 2025 09:18:53 -0400 Subject: [PATCH 82/92] [FIX] openupgrade_framework: fix return value for update_list method patch --- .../odoo_patch/odoo/addons/base/models/ir_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py index 3748fd3cbb55..5da43444ac50 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py @@ -11,7 +11,7 @@ def update_list(self): installed. Ignore localization modules that are set to auto_install """ - Module.update_list._original_method(self) + result = Module.update_list._original_method(self) new_auto_install_modules = self.browse([]) for module in self.env["ir.module.module"].search( [ @@ -27,6 +27,7 @@ def update_list(self): new_auto_install_modules |= module if new_auto_install_modules: new_auto_install_modules.button_install() + return result update_list._original_method = Module.update_list From b5e8b9ab197691bc0963fec9d0d4da40ccf6d918 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sat, 12 Jul 2025 10:56:46 +0000 Subject: [PATCH 83/92] [BOT] post-merge updates --- openupgrade_framework/README.rst | 2 +- openupgrade_framework/__manifest__.py | 2 +- openupgrade_framework/static/description/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openupgrade_framework/README.rst b/openupgrade_framework/README.rst index c10257233ae2..9885a1cdb5eb 100644 --- a/openupgrade_framework/README.rst +++ b/openupgrade_framework/README.rst @@ -11,7 +11,7 @@ Openupgrade Framework !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:a2caccd2a3fd5dcf987e796125ffddc5538268ea28c1848574a4f6eb6f42632b + !! source digest: sha256:769617c0fb9bfda518087d0415b7adaa611ac8b2e9b8216544093d7258467518 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/openupgrade_framework/__manifest__.py b/openupgrade_framework/__manifest__.py index 5b39a4fc8689..75bdc5b2dc69 100644 --- a/openupgrade_framework/__manifest__.py +++ b/openupgrade_framework/__manifest__.py @@ -9,7 +9,7 @@ "maintainers": ["legalsylvain", "StefanRijnhart", "hbrunn"], "website": "https://github.com/OCA/OpenUpgrade", "category": "Migration", - "version": "18.0.1.0.1", + "version": "18.0.1.0.2", "license": "AGPL-3", "depends": ["base"], "images": ["static/description/banner.jpg"], diff --git a/openupgrade_framework/static/description/index.html b/openupgrade_framework/static/description/index.html index e35eb3ee04cd..b4a32bc71c66 100644 --- a/openupgrade_framework/static/description/index.html +++ b/openupgrade_framework/static/description/index.html @@ -372,7 +372,7 @@

    Openupgrade Framework

    !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:a2caccd2a3fd5dcf987e796125ffddc5538268ea28c1848574a4f6eb6f42632b +!! source digest: sha256:769617c0fb9bfda518087d0415b7adaa611ac8b2e9b8216544093d7258467518 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

    Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

    This module is a technical module that contains a number of From 09d172d379c1b34b8ac0d0d25d21c7ce7e03a8be Mon Sep 17 00:00:00 2001 From: antonio Date: Sat, 2 Aug 2025 15:24:34 +0200 Subject: [PATCH 84/92] [FIX] openupgrade_framework: _process_ondelete skip fields without attribute ondelete --- .../odoo_patch/odoo/addons/base/models/ir_model.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py index 1185d8c0b027..98ebd846d5bb 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py @@ -74,12 +74,18 @@ def _module_data_uninstall(self): def _process_ondelete(self): - """Don't break on missing models when deleting their selection fields""" + """Don't break on missing models or wrong field types + when deleting their selection fields""" to_process = self.browse([]) for selection in self: try: - self.env[selection.field_id.model] # pylint: disable=pointless-statement - to_process += selection + model_name = selection.field_id.model + field = selection.field_id + # Validate that the model exists + # and that the field has an ondelete attribute + self.env[model_name] # pylint: disable=pointless-statement + if hasattr(field, "ondelete"): + to_process += selection except KeyError: continue return IrModelSelection._process_ondelete._original_method(to_process) From ed977dc17cf2519a3ee8a9f49599d607e7b322f7 Mon Sep 17 00:00:00 2001 From: Quoc - Pham Ngoc Date: Sat, 23 Aug 2025 13:55:53 +0700 Subject: [PATCH 85/92] [FIX] openupgrade_framework: Add decorator for method update_list() after overriding --- .../odoo_patch/odoo/addons/base/models/ir_module.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py index 5da43444ac50..6f8d976bd8c6 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py @@ -1,10 +1,12 @@ # Copyright Odoo Community Association (OCA) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api from odoo.addons.base.models.ir_module import Module +@api.model def update_list(self): """ Mark auto_install modules as to install if all their dependencies are some kind of From b5041ca7a43a3b662452af2662f869b917d403e3 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sat, 23 Aug 2025 08:42:23 +0000 Subject: [PATCH 86/92] [BOT] post-merge updates --- openupgrade_framework/README.rst | 2 +- openupgrade_framework/__manifest__.py | 2 +- openupgrade_framework/static/description/index.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openupgrade_framework/README.rst b/openupgrade_framework/README.rst index 9885a1cdb5eb..2ef91845db3c 100644 --- a/openupgrade_framework/README.rst +++ b/openupgrade_framework/README.rst @@ -11,7 +11,7 @@ Openupgrade Framework !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:769617c0fb9bfda518087d0415b7adaa611ac8b2e9b8216544093d7258467518 + !! source digest: sha256:cadb60113e07a02008a69c0d3e3fe2e0a8ca46545ad75dfe579711c42cb367b3 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/openupgrade_framework/__manifest__.py b/openupgrade_framework/__manifest__.py index 75bdc5b2dc69..15ccff4543e4 100644 --- a/openupgrade_framework/__manifest__.py +++ b/openupgrade_framework/__manifest__.py @@ -9,7 +9,7 @@ "maintainers": ["legalsylvain", "StefanRijnhart", "hbrunn"], "website": "https://github.com/OCA/OpenUpgrade", "category": "Migration", - "version": "18.0.1.0.2", + "version": "18.0.1.0.3", "license": "AGPL-3", "depends": ["base"], "images": ["static/description/banner.jpg"], diff --git a/openupgrade_framework/static/description/index.html b/openupgrade_framework/static/description/index.html index b4a32bc71c66..102a82e9b563 100644 --- a/openupgrade_framework/static/description/index.html +++ b/openupgrade_framework/static/description/index.html @@ -372,7 +372,7 @@

    Openupgrade Framework

    !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:769617c0fb9bfda518087d0415b7adaa611ac8b2e9b8216544093d7258467518 +!! source digest: sha256:cadb60113e07a02008a69c0d3e3fe2e0a8ca46545ad75dfe579711c42cb367b3 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

    Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

    This module is a technical module that contains a number of From 395c02b0d249b9ad26c1bf6ac40aa80e5273b532 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 26 Sep 2025 17:46:08 +0200 Subject: [PATCH 87/92] [MIG] openupgrade_framework to v19 --- openupgrade_framework/README.rst | 76 +++++++++---------- openupgrade_framework/__manifest__.py | 2 +- .../odoo_patch/odoo/__init__.py | 2 +- .../odoo/addons/base/models/ir_model.py | 39 +--------- .../odoo/addons/base/models/ir_module.py | 22 +++++- .../odoo/addons/base/models/ir_ui_view.py | 37 +++------ openupgrade_framework/odoo_patch/odoo/api.py | 43 ----------- .../odoo_patch/odoo/modules/__init__.py | 2 +- .../odoo_patch/odoo/modules/graph.py | 60 --------------- .../odoo_patch/odoo/modules/loading.py | 31 ++------ .../odoo_patch/odoo/modules/migration.py | 19 ----- .../odoo_patch/odoo/modules/module_graph.py | 35 +++++++++ .../odoo_patch/odoo/orm/__init__.py | 1 + .../odoo_patch/odoo/{ => orm}/models.py | 4 +- .../static/description/index.html | 14 ++-- 15 files changed, 123 insertions(+), 264 deletions(-) delete mode 100644 openupgrade_framework/odoo_patch/odoo/api.py delete mode 100644 openupgrade_framework/odoo_patch/odoo/modules/graph.py create mode 100644 openupgrade_framework/odoo_patch/odoo/modules/module_graph.py create mode 100644 openupgrade_framework/odoo_patch/odoo/orm/__init__.py rename openupgrade_framework/odoo_patch/odoo/{ => orm}/models.py (92%) diff --git a/openupgrade_framework/README.rst b/openupgrade_framework/README.rst index 2ef91845db3c..17277fb6cec1 100644 --- a/openupgrade_framework/README.rst +++ b/openupgrade_framework/README.rst @@ -21,13 +21,13 @@ Openupgrade Framework :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2FOpenUpgrade-lightgray.png?logo=github - :target: https://github.com/OCA/OpenUpgrade/tree/18.0/openupgrade_framework + :target: https://github.com/OCA/OpenUpgrade/tree/19.0/openupgrade_framework :alt: OCA/OpenUpgrade .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/OpenUpgrade-18-0/OpenUpgrade-18-0-openupgrade_framework + :target: https://translation.odoo-community.org/projects/OpenUpgrade-19-0/OpenUpgrade-19-0-openupgrade_framework :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/OpenUpgrade&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/OpenUpgrade&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -36,31 +36,31 @@ This module is a technical module that contains a number of monkeypatches to improve the behaviour of Odoo when migrating your database using the OpenUpgrade migration scripts: -- Prevent dropping columns or tables in the database when fields or - models are obsoleted in the Odoo data model of the target release. - After the migration, you can review and delete unused database tables - and columns using database_cleanup. See - https://odoo-community.org/shop/product/database-cleanup-918 -- When data records are deleted during the migration (such as views or - other system records), this is done in a secure mode. If the deletion - fails because of some unforeseen dependency, the deletion will be - cancelled and a message is logged, after which the migration - continues. -- Prevent a number of log messages that do not apply when using - OpenUpgrade. -- Suppress log messages about failed view validation, which are to be - expected during a migration. -- Run migration scripts for modules that are installed as new - dependencies of upgraded modules (when there are such scripts for - those particular modules) -- Production databases generated with demo data, will be transformed to - non-demo ones. If you want to avoid that, you have to pass through the - environment variable OPENUPGRADE_USE_DEMO, the value "yes". +- Prevent dropping columns or tables in the database when fields or + models are obsoleted in the Odoo data model of the target release. + After the migration, you can review and delete unused database tables + and columns using database_cleanup. See + https://odoo-community.org/shop/product/database-cleanup-918 +- When data records are deleted during the migration (such as views or + other system records), this is done in a secure mode. If the deletion + fails because of some unforeseen dependency, the deletion will be + cancelled and a message is logged, after which the migration + continues. +- Prevent a number of log messages that do not apply when using + OpenUpgrade. +- Suppress log messages about failed view validation, which are to be + expected during a migration. +- Run migration scripts for modules that are installed as new + dependencies of upgraded modules (when there are such scripts for + those particular modules) +- Production databases generated with demo data, will be transformed to + non-demo ones. If you want to avoid that, you have to pass through + the environment variable OPENUPGRADE_USE_DEMO, the value "yes". For detailed documentation see: -- https://github.com/OCA/OpenUpgrade/ -- https://oca.github.io/OpenUpgrade +- https://github.com/OCA/OpenUpgrade/ +- https://oca.github.io/OpenUpgrade **Table of contents** @@ -76,12 +76,12 @@ to be available via your ``addons-path``. Configuration ============= -- call your odoo instance with the option - ``--load=base,web,openupgrade_framework`` +- call your odoo instance with the option + ``--load=base,web,openupgrade_framework`` or -- add the key to your configuration file: +- add the key to your configuration file: .. code:: shell @@ -110,18 +110,18 @@ To see the patches added, you can use ``meld`` tools: To make more easy the diff analysis : -- Make sure the python files has the same path as the original one. -- Keep the same indentation as the original file. (using ``if True:`` if - required) -- Add the following two lines at the beginning of your file, to avoid - flake8 / pylint errors +- Make sure the python files has the same path as the original one. +- Keep the same indentation as the original file. (using ``if True:`` + if required) +- Add the following two lines at the beginning of your file, to avoid + flake8 / pylint errors .. code:: python # flake8: noqa # pylint: skip-file -- When you want to change the code. add the following tags: +- When you want to change the code. add the following tags: For an addition: @@ -153,7 +153,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. 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 -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -171,8 +171,8 @@ Authors Contributors ------------ -- Stefan Rijnhart -- Sylvain LE GAL +- Stefan Rijnhart +- Sylvain LE GAL Other credits ------------- @@ -209,6 +209,6 @@ Current `maintainers `__: |maintainer-legalsylvain| |maintainer-StefanRijnhart| |maintainer-hbrunn| -This module is part of the `OCA/OpenUpgrade `_ project on GitHub. +This module is part of the `OCA/OpenUpgrade `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/openupgrade_framework/__manifest__.py b/openupgrade_framework/__manifest__.py index 15ccff4543e4..491fb07402f6 100644 --- a/openupgrade_framework/__manifest__.py +++ b/openupgrade_framework/__manifest__.py @@ -9,7 +9,7 @@ "maintainers": ["legalsylvain", "StefanRijnhart", "hbrunn"], "website": "https://github.com/OCA/OpenUpgrade", "category": "Migration", - "version": "18.0.1.0.3", + "version": "19.0.1.0.0", "license": "AGPL-3", "depends": ["base"], "images": ["static/description/banner.jpg"], diff --git a/openupgrade_framework/odoo_patch/odoo/__init__.py b/openupgrade_framework/odoo_patch/odoo/__init__.py index 64a54f17e628..18ff659459d2 100644 --- a/openupgrade_framework/odoo_patch/odoo/__init__.py +++ b/openupgrade_framework/odoo_patch/odoo/__init__.py @@ -1 +1 @@ -from . import addons, api, models, modules +from . import addons, modules, orm diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py index 98ebd846d5bb..11c1864b2540 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_model.py @@ -2,14 +2,12 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from openupgradelib import openupgrade -from odoo import api, models +from odoo import models from odoo.addons.base.models.ir_model import ( IrModel, - IrModelData, IrModelFields, IrModelRelation, - IrModelSelection, ) @@ -50,19 +48,6 @@ def _drop_column(self): IrModelFields._drop_column = _drop_column -@api.model -def _module_data_uninstall(self, modules_to_remove): - """To pass context, that the patch in __getitem__ of api.Environment uses""" - patched_self = self.with_context(**{"missing_model": True}) - return IrModelData._module_data_uninstall._original_method( - patched_self, modules_to_remove - ) - - -_module_data_uninstall._original_method = IrModelData._module_data_uninstall -IrModelData._module_data_uninstall = _module_data_uninstall - - def _module_data_uninstall(self): """Don't delete many2many relation tables. Only unlink the ir.model.relation record itself. @@ -71,25 +56,3 @@ def _module_data_uninstall(self): IrModelRelation._module_data_uninstall = _module_data_uninstall - - -def _process_ondelete(self): - """Don't break on missing models or wrong field types - when deleting their selection fields""" - to_process = self.browse([]) - for selection in self: - try: - model_name = selection.field_id.model - field = selection.field_id - # Validate that the model exists - # and that the field has an ondelete attribute - self.env[model_name] # pylint: disable=pointless-statement - if hasattr(field, "ondelete"): - to_process += selection - except KeyError: - continue - return IrModelSelection._process_ondelete._original_method(to_process) - - -_process_ondelete._original_method = IrModelSelection._process_ondelete -IrModelSelection._process_ondelete = _process_ondelete diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py index 6f8d976bd8c6..b71e0f78534f 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_module.py @@ -3,7 +3,7 @@ from odoo import api -from odoo.addons.base.models.ir_module import Module +from odoo.addons.base.models.ir_module import IrModuleModule @api.model @@ -13,7 +13,7 @@ def update_list(self): installed. Ignore localization modules that are set to auto_install """ - result = Module.update_list._original_method(self) + result = IrModuleModule.update_list._original_method(self) new_auto_install_modules = self.browse([]) for module in self.env["ir.module.module"].search( [ @@ -32,5 +32,19 @@ def update_list(self): return result -update_list._original_method = Module.update_list -Module.update_list = update_list +def check_external_dependencies(self, module_name, newstate="to install"): + try: + IrModuleModule.check_external_dependencies._original_method( + self, module_name, newstate=newstate + ) + except AttributeError: # pylint: disable=except-pass + # this happens when a module is installed that doesn't exist in the new version + pass + + +update_list._original_method = IrModuleModule.update_list +IrModuleModule.update_list = update_list +check_external_dependencies._original_method = ( + IrModuleModule.check_external_dependencies +) +IrModuleModule.check_external_dependencies = check_external_dependencies diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py index 0cec05eab184..003da6054543 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py @@ -7,7 +7,7 @@ from odoo.exceptions import ValidationError from odoo.tools import mute_logger -from odoo.addons.base.models.ir_ui_view import NameManager, View +from odoo.addons.base.models.ir_ui_view import IrUiView _logger = logging.getLogger(__name__) @@ -18,7 +18,7 @@ def _check_xml(self): requested. Mute warnings about views which are common during migration.""" with mute_logger("odoo.addons.base.models.ir_ui_view"): try: - return View._check_xml._original_method(self) + return IrUiView._check_xml._original_method(self) except ValidationError as e: _logger.warning( "Can't render custom view %s for model %s. " @@ -31,21 +31,6 @@ def _check_xml(self): ) -def check(self, view): - """Because we captured the exception in _raise_view_error and archived that view, - so info is None, but it is called to info.get('select') in NameManager.check, - which will raise an exception AttributeError, - so we need to override to not raise an exception - """ - try: - return NameManager.check._original_method(self, view) - except AttributeError as e: - if e.args[0] == "'NoneType' object has no attribute 'get'": - pass - else: - raise - - def _raise_view_error( self, message, node=None, *, from_exception=None, from_traceback=None ): @@ -56,7 +41,7 @@ def _raise_view_error( to_mute = "odoo.addons.base.models.ir_ui_view" if raise_exception else "not_muted" with mute_logger(to_mute): try: - return View._raise_view_error._original_method( + return IrUiView._raise_view_error._original_method( self, message, node=node, @@ -78,18 +63,16 @@ def _raise_view_error( def _check_field_paths(self, node, field_paths, model_name, use): """Ignore UnboundLocalError when we squelched the raise about missing fields""" try: - return View._check_field_paths._original_method( + return IrUiView._check_field_paths._original_method( self, node, field_paths, model_name, use ) except UnboundLocalError: # pylint: disable=except-pass pass -_check_xml._original_method = View._check_xml -View._check_xml = _check_xml -check._original_method = NameManager.check -NameManager.check = check -_raise_view_error._original_method = View._raise_view_error -View._raise_view_error = _raise_view_error -_check_field_paths._original_method = View._check_field_paths -View._check_field_paths = _check_field_paths +_check_xml._original_method = IrUiView._check_xml +IrUiView._check_xml = _check_xml +_raise_view_error._original_method = IrUiView._raise_view_error +IrUiView._raise_view_error = _raise_view_error +_check_field_paths._original_method = IrUiView._check_field_paths +IrUiView._check_field_paths = _check_field_paths diff --git a/openupgrade_framework/odoo_patch/odoo/api.py b/openupgrade_framework/odoo_patch/odoo/api.py deleted file mode 100644 index 1e4fbac6225f..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/api.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright Odoo Community Association (OCA) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import logging - -from odoo.api import Environment - -_logger = logging.getLogger(__name__) - - -class FakeRecord: - """Artificial construct to handle delete(records) submethod""" - - def __new__(cls): - return object.__new__(cls) - - def __init__(self): - self._name = "ir.model.data" - self.ids = [] - self.browse = lambda x: None - - def __isub__(self, other): - return None - - -def __getitem__(self, model_name): - """This is used to bypass the call self.env[model] - (and other posterior calls) from _module_data_uninstall method of ir.model.data - """ - if ( - hasattr(self, "context") - and isinstance(model_name, str) - and self.context.get("missing_model", False) - ): - if not self.registry.models.get(model_name, False): - new_env = lambda: None # noqa: E731 - new_env._fields = {} - new_env.browse = lambda i: FakeRecord() - return new_env - return Environment.__getitem__._original_method(self, model_name) - - -__getitem__._original_method = Environment.__getitem__ -Environment.__getitem__ = __getitem__ diff --git a/openupgrade_framework/odoo_patch/odoo/modules/__init__.py b/openupgrade_framework/odoo_patch/odoo/modules/__init__.py index 6fa868885f86..e060fc2ed96b 100644 --- a/openupgrade_framework/odoo_patch/odoo/modules/__init__.py +++ b/openupgrade_framework/odoo_patch/odoo/modules/__init__.py @@ -1 +1 @@ -from . import graph, loading, migration +from . import module_graph, loading, migration diff --git a/openupgrade_framework/odoo_patch/odoo/modules/graph.py b/openupgrade_framework/odoo_patch/odoo/modules/graph.py deleted file mode 100644 index 4bf3f36d9a2c..000000000000 --- a/openupgrade_framework/odoo_patch/odoo/modules/graph.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright Odoo Community Association (OCA) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import os - -import odoo -from odoo.modules.graph import Graph - - -def update_from_db(self, cr): - """Prevent reloading of demo data from the new version on major upgrade""" - Graph.update_from_db._original_method(self, cr) - if os.environ.get("OPENUPGRADE_USE_DEMO", "") == "yes": - return - if ( - "base" in self - and self["base"].dbdemo - and self["base"].installed_version < odoo.release.major_version - ): - cr.execute("UPDATE ir_module_module SET demo = false") - for package in self.values(): - package.dbdemo = False - - -def add_modules(self, cr, module_list, force=None): - """Add extra dependencies directly to the graph for immediate installation and - proper dependency resolution. - """ - modules_in = list(module_list) - dependencies = module_list - while dependencies: - cr.execute( - """ - SELECT DISTINCT dep.name - FROM - ir_module_module, - ir_module_module_dependency dep - WHERE - module_id = ir_module_module.id - AND ir_module_module.name in %s - AND dep.name not in %s - """, - (tuple(dependencies), tuple(module_list)), - ) - dependencies = [x[0] for x in cr.fetchall()] - module_list += dependencies - # Set proper state for new dependencies so that any init scripts are run - cr.execute( - """ - UPDATE ir_module_module SET state = 'to install' - WHERE name IN %s AND name NOT IN %s AND state = 'uninstalled' - """, - (tuple(module_list), tuple(modules_in)), - ) - return Graph.add_modules._original_method(self, cr, module_list, force=force) - - -update_from_db._original_method = Graph.update_from_db -Graph.update_from_db = update_from_db -add_modules._original_method = Graph.add_modules -Graph.add_modules = add_modules diff --git a/openupgrade_framework/odoo_patch/odoo/modules/loading.py b/openupgrade_framework/odoo_patch/odoo/modules/loading.py index 1c570c1d9b8e..4715de5ea370 100644 --- a/openupgrade_framework/odoo_patch/odoo/modules/loading.py +++ b/openupgrade_framework/odoo_patch/odoo/modules/loading.py @@ -1,33 +1,18 @@ # Copyright Odoo Community Association (OCA) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import inspect import odoo.modules.loading -def load_openerp_module(module_name): +def load_module_graph(env, graph, *args, **kwargs): """ - Run pre-migration scripts of addons being installed + Force run pre-migration scripts for modules being installed """ - frame = inspect.currentframe() - while frame.f_back: - frame = frame.f_back - f_locals = frame.f_locals + env.registry._force_upgrade_scripts.update(set(package.name for package in graph)) + return odoo.modules.loading.load_module_graph._original_method( + env, graph, *args, **kwargs + ) - expected_locals = ( - "new_install", - "needs_update", - "package", - "migrations", - "env", - ) - if all(expected_local in f_locals for expected_local in expected_locals): - if f_locals["needs_update"] and f_locals["new_install"]: - f_locals["migrations"].migrate_module(f_locals["package"], "pre") - f_locals["env"].flush_all() - odoo.modules.loading.load_openerp_module._original_method(module_name) - - -load_openerp_module._original_method = odoo.modules.loading.load_openerp_module -odoo.modules.loading.load_openerp_module = load_openerp_module +load_module_graph._original_method = odoo.modules.loading.load_module_graph +odoo.modules.loading.load_module_graph = load_module_graph diff --git a/openupgrade_framework/odoo_patch/odoo/modules/migration.py b/openupgrade_framework/odoo_patch/odoo/modules/migration.py index 66eb1379656f..fdc6f09eda1b 100644 --- a/openupgrade_framework/odoo_patch/odoo/modules/migration.py +++ b/openupgrade_framework/odoo_patch/odoo/modules/migration.py @@ -3,23 +3,6 @@ from odoo.modules.migration import MigrationManager -def migrate_module(self, pkg, stage): - """In openupgrade, also run migration scripts upon installation. - We want to always pass in pre and post migration files and use a new - argument in the migrate decorator (explained in the docstring) - to decide if we want to do something if a new module is installed - during the migration. - We trick Odoo into running the scripts by temporarily changing the module - state. - """ - to_install = pkg.state == "to install" - if to_install: - pkg.state = "to upgrade" - MigrationManager.migrate_module._original_method(self, pkg, stage) - if to_install: - pkg.state = "to install" - - def _get_files(self): """Turns out Odoo SA sometimes add migration scripts that interfere with OpenUpgrade. Those we filter out here""" @@ -29,7 +12,5 @@ def _get_files(self): self.migrations.get(addon, {}).get("module", {}).pop(version, None) -migrate_module._original_method = MigrationManager.migrate_module -MigrationManager.migrate_module = migrate_module _get_files._original_method = MigrationManager._get_files MigrationManager._get_files = _get_files diff --git a/openupgrade_framework/odoo_patch/odoo/modules/module_graph.py b/openupgrade_framework/odoo_patch/odoo/modules/module_graph.py new file mode 100644 index 000000000000..dbe2d2a73f5f --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/modules/module_graph.py @@ -0,0 +1,35 @@ +# Copyright Odoo Community Association (OCA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import os + +import odoo +from odoo.modules.module_graph import ModuleGraph + + +def _update_from_database(self, *args, **kwargs) -> None: + """Prevent reloading of demo data from the new version on major upgrade""" + ModuleGraph._update_from_database._original_method(self, *args, **kwargs) + + # v19-specific: ir.model.fields#translate has changed semantics, untranslated fields + # need to be set to null instead of false. and as this is read before any upgrade + # scripts run, we do it here. the statement is a bit clunky because it has to work + # before and after the translate column is converted from boolean to varchar + self._cr.execute( + "UPDATE ir_model_fields SET translate=NULL where translate::varchar='false'" + ) + + if os.environ.get("OPENUPGRADE_USE_DEMO", "") == "yes": + return + + if ( + "base" in self._modules + and self._modules["base"].demo + and self._modules["base"].installed_version < odoo.release.major_version + ): + self._cr.execute("UPDATE ir_module_module SET demo = false") + for module in self._modules.values(): + module.demo = False + + +_update_from_database._original_method = ModuleGraph._update_from_database +ModuleGraph._update_from_database = _update_from_database diff --git a/openupgrade_framework/odoo_patch/odoo/orm/__init__.py b/openupgrade_framework/odoo_patch/odoo/orm/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/openupgrade_framework/odoo_patch/odoo/orm/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/openupgrade_framework/odoo_patch/odoo/models.py b/openupgrade_framework/odoo_patch/odoo/orm/models.py similarity index 92% rename from openupgrade_framework/odoo_patch/odoo/models.py rename to openupgrade_framework/odoo_patch/odoo/orm/models.py index 12ec270e2f4c..641043d49b69 100644 --- a/openupgrade_framework/odoo_patch/odoo/models.py +++ b/openupgrade_framework/odoo_patch/odoo/orm/models.py @@ -22,12 +22,12 @@ def unlink(self): savepoint = str(uuid4) try: self.env.cr.execute( # pylint: disable=sql-injection - 'SAVEPOINT "%s"' % savepoint + f'SAVEPOINT "{savepoint}"' ) return BaseModel.unlink._original_method(self) except Exception as e: self.env.cr.execute( # pylint: disable=sql-injection - 'ROLLBACK TO SAVEPOINT "%s"' % savepoint + f'ROLLBACK TO SAVEPOINT "{savepoint}"' ) _logger.warning( "Could not delete obsolete record with ids %s of model %s: %s", diff --git a/openupgrade_framework/static/description/index.html b/openupgrade_framework/static/description/index.html index 102a82e9b563..ff53fb70f442 100644 --- a/openupgrade_framework/static/description/index.html +++ b/openupgrade_framework/static/description/index.html @@ -374,7 +374,7 @@

    Openupgrade Framework

    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:cadb60113e07a02008a69c0d3e3fe2e0a8ca46545ad75dfe579711c42cb367b3 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

    Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

    +

    Beta License: AGPL-3 OCA/OpenUpgrade Translate me on Weblate Try me on Runboat

    This module is a technical module that contains a number of monkeypatches to improve the behaviour of Odoo when migrating your database using the OpenUpgrade migration scripts:

    @@ -397,8 +397,8 @@

    Openupgrade Framework

    dependencies of upgraded modules (when there are such scripts for those particular modules)
  • Production databases generated with demo data, will be transformed to -non-demo ones. If you want to avoid that, you have to pass through the -environment variable OPENUPGRADE_USE_DEMO, the value “yes”.
  • +non-demo ones. If you want to avoid that, you have to pass through +the environment variable OPENUPGRADE_USE_DEMO, the value “yes”.

    For detailed documentation see:

      @@ -458,8 +458,8 @@

      Development

      To make more easy the diff analysis :

      • Make sure the python files has the same path as the original one.
      • -
      • Keep the same indentation as the original file. (using if True: if -required)
      • +
      • Keep the same indentation as the original file. (using if True: +if required)
      • Add the following two lines at the beginning of your file, to avoid flake8 / pylint errors
      @@ -494,7 +494,7 @@

      Bug Tracker

      Bugs are tracked on GitHub Issues. 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 -feedback.

      +feedback.

      Do not contact contributors directly about support or help with technical issues.

      @@ -533,7 +533,7 @@

      Maintainers

      promote its widespread use.

      Current maintainers:

      legalsylvain StefanRijnhart hbrunn

      -

      This module is part of the OCA/OpenUpgrade project on GitHub.

      +

      This module is part of the OCA/OpenUpgrade project on GitHub.

      You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

      From be146f948c0a5a2cc09db17dc31832540272fa48 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Oct 2025 13:18:46 +0200 Subject: [PATCH 88/92] [IMP] add tests for openupgrade_framework --- openupgrade_framework/tests/__init__.py | 1 + .../tests/test_openupgrade_framework.py | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 openupgrade_framework/tests/__init__.py create mode 100644 openupgrade_framework/tests/test_openupgrade_framework.py diff --git a/openupgrade_framework/tests/__init__.py b/openupgrade_framework/tests/__init__.py new file mode 100644 index 000000000000..f0425e721034 --- /dev/null +++ b/openupgrade_framework/tests/__init__.py @@ -0,0 +1 @@ +from . import test_openupgrade_framework diff --git a/openupgrade_framework/tests/test_openupgrade_framework.py b/openupgrade_framework/tests/test_openupgrade_framework.py new file mode 100644 index 000000000000..4a763142f32f --- /dev/null +++ b/openupgrade_framework/tests/test_openupgrade_framework.py @@ -0,0 +1,35 @@ +from odoo.tests.common import TransactionCase +from odoo.tools.misc import mute_logger + +from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG + + +class TestOpenupgradeFramework(TransactionCase): + def test_01_delete_undeletable_record(self): + """ + Test that Odoo doesn't crash when deleting records that can't be deleted + during module upgrade + """ + with ( + mute_logger("odoo.sql_db"), + self.assertLogs( + "odoo.addons.openupgrade_framework.odoo_patch.odoo.orm.models" + ), + ): + self.env.ref("base.partner_admin").with_context( + **{MODULE_UNINSTALL_FLAG: True} + ).unlink() + + def test_02_invalid_view(self): + """ + Test that we patch away fatal view errors and log the problem + """ + with self.assertLogs( + "odoo.addons.openupgrade_framework.odoo_patch.odoo.addons.base.models.ir_ui_view" + ): + self.env["ir.ui.view"].create( + { + "model": "res.partner", + "arch": '
      ', + } + ) From 21d73b8640663687b78c25a15ca056cbd8cf1781 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 7 Nov 2025 07:41:05 +0100 Subject: [PATCH 89/92] [FIX] don't set install_filename when loading views from openupgrade_scripts --- .../odoo/addons/base/models/ir_ui_view.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py index 003da6054543..57bf22cae2ee 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py @@ -5,6 +5,7 @@ from odoo import api from odoo.exceptions import ValidationError +from odoo.modules.module import get_resource_from_path from odoo.tools import mute_logger from odoo.addons.base.models.ir_ui_view import IrUiView @@ -70,9 +71,26 @@ def _check_field_paths(self, node, field_paths, model_name, use): pass +def _inverse_arch(self): + """ + Remove install_filename from context if it's from openupgrade_scripts. + Without this, arch_fs will point to openupgrade_scripts' file which most likely + won't exist when the migrated database is deployed, which breaks resetting views + """ + if "install_filename" in self.env.context: + path_info = get_resource_from_path(self.env.context["install_filename"]) + if path_info[0] == "openupgrade_scripts": + self = self.with_context( # pylint: disable=context-overridden + {k: v for k, v in self.env.context.items() if k != "install_filename"} + ) + return _inverse_arch._original_method(self) + + _check_xml._original_method = IrUiView._check_xml IrUiView._check_xml = _check_xml _raise_view_error._original_method = IrUiView._raise_view_error IrUiView._raise_view_error = _raise_view_error _check_field_paths._original_method = IrUiView._check_field_paths IrUiView._check_field_paths = _check_field_paths +_inverse_arch._original_method = IrUiView._inverse_arch +IrUiView._inverse_arch = _inverse_arch From f109d371df4a39b8472390eebb48aac6db32337a Mon Sep 17 00:00:00 2001 From: javierjcf Date: Mon, 10 Nov 2025 12:19:30 +0100 Subject: [PATCH 90/92] [FIX]openupgrade_framework: Fix Nonetype Error when install_filenmae path does not exist --- .../odoo_patch/odoo/addons/base/models/ir_ui_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py index 57bf22cae2ee..5042778ee042 100644 --- a/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py +++ b/openupgrade_framework/odoo_patch/odoo/addons/base/models/ir_ui_view.py @@ -79,7 +79,7 @@ def _inverse_arch(self): """ if "install_filename" in self.env.context: path_info = get_resource_from_path(self.env.context["install_filename"]) - if path_info[0] == "openupgrade_scripts": + if path_info and path_info[0] == "openupgrade_scripts": self = self.with_context( # pylint: disable=context-overridden {k: v for k, v in self.env.context.items() if k != "install_filename"} ) From e6a54b8fb1ede63a5123061f6c0feeabd168518d Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Wed, 24 Dec 2025 16:39:17 +0100 Subject: [PATCH 91/92] [FIX] openupgrade_framework: set upgrade_path only if it exists --- openupgrade_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openupgrade_framework/__init__.py b/openupgrade_framework/__init__.py index 2aee639f0d3d..97a704fd9e63 100644 --- a/openupgrade_framework/__init__.py +++ b/openupgrade_framework/__init__.py @@ -8,7 +8,7 @@ if not config.get("upgrade_path"): path = get_module_path("openupgrade_scripts", display_warning=False) - if path: + if path and os.path.isdir(os.path.join(path, "scripts")): logging.getLogger(__name__).info( "Setting upgrade_path to the scripts directory inside the module " "location of openupgrade_scripts" From 9f45f79f85c0fbce4f2c09d710b288c221ae0307 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 25 Dec 2025 08:21:48 +0100 Subject: [PATCH 92/92] [IMP] CI: skip tests after test migration if there are no modules --- .github/workflows/test-migration.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test-migration.yml b/.github/workflows/test-migration.yml index a00244599594..9402b8527709 100644 --- a/.github/workflows/test-migration.yml +++ b/.github/workflows/test-migration.yml @@ -109,6 +109,7 @@ jobs: done fi - name: OpenUpgrade test + id: run_migration run: | # select modules and perform the upgrade MODULES_OLD=$(\ @@ -127,6 +128,8 @@ jobs: | sed -rn 's/((^\| *\|new\| *)|^\| *)([0-9a-z_]*)[ \|].*/\3/g p' \ | sed '/^\s*$/d' \ | paste -d, -s) + echo "modules_old=$MODULES_OLD" >> $GITHUB_OUTPUT + echo "modules_new=$MODULES_NEW" >> $GITHUB_OUTPUT if [ -z "$MODULES_NEW" ]; then echo "No modules to test yet" exit @@ -160,8 +163,10 @@ jobs: --without-demo=$MODULES_NEW \ --update=$MODULES_NEW - name: Generate coverage.xml + if: ${{ steps.run_migration.outputs.modules_new != '' }} run: coverage xml - uses: codecov/codecov-action@v4 + if: ${{ steps.run_migration.outputs.modules_new != '' }} with: files: coverage.xml token: ${{ secrets.CODECOV_TOKEN }}