Browse Source

[MIG] module_auto_update from 11 to 12

Use tagged to mark post install tests as previous mechanism
seems to be broken.
pull/1382/head
Stéphane Bidoul (ACSONE) 6 years ago
parent
commit
2c25f45ac9
No known key found for this signature in database GPG Key ID: BCAB2555446B5B92
  1. 1
      module_auto_update/__init__.py
  2. 5
      module_auto_update/__manifest__.py
  3. 20
      module_auto_update/data/cron_data_deprecated.xml
  4. 6
      module_auto_update/hooks.py
  5. 24
      module_auto_update/migrations/10.0.2.0.0/pre-migrate.py
  6. 1
      module_auto_update/models/__init__.py
  7. 69
      module_auto_update/models/module_deprecated.py
  8. 17
      module_auto_update/readme/ROADMAP.rst
  9. 2
      module_auto_update/tests/__init__.py
  10. 7
      module_auto_update/tests/test_module.py
  11. 209
      module_auto_update/tests/test_module_deprecated.py
  12. 46
      module_auto_update/tests/test_module_upgrade_deprecated.py
  13. 3
      module_auto_update/wizards/__init__.py
  14. 84
      module_auto_update/wizards/module_upgrade_deprecated.py
  15. 2
      setup/.setuptools-odoo-make-default-ignore
  16. 2
      setup/README
  17. 1
      setup/module_auto_update/odoo/addons/module_auto_update
  18. 6
      setup/module_auto_update/setup.py

1
module_auto_update/__init__.py

@ -1,5 +1,4 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import models from . import models
from . import wizards
from .hooks import uninstall_hook from .hooks import uninstall_hook

5
module_auto_update/__manifest__.py

@ -4,7 +4,7 @@
{ {
'name': 'Module Auto Update', 'name': 'Module Auto Update',
'summary': 'Automatically update Odoo modules', 'summary': 'Automatically update Odoo modules',
'version': '11.0.2.0.4',
'version': '12.0.2.0.4',
'category': 'Extra Tools', 'category': 'Extra Tools',
'website': 'https://github.com/OCA/server-tools', 'website': 'https://github.com/OCA/server-tools',
'author': 'LasLabs, ' 'author': 'LasLabs, '
@ -19,9 +19,6 @@
'depends': [ 'depends': [
'base', 'base',
], ],
'data': [
'data/cron_data_deprecated.xml',
],
'development_status': 'Production/Stable', 'development_status': 'Production/Stable',
'maintainers': ['sbidoul'], 'maintainers': ['sbidoul'],
} }

20
module_auto_update/data/cron_data_deprecated.xml

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 LasLabs - Dave Lasley
Copyright 2017 Tecnativa - Jairo Llopis
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). -->
<odoo noupdate="1">
<record model="ir.cron" id="module_check_upgrades_cron">
<field name="name">Perform Module Upgrades</field>
<field name="active" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="nextcall" eval="(DateTime.now() + timedelta(days= +1)).strftime('%Y-%m-%d 3:00:00')"/>
<field name="model_id" ref="base.model_base_module_upgrade"/>
<field name="state">code</field>
<field name="code">model.upgrade_module()</field>
</record>
</odoo>

6
module_auto_update/hooks.py

@ -4,14 +4,8 @@
from odoo import SUPERUSER_ID, api from odoo import SUPERUSER_ID, api
from .models.module import PARAM_INSTALLED_CHECKSUMS from .models.module import PARAM_INSTALLED_CHECKSUMS
from .models.module_deprecated import PARAM_DEPRECATED
def uninstall_hook(cr, registry): def uninstall_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {}) env = api.Environment(cr, SUPERUSER_ID, {})
env["ir.config_parameter"].set_param(PARAM_INSTALLED_CHECKSUMS, False) env["ir.config_parameter"].set_param(PARAM_INSTALLED_CHECKSUMS, False)
# TODO Remove from here when removing deprecated features
env["ir.config_parameter"].set_param(PARAM_DEPRECATED, False)
prefix = "module_auto_update.field_ir_module_module_checksum_%s"
fields = env.ref(prefix % "dir") | env.ref(prefix % "installed")
fields.with_context(_force_unlink=True).unlink()

