Stéphane Bidoul (ACSONE)
7 years ago
No known key found for this signature in database
GPG Key ID: BCAB2555446B5B92
14 changed files with 11 additions and 448 deletions
-
23module_auto_update/README.rst
-
3module_auto_update/__init__.py
-
6module_auto_update/__openerp__.py
-
15module_auto_update/data/cron_data_deprecated.xml
-
10module_auto_update/hooks.py
-
23module_auto_update/migrations/9.0.2.0.0/pre-migrate.py
-
1module_auto_update/models/__init__.py
-
2module_auto_update/models/module.py
-
65module_auto_update/models/module_deprecated.py
-
2module_auto_update/tests/__init__.py
-
172module_auto_update/tests/test_module_deprecated.py
-
47module_auto_update/tests/test_module_upgrade_deprecated.py
-
5module_auto_update/wizards/__init__.py
-
85module_auto_update/wizards/module_upgrade_deprecated.py
@ -1,15 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<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">base.module.upgrade</field> |
|
||||
<field name="function">upgrade_module</field> |
|
||||
<field name="args" eval="'()'"/> |
|
||||
</record> |
|
||||
</odoo> |
|
@ -1,23 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Copyright 2018 Tecnativa - Jairo Llopis |
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). |
|
||||
import logging |
|
||||
from psycopg2 import IntegrityError |
|
||||
from openerp.addons.module_auto_update.models.module_deprecated import \ |
|
||||
PARAM_DEPRECATED |
|
||||
|
|
||||
_logger = logging.getLogger(__name__) |
|
||||
|
|
||||
|
|
||||
def migrate(cr, version): |
|
||||
"""Autoenable deprecated behavior.""" |
|
||||
try: |
|
||||
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,69 +1,4 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
# Copyright 2017 LasLabs Inc. |
|
||||
# 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 openerp import api, fields, models |
|
||||
|
|
||||
PARAM_DEPRECATED = "module_auto_update.enable_deprecated" |
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 |
|
@ -1,172 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Copyright 2017 LasLabs Inc. |
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
|
||||
|
|
||||
import os |
|
||||
|
|
||||
import mock |
|
||||
|
|
||||
from openerp.modules import get_module_path |
|
||||
from openerp.tests.common import TransactionCase |
|
||||
from openerp.tools import mute_logger |
|
||||
|
|
||||
from openerp.addons.module_auto_update.addon_hash import addon_hash |
|
||||
|
|
||||
from ..models.module_deprecated import PARAM_DEPRECATED |
|
||||
|
|
||||
|
|
||||
model = 'openerp.addons.module_auto_update.models.module' |
|
||||
|
|
||||
|
|
||||
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', |
|
||||
) |
|
||||
|
|
||||
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', |
|
||||
) |
|
@ -1,47 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Copyright 2017 LasLabs Inc. |
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
|
||||
|
|
||||
import mock |
|
||||
|
|
||||
from openerp.modules import get_module_path |
|
||||
from openerp.modules.registry import RegistryManager |
|
||||
from openerp.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(RegistryManager, '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') |
|
@ -1,5 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Copyright 2017 LasLabs Inc. |
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
|
||||
|
|
||||
from . import module_upgrade_deprecated |
|
@ -1,85 +0,0 @@ |
|||||
# -*- coding: utf-8 -*- |
|
||||
# Copyright 2017 LasLabs Inc. |
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
|
||||
|
|
||||
import logging |
|
||||
|
|
||||
from openerp 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 |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue