diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql index c490df32294..3fa847ce23c 100644 --- a/openerp/addons/base/base.sql +++ b/openerp/addons/base/base.sql @@ -149,7 +149,6 @@ CREATE TABLE res_users ( active boolean default True, login varchar(64) NOT NULL UNIQUE, password varchar(64) default null, - tz varchar(64) default null, lang varchar(64) default '', -- No FK references below, will be added later by ORM -- (when the destination rows exist) diff --git a/openerp/addons/base/base_demo.xml b/openerp/addons/base/base_demo.xml index 71a5641a540..69cc4994f82 100644 --- a/openerp/addons/base/base_demo.xml +++ b/openerp/addons/base/base_demo.xml @@ -88,6 +88,7 @@ gk//2Q== admin@example.com + Europe/Brussels /9j/4AAQSkZJRgABAQEBLAEsAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEP ERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4e Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCACmAKYDASIA diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index c6da65c7c2c..fd00e72c4a2 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -18,7 +18,10 @@ # along with this program. If not, see . # ############################################################################## +from docutils import io, nodes from docutils.core import publish_string +from docutils.transforms import Transform, writer_aux +from docutils.writers.html4css1 import Writer import imp import logging import os @@ -37,6 +40,7 @@ except ImportError: import openerp from openerp import modules, pooler, tools, addons +from openerp.modules.db import create_categories from openerp.tools.parse_version import parse_version from openerp.tools.translate import _ from openerp.osv import fields, osv, orm @@ -104,6 +108,32 @@ class module_category(osv.osv): 'visible': 1, } +class MyFilterMessages(Transform): + """ + Custom docutils transform to remove `system message` for a document and + generate warnings. + + (The standard filter removes them based on some `report_level` passed in + the `settings_override` dictionary, but if we use it, we can't see them + and generate warnings.) + """ + + default_priority = 870 + + def apply(self): + for node in self.document.traverse(nodes.system_message): + _logger.warning("docutils' system message present: %s", str(node)) + node.parent.remove(node) + +class MyWriter(Writer): + """ + Custom docutils html4ccs1 writer that doesn't add the warnings to the + output document. + """ + + def get_transforms(self): + return [MyFilterMessages, writer_aux.Admonitions] + class module(osv.osv): _name = "ir.module.module" _rec_name = "shortdesc" @@ -123,7 +153,7 @@ class module(osv.osv): res = dict.fromkeys(ids, '') for module in self.browse(cr, uid, ids, context=context): overrides = dict(embed_stylesheet=False, doctitle_xform=False, output_encoding='unicode') - output = publish_string(source=module.description, writer_name='html', settings_overrides=overrides) + output = publish_string(source=module.description, settings_overrides=overrides, writer=MyWriter()) res[module.id] = output return res @@ -692,21 +722,8 @@ class module(osv.osv): categs = category.split('/') if categs != current_category_path: - p_id = None - while categs: - if p_id is not None: - cr.execute('SELECT id FROM ir_module_category WHERE name=%s AND parent_id=%s', (categs[0], p_id)) - else: - cr.execute('SELECT id FROM ir_module_category WHERE name=%s AND parent_id is NULL', (categs[0],)) - c_id = cr.fetchone() - if not c_id: - cr.execute('INSERT INTO ir_module_category (name, parent_id) VALUES (%s, %s) RETURNING id', (categs[0], p_id)) - c_id = cr.fetchone()[0] - else: - c_id = c_id[0] - p_id = c_id - categs = categs[1:] - self.write(cr, uid, [mod_browse.id], {'category_id': p_id}) + cat_id = create_categories(cr, categs) + mod_browse.write({'category_id': cat_id}) def update_translations(self, cr, uid, ids, filter_lang=None, context=None): if not filter_lang: diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 56ac39a6f84..b14620a58a4 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -19,6 +19,7 @@ # ############################################################################## +import datetime import math import openerp from osv import osv, fields @@ -178,6 +179,12 @@ class res_partner(osv.osv, format_address): result[obj.id] = tools.image_get_resized_images(obj.image) return result + def _get_tz_offset(self, cr, uid, ids, name, args, context=None): + result = dict.fromkeys(ids, False) + for obj in self.browse(cr, uid, ids, context=context): + result[obj.id] = datetime.datetime.now(pytz.timezone(obj.tz or 'GMT')).strftime('%z') + return result + def _set_image(self, cr, uid, id, name, value, args, context=None): return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context) @@ -195,6 +202,7 @@ class res_partner(osv.osv, format_address): help="The partner's timezone, used to output proper date and time values inside printed reports. " "It is important to set a value for this field. You should use the same timezone " "that is otherwise used to pick and render date and time values: your computer's timezone."), + 'tz_offset': fields.function(_get_tz_offset, type='char', size=5, string='Timezone offset', store=True), 'user_id': fields.many2one('res.users', 'Salesperson', help='The internal user that is in charge of communicating with this contact if any.'), 'vat': fields.char('TIN', size=32, help="Tax Identification Number. Check the box if this contact is subjected to taxes. Used by the some of the legal statements."), 'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'), diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 4565f861b40..2b613778419 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -343,7 +343,7 @@ class res_users(osv.osv): for k in self._all_columns.keys(): if k.startswith('context_'): context_key = k[8:] - elif k in ['lang', 'tz']: + elif k in ['lang', 'tz', 'tz_offset']: context_key = k else: context_key = False diff --git a/openerp/cli/server.py b/openerp/cli/server.py index 8ef63ddc414..3b90f65b6bd 100644 --- a/openerp/cli/server.py +++ b/openerp/cli/server.py @@ -94,7 +94,11 @@ def setup_pid_file(): def preload_registry(dbname): """ Preload a registry, and start the cron.""" try: - db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=openerp.tools.config['init'] or openerp.tools.config['update'], pooljobs=False) + config = openerp.tools.config + update_module = True if config['init'] or config['update'] else False + db, registry = openerp.pooler.get_db_and_pool( + dbname, update_module=update_module, pooljobs=False, + force_demo=not config['without_demo']) # jobs will start to be processed later, when openerp.cron.start_master_thread() is called by openerp.service.start_services() registry.schedule_cron_jobs() @@ -105,7 +109,9 @@ def run_test_file(dbname, test_file): """ Preload a registry, possibly run a test file, and start the cron.""" try: config = openerp.tools.config - db, registry = openerp.pooler.get_db_and_pool(dbname, update_module=config['init'] or config['update'], pooljobs=False) + update_module = True if config['init'] or config['update'] else False + db, registry = openerp.pooler.get_db_and_pool( + dbname, update_module=update_module, pooljobs=False, force_demo=not config['without_demo']) cr = db.cursor() _logger.info('loading test file %s', test_file) openerp.tools.convert_yaml_import(cr, 'base', file(test_file), 'test', {}, 'test', True) diff --git a/openerp/modules/db.py b/openerp/modules/db.py index 6318bd954f3..f13708a8a9b 100644 --- a/openerp/modules/db.py +++ b/openerp/modules/db.py @@ -3,7 +3,7 @@ # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). -# Copyright (C) 2010 OpenERP s.a. (). +# Copyright (C) 2010-2012 OpenERP s.a. (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -119,21 +119,17 @@ def create_categories(cr, categories): category = [] while categories: category.append(categories[0]) - if p_id is not None: - cr.execute('SELECT id \ - FROM ir_module_category \ - WHERE name=%s AND parent_id=%s', (categories[0], p_id)) - else: - cr.execute('SELECT id \ - FROM ir_module_category \ - WHERE name=%s AND parent_id IS NULL', (categories[0],)) + xml_id = 'module_category_' + ('_'.join(map(lambda x: x.lower(), category))).replace('&', 'and').replace(' ', '_') + # search via xml_id (because some categories are renamed) + cr.execute("SELECT res_id FROM ir_model_data WHERE name=%s AND module=%s AND model=%s", + (xml_id, "base", "ir.module.category")) + c_id = cr.fetchone() if not c_id: cr.execute('INSERT INTO ir_module_category \ (name, parent_id) \ VALUES (%s, %s) RETURNING id', (categories[0], p_id)) c_id = cr.fetchone()[0] - xml_id = 'module_category_' + ('_'.join(map(lambda x: x.lower(), category))).replace('&', 'and').replace(' ', '_') cr.execute('INSERT INTO ir_model_data (module, name, res_id, model) \ VALUES (%s, %s, %s, %s)', ('base', xml_id, c_id, 'ir.module.category')) else: diff --git a/openerp/modules/graph.py b/openerp/modules/graph.py index 56b17e3239a..f7e9bc7c77a 100644 --- a/openerp/modules/graph.py +++ b/openerp/modules/graph.py @@ -87,15 +87,19 @@ class Graph(dict): for k, v in additional_data[package.name].items(): setattr(package, k, v) - def add_module(self, cr, module, force=None): - self.add_modules(cr, [module], force) + def add_module(self, cr, module, force_demo=False): + self.add_modules(cr, [module], force_demo) - def add_modules(self, cr, module_list, force=None): - if force is None: - force = [] + def add_modules(self, cr, module_list, force_demo=False): packages = [] len_graph = len(self) for module in module_list: + if force_demo: + cr.execute(""" + UPDATE ir_module_module + SET demo='t' + WHERE name = %s""", + (module,)) # 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. @@ -121,9 +125,6 @@ class Graph(dict): current.remove(package) node = self.add_node(package, info) node.data = 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)) @@ -185,18 +186,11 @@ class Node(Singleton): node.depth = self.depth + 1 if node not in self.children: self.children.append(node) - for attr in ('init', 'update', 'demo'): - if hasattr(self, attr): - setattr(node, attr, True) self.children.sort(lambda x, y: cmp(x.name, y.name)) return node def __setattr__(self, name, value): super(Singleton, self).__setattr__(name, value) - if name in ('init', 'update', 'demo'): - tools.config[name][self.name] = 1 - for child in self.children: - setattr(child, name, value) if name == 'depth': for child in self.children: setattr(child, name, value + 1) diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index bcda1690c91..13b02bd7a6e 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -42,6 +42,7 @@ from openerp import SUPERUSER_ID from openerp import SUPERUSER_ID from openerp.tools.translate import _ +from openerp.tools import assertion_report from openerp.modules.module import initialize_sys_path, \ load_openerp_module, init_module_models @@ -157,7 +158,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= models = pool.load(cr, package) loaded_modules.append(package.name) - if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): + if package.state in ('to install', 'to upgrade'): init_module_models(cr, package.name, models) pool._init_modules.add(package.name) status['progress'] = float(index) / len(graph) @@ -171,18 +172,19 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= idref = {} - mode = 'update' - if hasattr(package, 'init') or package.state == 'to install': + if package.state == 'to install': mode = 'init' + else: + mode = 'update' - if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): + if package.state in ('to install', 'to upgrade'): if package.state=='to upgrade': # upgrading the module information modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data)) load_init_xml(module_name, idref, mode) load_update_xml(module_name, idref, mode) load_data(module_name, idref, mode) - if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'): + if package.dbdemo and package.state != 'installed': status['progress'] = (index + 0.75) / len(graph) load_demo_xml(module_name, idref, mode) load_demo(module_name, idref, mode) @@ -212,9 +214,6 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= modobj.update_translations(cr, SUPERUSER_ID, [module_id], None) package.state = 'installed' - for kind in ('init', 'demo', 'update'): - if hasattr(package, kind): - delattr(package, kind) cr.commit() @@ -269,10 +268,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if not openerp.modules.db.is_initialized(cr): _logger.info("init db") openerp.modules.db.initialize(cr) - tools.config["init"]["all"] = 1 - tools.config['update']['all'] = 1 - if not tools.config['without_demo']: - tools.config["demo"]['all'] = 1 + update_module = True # This is a brand new pool, just created in pooler.get_db_and_pool() pool = pooler.get_pool(cr.dbname) @@ -282,43 +278,51 @@ def load_modules(db, force_demo=False, status=None, update_module=False): # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) graph = openerp.modules.graph.Graph() - graph.add_module(cr, 'base', force) + graph.add_module(cr, 'base', force_demo) if not graph: _logger.critical('module base cannot be loaded! (hint: verify addons-path)') raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)')) # processed_modules: for cleanup step after install # loaded_modules: to avoid double loading - report = pool._assertion_report - loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report) + # After load_module_graph(), 'base' has been installed or updated and its state is 'installed'. + report = assertion_report.assertion_report() + loaded_modules, processed_modules = load_module_graph(cr, graph, status, report=report) if tools.config['load_language']: for lang in tools.config['load_language'].split(','): tools.load_language(cr, lang) # STEP 2: Mark other modules to be loaded/updated + # This is a one-shot use of tools.config[init|update] from the command line + # arguments. It is directly cleared to not interfer with later create/update + # issued via RPC. if update_module: modobj = pool.get('ir.module.module') - if ('base' in tools.config['init']) or ('base' in tools.config['update']): + if ('base' in tools.config['init']) or ('base' in tools.config['update']) \ + or ('all' in tools.config['init']) or ('all' in tools.config['update']): _logger.info('updating modules list') modobj.update_list(cr, SUPERUSER_ID) + if 'all' in tools.config['init']: + ids = modobj.search(cr, 1, []) + tools.config['init'] = dict.fromkeys([m['name'] for m in modobj.read(cr, 1, ids, ['name'])], 1) + _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys())) - mods = [k for k in tools.config['init'] if tools.config['init'][k]] - if mods: - ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)]) - if ids: - modobj.button_install(cr, SUPERUSER_ID, ids) + mods = [k for k in tools.config['init'] if tools.config['init'][k] and k not in ('base', 'all')] + ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)]) + if ids: + modobj.button_install(cr, SUPERUSER_ID, ids) # goes from 'uninstalled' to 'to install' - mods = [k for k in tools.config['update'] if tools.config['update'][k]] - if mods: - ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)]) - if ids: - modobj.button_upgrade(cr, SUPERUSER_ID, ids) - - cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base')) + mods = [k for k in tools.config['update'] if tools.config['update'][k] and k not in ('base', 'all')] + ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)]) + if ids: + modobj.button_upgrade(cr, SUPERUSER_ID, ids) # goes from 'installed' to 'to upgrade' + # Remove that funky global one-shot thingy. + for kind in ('init', 'demo', 'update'): + tools.config[kind] = {} # 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 @@ -370,9 +374,6 @@ def load_modules(db, force_demo=False, status=None, update_module=False): # Cleanup orphan records pool.get('ir.model.data')._process_end(cr, SUPERUSER_ID, processed_modules) - for kind in ('init', 'demo', 'update'): - tools.config[kind] = {} - cr.commit() # STEP 5: Cleanup menus diff --git a/openerp/service/web_services.py b/openerp/service/web_services.py index 57eb434100d..5b6d6f8be1c 100644 --- a/openerp/service/web_services.py +++ b/openerp/service/web_services.py @@ -205,10 +205,15 @@ class db(netsvc.ExportService): # Try to terminate all other connections that might prevent # dropping the database try: - cr.execute("""SELECT pg_terminate_backend(procpid) + + # PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid: + # http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389 + pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid' + + cr.execute("""SELECT pg_terminate_backend(%(pid_col)s) FROM pg_stat_activity - WHERE datname = %s AND - procpid != pg_backend_pid()""", + WHERE datname = %%s AND + %(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col}, (db_name,)) except Exception: pass diff --git a/openerp/tests/addons/test_impex/tests/test_load.py b/openerp/tests/addons/test_impex/tests/test_load.py index 7c56ab78bbd..fe01a267d58 100644 --- a/openerp/tests/addons/test_impex/tests/test_load.py +++ b/openerp/tests/addons/test_impex/tests/test_load.py @@ -1140,6 +1140,10 @@ class test_datetime(ImporterCase): """ If there is no tz either in the context or on the user, falls back to UTC """ + self.registry('res.users').write( + self.cr, openerp.SUPERUSER_ID, [openerp.SUPERUSER_ID], + {'tz': False}) + result = self.import_(['value'], [['2012-02-03 11:11:11']]) self.assertFalse(result['messages']) self.assertEqual( diff --git a/openerp/workflow/wkf_expr.py b/openerp/workflow/wkf_expr.py index 608af34d118..b4d6c5b9ccc 100644 --- a/openerp/workflow/wkf_expr.py +++ b/openerp/workflow/wkf_expr.py @@ -46,6 +46,8 @@ def _eval_expr(cr, ident, workitem, action): assert action, 'You used a NULL action in a workflow, use dummy node instead.' for line in action.split('\n'): line = line.strip() + if not line: + continue uid=ident[0] model=ident[1] ids=[ident[2]]