24
module_auto_update/migrations/10.0.2.0.0/pre-migrate.py

@ -1,24 +0,0 @@
# Copyright 2018 Tecnativa - Jairo Llopis
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
import logging
from psycopg2 import IntegrityError
from odoo.addons.module_auto_update.models.module_deprecated import \
PARAM_DEPRECATED
_logger = logging.getLogger(__name__)
def migrate(cr, version):
"""Autoenable deprecated behavior."""
try:
with cr.savepoint():
cr.execute(
"""INSERT INTO ir_config_parameter (key, value)
VALUES (%s, '1')""",
(PARAM_DEPRECATED,)
)
_logger.warn("Deprecated features have been autoenabled, see "
"addon's README to know how to upgrade to the new "
"supported autoupdate mechanism.")
except IntegrityError:
_logger.info("Deprecated features setting exists, not autoenabling")

1
module_auto_update/models/__init__.py

@ -1,4 +1,3 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import module from . import module
from . import module_deprecated

69
module_auto_update/models/module_deprecated.py

@ -1,69 +0,0 @@
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
# pylint: disable=consider-merging-classes-inherited
from odoo import api, fields, models
PARAM_DEPRECATED = "module_auto_update.enable_deprecated"
class Module(models.Model):
_inherit = 'ir.module.module'
checksum_dir = fields.Char(
deprecated=True,
compute='_compute_checksum_dir',
)
checksum_installed = fields.Char(
deprecated=True,
compute='_compute_checksum_installed',
inverse='_inverse_checksum_installed',
store=False,
)
@api.depends('name')
def _compute_checksum_dir(self):
for rec in self:
rec.checksum_dir = rec._get_checksum_dir()
def _compute_checksum_installed(self):
saved_checksums = self._get_saved_checksums()
for rec in self:
rec.checksum_installed = saved_checksums.get(rec.name, False)
def _inverse_checksum_installed(self):
checksums = self._get_saved_checksums()
for rec in self:
checksums[rec.name] = rec.checksum_installed
self._save_checksums(checksums)
@api.multi
def _store_checksum_installed(self, vals):
"""Store the right installed checksum, if addon is installed."""
if not self.env["base.module.upgrade"]._autoupdate_deprecated():
# Skip if deprecated features are disabled
return
if 'checksum_installed' not in vals:
try:
version = vals["latest_version"]
except KeyError:
return # Not [un]installing/updating any addon
if version is False:
# Uninstalling
self.write({'checksum_installed': False})
else:
# Installing or updating
for one in self:
one.checksum_installed = one.checksum_dir
@api.model
def create(self, vals):
res = super(Module, self).create(vals)
res._store_checksum_installed(vals)
return res
@api.multi
def write(self, vals):
res = super(Module, self).write(vals)
self._store_checksum_installed(vals)
return res

17
module_auto_update/readme/ROADMAP.rst

@ -1,17 +0,0 @@
* Since version ``2.0.0``, some features have been deprecated.
When you upgrade from previous versions, these features will be kept for
backwards compatibility, but beware! They are buggy!
If you install this addon from scratch, these features are disabled by
default.
To force enabling or disabling the deprecated features, set a configuration
parameter called ``module_auto_update.enable_deprecated`` to either ``1``
or ``0``. It is recommended that you disable them.
Keep in mind that from this version, all upgrades are assumed to run in a
separate odoo instance, dedicated exclusively to upgrade Odoo.
* When migrating the addon to new versions, the deprecated features should be
removed. To make it simple all deprecated features are found in files
suffixed with ``_deprecated``.

2
module_auto_update/tests/__init__.py

@ -2,5 +2,3 @@
from . import test_addon_hash from . import test_addon_hash
from . import test_module from . import test_module
from . import test_module_deprecated
from . import test_module_upgrade_deprecated

7
module_auto_update/tests/test_module.py

