diff --git a/openupgrade_framework/odoo_patch/__init__.py b/openupgrade_framework/odoo_patch/__init__.py index 1fd6e16..56a70db 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 e5aa886..e69de29 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 f7b8b86..0000000 --- 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/models/__init__.py b/openupgrade_framework/odoo_patch/addons/point_of_sale/models/__init__.py deleted file mode 100644 index db8634a..0000000 --- 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 ac0f5dc..0000000 --- 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 b66d7f4..0000000 --- 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 f5065ae..f13459f 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 ee09595..0000000 --- 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 eb25c80..6386567 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 6ad1565..0000000 --- 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 49531bf..0000000 --- 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 81c8916..0000000 --- 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')) diff --git a/openupgrade_records/__init__.py b/openupgrade_records/__init__.py index e28ed8d..090024d 100644 --- a/openupgrade_records/__init__.py +++ b/openupgrade_records/__init__.py @@ -1,3 +1,5 @@ +from . import blacklist from . import models +from . import odoo_patch +from . import openupgrade_log from . import wizards -from . import blacklist diff --git a/openupgrade_records/odoo_patch/__init__.py b/openupgrade_records/odoo_patch/__init__.py new file mode 100644 index 0000000..4a18379 --- /dev/null +++ b/openupgrade_records/odoo_patch/__init__.py @@ -0,0 +1,3 @@ +from . import addons +from . import odoo +from . import odoo_patch diff --git a/openupgrade_records/odoo_patch/addons/__init__.py b/openupgrade_records/odoo_patch/addons/__init__.py new file mode 100644 index 0000000..3247dc7 --- /dev/null +++ b/openupgrade_records/odoo_patch/addons/__init__.py @@ -0,0 +1,3 @@ +from . import mrp +from . import point_of_sale +from . import stock diff --git a/openupgrade_records/odoo_patch/addons/mrp/__init__.py b/openupgrade_records/odoo_patch/addons/mrp/__init__.py new file mode 100644 index 0000000..1770464 --- /dev/null +++ b/openupgrade_records/odoo_patch/addons/mrp/__init__.py @@ -0,0 +1,11 @@ +from odoo.addons import mrp +from odoo.addons.openupgrade_records.odoo_patch.odoo_patch import OdooPatch + + +class PreInitHookPatch(OdooPatch): + target = mrp + method_names = ['_pre_init_mrp'] + + def _pre_init_mrp(cr): + """ Don't try to create an existing column on reinstall """ + pass diff --git a/openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py b/openupgrade_records/odoo_patch/addons/point_of_sale/__init__.py similarity index 100% rename from openupgrade_framework/odoo_patch/addons/point_of_sale/__init__.py rename to openupgrade_records/odoo_patch/addons/point_of_sale/__init__.py diff --git a/openupgrade_records/odoo_patch/addons/point_of_sale/models/pos_config.py b/openupgrade_records/odoo_patch/addons/point_of_sale/models/pos_config.py new file mode 100644 index 0000000..31707d8 --- /dev/null +++ b/openupgrade_records/odoo_patch/addons/point_of_sale/models/pos_config.py @@ -0,0 +1,13 @@ +from odoo import api +from odoo.addons.point_of_sale.models.pos_config import PosConfig +from odoo.addons.openupgrade_records.odoo_patch.odoo_patch import OdooPatch + + +class PostInstallPosLocalisationPatch(OdooPatch): + target = PosConfig + method_names = ['post_install_pos_localisation'] + + @api.model + def post_install_pos_localisation(self, companies=False): + """ Don't create duplicate journals etc. on reinstall """ + pass diff --git a/openupgrade_records/odoo_patch/addons/stock/__init__.py b/openupgrade_records/odoo_patch/addons/stock/__init__.py new file mode 100644 index 0000000..610d162 --- /dev/null +++ b/openupgrade_records/odoo_patch/addons/stock/__init__.py @@ -0,0 +1,11 @@ +from odoo.addons import stock +from odoo.addons.openupgrade_records.odoo_patch.odoo_patch import OdooPatch + + +class PreInitHookPatch(OdooPatch): + target = stock + method_names = ['pre_init_hook'] + + def pre_init_hook(cr): + """ Don't unlink stock data on reinstall """ + pass diff --git a/openupgrade_records/odoo_patch/odoo/__init__.py b/openupgrade_records/odoo_patch/odoo/__init__.py new file mode 100644 index 0000000..4541924 --- /dev/null +++ b/openupgrade_records/odoo_patch/odoo/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import modules +from . import tools diff --git a/openupgrade_records/odoo_patch/odoo/models.py b/openupgrade_records/odoo_patch/odoo/models.py new file mode 100644 index 0000000..ea1be85 --- /dev/null +++ b/openupgrade_records/odoo_patch/odoo/models.py @@ -0,0 +1,22 @@ +from odoo import api, models +from odoo.addons.openupgrade_records.odoo_patch.odoo_patch import OdooPatch +from odoo.addons.openupgrade_records import openupgrade_log +from odoo.models import BaseModel + + +class BaseModelPatch(OdooPatch): + target = models.BaseModel + method_names = ['_convert_records'] + + @api.model + def _convert_records(self, records, log=lambda a: None): + """ Log data ids that are imported with `load` """ + current_module = self.env.context['module'] + for res in BaseModelPatch._convert_records._original_method( + self, records, log=log): + _id, xid, _record, _info = res + if xid: + xid = xid if '.' in xid else "%s.%s" % (current_module, xid) + openupgrade_log.log_xml_id(self.env.cr, current_module, xid) + + yield res diff --git a/openupgrade_records/odoo_patch/odoo/modules/__init__.py b/openupgrade_records/odoo_patch/odoo/modules/__init__.py new file mode 100644 index 0000000..7246323 --- /dev/null +++ b/openupgrade_records/odoo_patch/odoo/modules/__init__.py @@ -0,0 +1 @@ +from . import registry diff --git a/openupgrade_records/odoo_patch/odoo/modules/registry.py b/openupgrade_records/odoo_patch/odoo/modules/registry.py new file mode 100644 index 0000000..a439460 --- /dev/null +++ b/openupgrade_records/odoo_patch/odoo/modules/registry.py @@ -0,0 +1,29 @@ +import logging +from threading import current_thread +from odoo import api, SUPERUSER_ID +from odoo.addons.openupgrade_records.odoo_patch.odoo_patch import OdooPatch +from odoo.addons.openupgrade_records import openupgrade_log +from odoo.modules.registry import Registry + +_logger = logging.getLogger(__name__) + + +class RegistryPatch(OdooPatch): + target = Registry + method_names = ['init_models'] + + def init_models(self, cr, model_names, context, install=True): + module_name = context['module'] + _logger.debug('Logging models of module %s', module_name) + upg_registry = current_thread()._openupgrade_registry + local_registry = {} + env = api.Environment(cr, SUPERUSER_ID, {}) + for model in env.values(): + if not model._auto: + continue + openupgrade_log.log_model(model, local_registry) + openupgrade_log.compare_registries( + cr, context['module'], upg_registry, local_registry) + + return RegistryPatch.init_models._original_method( + self, cr, model_names, context, install=install) diff --git a/openupgrade_records/odoo_patch/odoo/tools/__init__.py b/openupgrade_records/odoo_patch/odoo/tools/__init__.py new file mode 100644 index 0000000..99a9527 --- /dev/null +++ b/openupgrade_records/odoo_patch/odoo/tools/__init__.py @@ -0,0 +1 @@ +from . import convert diff --git a/openupgrade_records/odoo_patch/odoo/tools/convert.py b/openupgrade_records/odoo_patch/odoo/tools/convert.py new file mode 100644 index 0000000..2f1d232 --- /dev/null +++ b/openupgrade_records/odoo_patch/odoo/tools/convert.py @@ -0,0 +1,13 @@ +from odoo.addons.openupgrade_records.odoo_patch.odoo_patch import OdooPatch +from odoo.addons.openupgrade_records import openupgrade_log +from odoo.tools.convert import xml_import + + +class XMLImportPatch(OdooPatch): + target = xml_import + method_names = ['_test_xml_id'] + + def _test_xml_id(self, xml_id): + res = XMLImportPatch._test_xml_id._original_method(self, xml_id) + openupgrade_log.log_xml_id(self.env.cr, self.module, xml_id) + return res diff --git a/openupgrade_records/odoo_patch/odoo_patch.py b/openupgrade_records/odoo_patch/odoo_patch.py new file mode 100644 index 0000000..ac9f02f --- /dev/null +++ b/openupgrade_records/odoo_patch/odoo_patch.py @@ -0,0 +1,59 @@ +import logging + +_logger = logging.getLogger(__name__) + + +class OdooPatch(object): + """ Simple mechanism to apply a collection of monkeypatches using a + context manager. + + Classes can register their monkeypatches by inheriting from this class. + They need to define a `target` member, referring to the object or module + that needs to be patched, and a list `method_names`. They also need to + redefine those methods under the same name. + + The original method is made available on the new method as + `_original_method`. + + Example: + + ``` + from odoo import api + from odoo.addons.some_module.models.my_model import MyModel + + class MyModelPatch(OdooPatch): + target = MyModel + method_names = ['do_something'] + + @api.model + def do_something(self): + res = MyModelPatch.do_something._original_method() + ... + return res + ``` + + Usage: + + ``` + with OdooPatch(): + do_something() + ``` + """ + def __enter__(self): + for cls in OdooPatch.__subclasses__(): + for method_name in cls.method_names: + method = getattr(cls, method_name) + setattr(method, '_original_method', + getattr(cls.target, method_name)) + setattr(cls.target, method_name, method) + + def __exit__(self, exc_type, exc_value, tb): + for cls in OdooPatch.__subclasses__(): + for method_name in cls.method_names: + method = getattr(cls.target, method_name) + if hasattr(method, '_original_method'): + setattr(cls.target, method_name, method._original_method) + else: + _logger.warn( + '_original_method not found on method %s of class %s', + method_name, cls.target) diff --git a/openupgrade_records/openupgrade_log.py b/openupgrade_records/openupgrade_log.py new file mode 100644 index 0000000..fc14e03 --- /dev/null +++ b/openupgrade_records/openupgrade_log.py @@ -0,0 +1,212 @@ +# Copyright 2011-2015 Therp BV +# Copyright 2016 Opener B.V. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging +from openupgradelib.openupgrade_tools import table_exists + +_logger = logging.getLogger(__name__) + + +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 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 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: + _logger.warn("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_records/wizards/openupgrade_generate_records_wizard.py b/openupgrade_records/wizards/openupgrade_generate_records_wizard.py index 9c6f113..4fd517c 100644 --- a/openupgrade_records/wizards/openupgrade_generate_records_wizard.py +++ b/openupgrade_records/wizards/openupgrade_generate_records_wizard.py @@ -2,7 +2,9 @@ # Copyright 2016 Opener B.V. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from ..odoo_patch.odoo_patch import OdooPatch from openupgradelib import openupgrade_tools +from threading import current_thread from odoo import _, fields, models from odoo.exceptions import UserError @@ -63,7 +65,17 @@ def generate(self): {"state": "to install"} ) self.env.cr.commit() # pylint: disable=invalid-commit - Registry.new(self.env.cr.dbname, update_module=True) + + # Patch the registry on the thread + thread = current_thread() + thread._openupgrade_registry = {} + + # Regenerate the registry with monkeypatches that log the records + with OdooPatch(): + Registry.new(self.env.cr.dbname, update_module=True) + + # Free the registry + delattr(thread, '_openupgrade_registry') # Set domain property self.env.cr.execute( diff --git a/openupgrade_records/wizards/openupgrade_install_all_wizard.py b/openupgrade_records/wizards/openupgrade_install_all_wizard.py index 7c83395..f469eb9 100644 --- a/openupgrade_records/wizards/openupgrade_install_all_wizard.py +++ b/openupgrade_records/wizards/openupgrade_install_all_wizard.py @@ -49,7 +49,7 @@ def install_all(self, extra_domain=None): domain = self._domain_to_install(extra_domain=extra_domain) modules = self.env["ir.module.module"].search(domain) if modules: - modules.write({"state": "to install"}) + modules.button_install() self.env.cr.commit() # pylint: disable=invalid-commit Registry.new(self.env.cr.dbname, update_module=True) self.write({"state": "ready"})