@ -7,9 +7,9 @@ import tempfile
import mock import mock
import odoo
from odoo.modules import get_module_path from odoo.modules import get_module_path
from odoo.tests import common
from odoo.tests.common import TransactionCase
from odoo.tests import TransactionCase
from ..addon_hash import addon_hash from ..addon_hash import addon_hash
from ..models.module import IncompleteUpgradeError, DEFAULT_EXCLUDE_PATTERNS from ..models.module import IncompleteUpgradeError, DEFAULT_EXCLUDE_PATTERNS
@ -79,8 +79,7 @@ class TestModule(TransactionCase):
self.assertFalse(Imm._get_modules_with_changed_checksum()) self.assertFalse(Imm._get_modules_with_changed_checksum())
@common.at_install(False)
@common.post_install(True)
@odoo.tests.tagged('post_install', '-at_install')
class TestModuleAfterInstall(TransactionCase): class TestModuleAfterInstall(TransactionCase):
def setUp(self): def setUp(self):

209
module_auto_update/tests/test_module_deprecated.py

@ -1,209 +0,0 @@
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import os
import mock
from odoo.modules import get_module_path
from odoo.tests.common import TransactionCase
from odoo.tools import mute_logger
from .. addon_hash import addon_hash
from ..models.module_deprecated import PARAM_DEPRECATED
model = 'odoo.addons.module_auto_update.models.module'
class EndTestException(Exception):
pass
class TestModule(TransactionCase):
def setUp(self):
super(TestModule, self).setUp()
module_name = 'module_auto_update'
self.env["ir.config_parameter"].set_param(PARAM_DEPRECATED, "1")
self.own_module = self.env['ir.module.module'].search([
('name', '=', module_name),
])
self.own_dir_path = get_module_path(module_name)
keep_langs = self.env['res.lang'].search([]).mapped('code')
self.own_checksum = addon_hash(
self.own_dir_path,
exclude_patterns=['*.pyc', '*.pyo', '*.pot', 'static/*'],
keep_langs=keep_langs,
)
self.own_writeable = os.access(self.own_dir_path, os.W_OK)
@mock.patch('%s.get_module_path' % model)
def create_test_module(self, vals, get_module_path_mock):
get_module_path_mock.return_value = self.own_dir_path
test_module = self.env['ir.module.module'].create(vals)
return test_module
def test_store_checksum_installed_state_installed(self):
"""It should set the module's checksum_installed equal to
checksum_dir when vals contain a ``latest_version`` str."""
self.own_module.checksum_installed = 'test'
self.own_module._store_checksum_installed({'latest_version': '1.0'})
self.assertEqual(
self.own_module.checksum_installed, self.own_module.checksum_dir,
)
def test_store_checksum_installed_state_uninstalled(self):
"""It should clear the module's checksum_installed when vals
contain ``"latest_version": False``"""
self.own_module.checksum_installed = 'test'
self.own_module._store_checksum_installed({'latest_version': False})
self.assertIs(self.own_module.checksum_installed, False)
def test_store_checksum_installed_vals_contain_checksum_installed(self):
"""It should not set checksum_installed to False or checksum_dir when
a checksum_installed is included in vals"""
self.own_module.checksum_installed = 'test'
self.own_module._store_checksum_installed({
'state': 'installed',
'checksum_installed': 'test',
})
self.assertEqual(
self.own_module.checksum_installed, 'test',
'Providing checksum_installed in vals did not prevent overwrite',
)
def test_store_checksum_installed_with_retain_context(self):
"""It should not set checksum_installed to False or checksum_dir when
self has context retain_checksum_installed=True"""
self.own_module.checksum_installed = 'test'
self.own_module.with_context(
retain_checksum_installed=True,
)._store_checksum_installed({'state': 'installed'})
self.assertEqual(
self.own_module.checksum_installed, 'test',
'Providing retain_checksum_installed context did not prevent '
'overwrite',
)
@mock.patch('%s.get_module_path' % model)
def test_button_uninstall_no_recompute(self, module_path_mock):
"""It should not attempt update on `button_uninstall`."""
module_path_mock.return_value = self.own_dir_path
vals = {
'name': 'module_auto_update_test_module',
'state': 'installed',
}
test_module = self.create_test_module(vals)
test_module.checksum_installed = 'test'
uninstall_module = self.env['ir.module.module'].search([
('name', '=', 'web'),
])
uninstall_module.button_uninstall()
self.assertNotEqual(
test_module.state, 'to upgrade',
'Auto update logic was triggered during uninstall.',
)
def test_button_immediate_uninstall_no_recompute(self):
"""It should not attempt update on `button_immediate_uninstall`."""
uninstall_module = self.env['ir.module.module'].search([
('name', '=', 'web'),
])
try:
mk = mock.MagicMock()
uninstall_module._patch_method('button_uninstall', mk)
mk.side_effect = EndTestException
with self.assertRaises(EndTestException):
uninstall_module.button_immediate_uninstall()
finally:
uninstall_module._revert_method('button_uninstall')
def test_button_uninstall_cancel(self):
"""It should preserve checksum_installed when cancelling uninstall"""
self.own_module.write({'state': 'to remove'})
self.own_module.checksum_installed = 'test'
self.own_module.button_uninstall_cancel()
self.assertEqual(
self.own_module.checksum_installed, 'test',
'Uninstall cancellation does not preserve checksum_installed',
)
def test_button_upgrade_cancel(self):
"""It should preserve checksum_installed when cancelling upgrades"""
self.own_module.write({'state': 'to upgrade'})
self.own_module.checksum_installed = 'test'
self.own_module.button_upgrade_cancel()
self.assertEqual(
self.own_module.checksum_installed, 'test',
'Upgrade cancellation does not preserve checksum_installed',
)
def test_create(self):
"""It should call _store_checksum_installed method"""
_store_checksum_installed_mock = mock.MagicMock()
try:
self.env['ir.module.module']._patch_method(
'_store_checksum_installed',
_store_checksum_installed_mock,
)
vals = {
'name': 'module_auto_update_test_module',
'state': 'installed',
}
self.create_test_module(vals)
_store_checksum_installed_mock.assert_called_once_with(vals)
finally:
self.env['ir.module.module']._revert_method(
'_store_checksum_installed',
)
@mute_logger("openerp.modules.module")
@mock.patch('%s.get_module_path' % model)
def test_get_module_list(self, module_path_mock):
"""It should change the state of modules with different
checksum_dir and checksum_installed to 'to upgrade'"""
module_path_mock.return_value = self.own_dir_path
vals = {
'name': 'module_auto_update_test_module',
'state': 'installed',
}
test_module = self.create_test_module(vals)
test_module.checksum_installed = 'test'
self.env['base.module.upgrade'].get_module_list()
self.assertEqual(
test_module.state, 'to upgrade',
'List update does not mark upgradeable modules "to upgrade"',
)
@mock.patch('%s.get_module_path' % model)
def test_get_module_list_only_changes_installed(self, module_path_mock):
"""It should not change the state of a module with a former state
other than 'installed' to 'to upgrade'"""
module_path_mock.return_value = self.own_dir_path
vals = {
'name': 'module_auto_update_test_module',
'state': 'uninstalled',
}
test_module = self.create_test_module(vals)
self.env['base.module.upgrade'].get_module_list()
self.assertNotEqual(
test_module.state, 'to upgrade',
'List update changed state of an uninstalled module',
)
def test_write(self):
"""It should call _store_checksum_installed method"""
_store_checksum_installed_mock = mock.MagicMock()
self.env['ir.module.module']._patch_method(
'_store_checksum_installed',
_store_checksum_installed_mock,
)
vals = {'state': 'installed'}
self.own_module.write(vals)
_store_checksum_installed_mock.assert_called_once_with(vals)
self.env['ir.module.module']._revert_method(
'_store_checksum_installed',
)

46
module_auto_update/tests/test_module_upgrade_deprecated.py

@ -1,46 +0,0 @@
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import mock
from odoo.modules import get_module_path
from odoo.modules.registry import Registry
from odoo.tests.common import TransactionCase
from ..models.module_deprecated import PARAM_DEPRECATED
class TestModuleUpgrade(TransactionCase):
def setUp(self):
super(TestModuleUpgrade, self).setUp()
module_name = 'module_auto_update'
self.env["ir.config_parameter"].set_param(PARAM_DEPRECATED, "1")
self.own_module = self.env['ir.module.module'].search([
('name', '=', module_name),
])
self.own_dir_path = get_module_path(module_name)
def test_upgrade_module_cancel(self):
"""It should preserve checksum_installed when cancelling upgrades"""
self.own_module.write({'state': 'to upgrade'})
self.own_module.checksum_installed = 'test'
self.env['base.module.upgrade'].upgrade_module_cancel()
self.assertEqual(
self.own_module.checksum_installed, 'test',
'Upgrade cancellation does not preserve checksum_installed',
)
@mock.patch.object(Registry, 'new')
def test_upgrade_module(self, new_mock):
"""Calls get_module_list when upgrading in api.model mode"""
get_module_list_mock = mock.MagicMock()
try:
self.env['base.module.upgrade']._patch_method(
'get_module_list',
get_module_list_mock,
)
self.env['base.module.upgrade'].upgrade_module()
get_module_list_mock.assert_called_once_with()
finally:
self.env['base.module.upgrade']._revert_method('get_module_list')

3
module_auto_update/wizards/__init__.py

@ -1,3 +0,0 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from . import module_upgrade_deprecated

84
module_auto_update/wizards/module_upgrade_deprecated.py

@ -1,84 +0,0 @@
# Copyright 2017 LasLabs Inc.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
from odoo import api, models
from ..models.module_deprecated import PARAM_DEPRECATED
_logger = logging.getLogger(__name__)
class ModuleUpgrade(models.TransientModel):
_inherit = 'base.module.upgrade'
@api.model
def _autoupdate_deprecated(self):
"""Know if we should enable deprecated features."""
deprecated = (
self.env["ir.config_parameter"].get_param(PARAM_DEPRECATED))
if deprecated is False:
# Enable deprecated features if this is the 1st automated update
# after the version that deprecated them (X.Y.2.0.0)
own_module = self.env["ir.module.module"].search([
("name", "=", "module_auto_update"),
])
try:
if own_module.latest_version.split(".")[2] == "1":
deprecated = "1"
except AttributeError:
pass # 1st install, there's no latest_version
return deprecated == "1"
@api.model
def get_module_list(self):
"""Set modules to upgrade searching by their dir checksum."""
if self._autoupdate_deprecated():
Module = self.env["ir.module.module"]
installed_modules = Module.search([('state', '=', 'installed')])
upgradeable_modules = installed_modules.filtered(
lambda r: r.checksum_dir != r.checksum_installed,
)
upgradeable_modules.button_upgrade()
return super(ModuleUpgrade, self).get_module_list()
@api.multi
def upgrade_module(self):
"""Make a fully automated addon upgrade."""
if self._autoupdate_deprecated():
_logger.warning(
"You are possibly using an unsupported upgrade system; "
"set '%s' system parameter to '0' and start calling "
"`env['ir.module.module'].upgrade_changed_checksum()` from "
"now on to get rid of this message. See module's README's "
"Known Issues section for further information on the matter."
)
# Compute updates by checksum when called in @api.model fashion
self.env.cr.autocommit(True) # Avoid transaction lock
if not self:
self.get_module_list()
Module = self.env["ir.module.module"]
# Get every addon state before updating
pre_states = {addon["name"]: addon["state"] for addon
in Module.search_read([], ["name", "state"])}
# Perform upgrades, possibly in a limited graph that excludes me
result = super(ModuleUpgrade, self).upgrade_module()
if self._autoupdate_deprecated():
self.env.cr.autocommit(False)
# Reload environments, anything may have changed
self.env.clear()
# Update addons checksum if state changed and I wasn't uninstalled
own = Module.search_read(
[("name", "=", "module_auto_update")],
["state"],
limit=1)
if own and own[0]["state"] != "uninstalled":
for addon in Module.search([]):
if addon.state != pre_states.get(addon.name):
# Trigger the write hook that should have been
# triggered when the module was [un]installed/updated
# in the limited module graph inside above call to
# super(), and updates its dir checksum as needed
addon.latest_version = addon.latest_version
return result

2
setup/.setuptools-odoo-make-default-ignore

@ -0,0 +1,2 @@
# addons listed in this file are ignored by
# setuptools-odoo-make-default (one addon per line)

2
setup/README

@ -0,0 +1,2 @@
To learn more about this directory, please visit
https://pypi.python.org/pypi/setuptools-odoo

1
setup/module_auto_update/odoo/addons/module_auto_update

@ -0,0 +1 @@
../../../../module_auto_update

6
setup/module_auto_update/setup.py

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
Loading…
Cancel
Save