From fec027e83203e6705f690469cd39101e4eba3b06 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Tue, 15 Jan 2013 11:40:15 +0100 Subject: [PATCH 01/86] [IMP] reorder modules menus bzr revid: chs@openerp.com-20130115104015-5ytx00t0b5230aaf --- openerp/addons/base/module/module_view.xml | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/openerp/addons/base/module/module_view.xml b/openerp/addons/base/module/module_view.xml index 3a43a2591ba..3923c9de3bd 100644 --- a/openerp/addons/base/module/module_view.xml +++ b/openerp/addons/base/module/module_view.xml @@ -171,23 +171,18 @@ - - Modules ir.module.module form kanban,tree,form -

No module found!

You should try others search criteria.

- + @@ -195,20 +190,14 @@ Apps apps - - + Updates apps.updates {} - - + From 7e1a76cdd846a8c455d402fcd76d8baa58a9b2f8 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Tue, 15 Jan 2013 11:40:47 +0100 Subject: [PATCH 02/86] [FIX] setup.py: correct windows install bzr revid: chs@openerp.com-20130115104047-6hrl825bn5pkmoo6 --- setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.py b/setup.py index b144924d022..e4e6001d302 100755 --- a/setup.py +++ b/setup.py @@ -43,6 +43,12 @@ def data(): base = os.path.join('pytz', root[len(tzdir) + 1:]) r[base] = [os.path.join(root, f) for f in filenames] + import docutils + dudir = os.path.dirname(docutils.__file__) + for root, _, filenames in os.walk(dudir): + base = os.path.join('docutils', root[len(dudir) + 1:]) + r[base] = [os.path.join(root, f) for f in filenames if not f.endswith(('.py', '.pyc', '.pyo'))] + return r.items() def gen_manifest(): From e2639618f666605e385befb578db3f889a8c035f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 17 Jan 2013 10:27:22 +0100 Subject: [PATCH 03/86] [DOC] remove openerpdev intersphinx, add ref to module doc bzr revid: xmo@openerp.com-20130117092722-6rfhkcu4igrgeo06 --- addons/web/doc/conf.py | 1 - addons/web/doc/module.rst | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web/doc/conf.py b/addons/web/doc/conf.py index 54434190707..7fe38022ab1 100644 --- a/addons/web/doc/conf.py +++ b/addons/web/doc/conf.py @@ -253,6 +253,5 @@ todo_include_todos = True intersphinx_mapping = { 'python': ('http://docs.python.org/', None), 'openerpserver': ('http://doc.openerp.com/trunk/developers/server', None), - 'openerpdev': ('http://doc.openerp.com/trunk/developers', None), 'openerpcommand': ('http://doc.openerp.com/trunk/developers/command', None), } diff --git a/addons/web/doc/module.rst b/addons/web/doc/module.rst index 1bdb7c14799..8306e885f65 100644 --- a/addons/web/doc/module.rst +++ b/addons/web/doc/module.rst @@ -1,3 +1,5 @@ +.. _module: + Building an OpenERP Web module ============================== From 3574038ffdf78e8c63430ecc92a4f2d916207fda Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 17 Jan 2013 14:29:24 +0100 Subject: [PATCH 04/86] [FIX] apps: install_from_url: fix install condition + better logging bzr revid: chs@openerp.com-20130117132924-ejzbkh5przqi6hwf --- openerp/addons/base/module/module.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index 9e8b68451c5..123bf4124ed 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -2,7 +2,7 @@ ############################################################################## # # OpenERP, Open Source Management Solution -# Copyright (C) 2004-2012 OpenERP S.A. (). +# Copyright (C) 2004-2013 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 @@ -656,6 +656,7 @@ class module(osv.osv): def install_from_urls(self, cr, uid, urls, context=None): OPENERP = 'openerp' tmp = tempfile.mkdtemp() + _logger.debug('Install from url: %r', urls) try: # 1. Download & unzip missing modules for module_name, url in urls.items(): @@ -672,12 +673,13 @@ class module(osv.osv): zipfile.ZipFile(StringIO(content)).extractall(tmp) assert os.path.isdir(os.path.join(tmp, module_name)) - # 2a. Copy/Replace module source in addons path + # 2a. Copy/Replace module source in addons path for module_name, url in urls.items(): if module_name == OPENERP or not url: continue # OPENERP is special case, handled below, and no URL means local module module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False) bck = backup(module_path, False) + _logger.info('Copy downloaded module `%s` to `%s`', module_name, module_path) shutil.move(os.path.join(tmp, module_name), module_path) if bck: shutil.rmtree(bck) @@ -697,15 +699,22 @@ class module(osv.osv): # then replace the server by the new "base" module server_dir = openerp.tools.config['root_path'] # XXX or dirname() bck = backup(server_dir) + _logger.info('Copy downloaded module `openerp` to `%s`', server_dir) shutil.move(os.path.join(tmp, OPENERP), server_dir) #if bck: # shutil.rmtree(bck) self.update_list(cr, uid, context=context) - ids = self.search(cr, uid, [('name', 'in', urls.keys())], context=context) - if self.search_count(cr, uid, [('id', 'in', ids), ('state', '=', 'installed')], context=context): - # if any to update + with_urls = [m for m, u in urls.items() if u] + downloaded_ids = self.search(cr, uid, [('name', 'in', with_urls)], context=context) + already_installed = self.search(cr, uid, [('id', 'in', downloaded_ids), ('state', '=', 'installed')], context=context) + + to_install_ids = self.search(cr, uid, [('name', 'in', urls.keys()), ('state', '=', 'uninstalled')], context=context) + post_install_action = self.button_immediate_install(cr, uid, to_install_ids, context=context) + + if already_installed: + # in this case, force server restart to reload python code... cr.commit() openerp.service.restart_server() return { @@ -713,7 +722,7 @@ class module(osv.osv): 'tag': 'home', 'params': {'wait': True}, } - return self.button_immediate_install(cr, uid, ids, context=context) + return post_install_action finally: shutil.rmtree(tmp) From 4857a8a020d81ebd375122fb478726103c3f2393 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 17 Jan 2013 14:51:08 +0100 Subject: [PATCH 05/86] [FIX] module loading: call adapt_version() instead of computing it ourself bzr revid: chs@openerp.com-20130117135108-a4wt4wtbtfdwiui3 --- openerp/modules/loading.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index fb68642f137..638a40c9fa8 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -3,7 +3,7 @@ # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). -# Copyright (C) 2010-2012 OpenERP s.a. (). +# Copyright (C) 2010-2013 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 @@ -36,14 +36,12 @@ import openerp.modules.graph import openerp.modules.migration import openerp.osv as osv import openerp.pooler as pooler -import openerp.release as release import openerp.tools as tools from openerp import SUPERUSER_ID -from openerp import SUPERUSER_ID from openerp.tools.translate import _ from openerp.modules.module import initialize_sys_path, \ - load_openerp_module, init_module_models + load_openerp_module, init_module_models, adapt_version _logger = logging.getLogger(__name__) @@ -213,7 +211,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= migrations.migrate_module(package, 'post') - ver = release.major_version + '.' + package.data['version'] + ver = adapt_version(package.data['version']) # Set new modules and dependencies modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver}) # Update translations for all installed languages From 3276861ff6125d02e08b5c5b12b75925e056c4bc Mon Sep 17 00:00:00 2001 From: "Xavier Fernandez http://www.smile.fr" <> Date: Wed, 28 Aug 2013 17:21:17 +0200 Subject: [PATCH 07/86] [FIX] port Xavier Fernandez's fix for Bug #971412: Translations export has no fixed order lp bug: https://launchpad.net/bugs/971412 fixed bzr revid: ls@numerigraphe.com-20130828152117-e1h0yljgbn538v9o --- openerp/tools/translate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py index 41113b8ccd9..53762b87970 100644 --- a/openerp/tools/translate.py +++ b/openerp/tools/translate.py @@ -458,7 +458,8 @@ def trans_export(lang, modules, buffer, format, cr): row.setdefault('tnrs', []).append((type, name, res_id)) row.setdefault('comments', set()).update(comments) - for src, row in grouped_rows.items(): + for src in sorted(grouped_rows): + row = grouped_rows[src] if not lang: # translation template, so no translation value row['translation'] = '' From 1f3a482f4fc309262802b980e143234a56b9503d Mon Sep 17 00:00:00 2001 From: Lionel Sausin Date: Thu, 29 Aug 2013 09:50:35 +0200 Subject: [PATCH 08/86] [REF] avoid the lookup in sorted translation export as suggested by Guewen Baconnier @ Camptocamp bzr revid: ls@numerigraphe.com-20130829075035-lml81a7bbn66c8qu --- openerp/tools/translate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py index 53762b87970..6e9eb50f162 100644 --- a/openerp/tools/translate.py +++ b/openerp/tools/translate.py @@ -458,8 +458,7 @@ def trans_export(lang, modules, buffer, format, cr): row.setdefault('tnrs', []).append((type, name, res_id)) row.setdefault('comments', set()).update(comments) - for src in sorted(grouped_rows): - row = grouped_rows[src] + for src, row in sorted(grouped_rows.items()): if not lang: # translation template, so no translation value row['translation'] = '' From 855836e96e42f1be3075eaa87628184f52112b80 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Thu, 27 Feb 2014 13:56:48 +0100 Subject: [PATCH 09/86] [IMP] Move the netsvc.dispatch_rpc function from netsvc to openerp.http bzr revid: stw@openerp.com-20140227125648-kunaefr22y28honx --- openerp/http.py | 96 +++++++++++++++++++++++++++++++--- openerp/netsvc.py | 86 ------------------------------ openerp/service/wsgi_server.py | 2 +- openerp/tools/__init__.py | 2 +- openerp/tools/debugger.py | 8 +++ 5 files changed, 98 insertions(+), 96 deletions(-) create mode 100644 openerp/tools/debugger.py diff --git a/openerp/http.py b/openerp/http.py index bcd7511d769..ea1e82ed4c6 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -21,8 +21,10 @@ import time import traceback import urlparse import warnings +from pprint import pformat import babel.core +import psutil import psycopg2 import simplejson import werkzeug.contrib.sessions @@ -34,6 +36,7 @@ import werkzeug.wrappers import werkzeug.wsgi import openerp +import openerp.netsvc from openerp.service import security, model as service_model import openerp.tools @@ -50,6 +53,83 @@ request = _request_stack() A global proxy that always redirect to the current request object. """ +def replace_request_password(args): + # password is always 3rd argument in a request, we replace it in RPC logs + # so it's easier to forward logs for diagnostics/debugging purposes... + if len(args) > 2: + args = list(args) + args[2] = '*' + return tuple(args) + +def log(logger, level, prefix, msg, depth=None): + indent='' + indent_after=' '*len(prefix) + for line in (prefix+pformat(msg, depth=depth)).split('\n'): + logger.log(level, indent+line) + indent=indent_after + +def dispatch_rpc(service_name, method, params): + """ Handle a RPC call. + + This is pure Python code, the actual marshalling (from/to XML-RPC) is done + in a upper layer. + """ + try: + rpc_request = logging.getLogger(__name__ + '.rpc.request') + rpc_response = logging.getLogger(__name__ + '.rpc.response') + rpc_request_flag = rpc_request.isEnabledFor(logging.DEBUG) + rpc_response_flag = rpc_response.isEnabledFor(logging.DEBUG) + if rpc_request_flag or rpc_response_flag: + start_time = time.time() + start_rss, start_vms = 0, 0 + start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info() + if rpc_request and rpc_response_flag: + log(rpc_request, logging.DEBUG, '%s.%s' % (service_name, method), replace_request_password(params)) + + threading.current_thread().uid = None + threading.current_thread().dbname = None + if service_name == 'common': + dispatch = openerp.service.common.dispatch + elif service_name == 'db': + dispatch = openerp.service.db.dispatch + elif service_name == 'object': + dispatch = openerp.service.model.dispatch + elif service_name == 'report': + dispatch = openerp.service.report.dispatch + else: + dispatch = openerp.service.wsgi_server.rpc_handlers.get(service_name) + result = dispatch(method, params) + + if rpc_request_flag or rpc_response_flag: + end_time = time.time() + end_rss, end_vms = 0, 0 + end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info() + logline = '%s.%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (service_name, method, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024) + if rpc_response_flag: + log(rpc_response, logging.DEBUG, logline, result) + else: + log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1) + + return result + except openerp.osv.orm.except_orm: + raise + except openerp.exceptions.AccessError: + raise + except openerp.exceptions.AccessDenied: + raise + except openerp.exceptions.Warning: + raise + except openerp.exceptions.RedirectWarning: + raise + except openerp.exceptions.DeferredException, e: + _logger.exception(openerp.tools.exception_to_unicode(e)) + openerp.tools.debugger.post_mortem(openerp.tools.config, e.traceback) + raise + except Exception, e: + _logger.exception(openerp.tools.exception_to_unicode(e)) + openerp.tools.debugger.post_mortem(openerp.tools.config, sys.exc_info()) + raise + def local_redirect(path, query=None, keep_hash=False, forward_debug=True, code=303): url = path if not query: @@ -618,7 +698,7 @@ class SessionExpiredException(Exception): class Service(object): """ .. deprecated:: 8.0 - Use ``openerp.netsvc.dispatch_rpc()`` instead. + Use ``dispatch_rpc()`` instead. """ def __init__(self, session, service_name): self.session = session @@ -626,7 +706,7 @@ class Service(object): def __getattr__(self, method): def proxy_method(*args): - result = openerp.netsvc.dispatch_rpc(self.service_name, method, args) + result = dispatch_rpc(self.service_name, method, args) return result return proxy_method @@ -699,7 +779,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): HTTP_HOST=wsgienv['HTTP_HOST'], REMOTE_ADDR=wsgienv['REMOTE_ADDR'], ) - uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env]) + uid = dispatch_rpc('common', 'authenticate', [db, login, password, env]) else: security.check(db, uid, password) self.db = db @@ -803,14 +883,14 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): def send(self, service_name, method, *args): """ .. deprecated:: 8.0 - Use ``openerp.netsvc.dispatch_rpc()`` instead. + Use ``dispatch_rpc()`` instead. """ - return openerp.netsvc.dispatch_rpc(service_name, method, args) + return dispatch_rpc(service_name, method, args) def proxy(self, service): """ .. deprecated:: 8.0 - Use ``openerp.netsvc.dispatch_rpc()`` instead. + Use ``dispatch_rpc()`` instead. """ return Service(self, service) @@ -1184,7 +1264,7 @@ class Root(object): return request.registry['ir.http'].routing_map() def db_list(force=False, httprequest=None): - dbs = openerp.netsvc.dispatch_rpc("db", "list", [force]) + dbs = dispatch_rpc("db", "list", [force]) return db_filter(dbs, httprequest=httprequest) def db_filter(dbs, httprequest=None): @@ -1229,7 +1309,7 @@ class CommonController(Controller): @route('/jsonrpc', type='json', auth="none") def jsonrpc(self, service, method, args): """ Method used by client APIs to contact OpenERP. """ - return openerp.netsvc.dispatch_rpc(service, method, args) + return dispatch_rpc(service, method, args) @route('/gen_session_id', type='json', auth="none") def gen_session_id(self): diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 755f6513b25..f19162cde5a 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -26,10 +26,6 @@ import os import release import sys import threading -import time -import types -from pprint import pformat -import psutil import tools import openerp @@ -188,86 +184,4 @@ def init_alternative_logger(): logger.addHandler(handler) logger.setLevel(logging.ERROR) -def replace_request_password(args): - # password is always 3rd argument in a request, we replace it in RPC logs - # so it's easier to forward logs for diagnostics/debugging purposes... - if len(args) > 2: - args = list(args) - args[2] = '*' - return tuple(args) - -def log(logger, level, prefix, msg, depth=None): - indent='' - indent_after=' '*len(prefix) - for line in (prefix+pformat(msg, depth=depth)).split('\n'): - logger.log(level, indent+line) - indent=indent_after - -def dispatch_rpc(service_name, method, params): - """ Handle a RPC call. - - This is pure Python code, the actual marshalling (from/to XML-RPC) is done - in a upper layer. - """ - try: - rpc_request = logging.getLogger(__name__ + '.rpc.request') - rpc_response = logging.getLogger(__name__ + '.rpc.response') - rpc_request_flag = rpc_request.isEnabledFor(logging.DEBUG) - rpc_response_flag = rpc_response.isEnabledFor(logging.DEBUG) - if rpc_request_flag or rpc_response_flag: - start_time = time.time() - start_rss, start_vms = 0, 0 - start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info() - if rpc_request and rpc_response_flag: - log(rpc_request,logging.DEBUG,'%s.%s'%(service_name,method), replace_request_password(params)) - - threading.current_thread().uid = None - threading.current_thread().dbname = None - if service_name == 'common': - dispatch = openerp.service.common.dispatch - elif service_name == 'db': - dispatch = openerp.service.db.dispatch - elif service_name == 'object': - dispatch = openerp.service.model.dispatch - elif service_name == 'report': - dispatch = openerp.service.report.dispatch - else: - dispatch = openerp.service.wsgi_server.rpc_handlers.get(service_name) - result = dispatch(method, params) - - if rpc_request_flag or rpc_response_flag: - end_time = time.time() - end_rss, end_vms = 0, 0 - end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info() - logline = '%s.%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (service_name, method, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024) - if rpc_response_flag: - log(rpc_response,logging.DEBUG, logline, result) - else: - log(rpc_request,logging.DEBUG, logline, replace_request_password(params), depth=1) - - return result - except openerp.osv.orm.except_orm: - raise - except openerp.exceptions.AccessError: - raise - except openerp.exceptions.AccessDenied: - raise - except openerp.exceptions.Warning: - raise - except openerp.exceptions.RedirectWarning: - raise - except openerp.exceptions.DeferredException, e: - _logger.exception(tools.exception_to_unicode(e)) - post_mortem(e.traceback) - raise - except Exception, e: - _logger.exception(tools.exception_to_unicode(e)) - post_mortem(sys.exc_info()) - raise - -def post_mortem(info): - if tools.config['debug_mode'] and isinstance(info[2], types.TracebackType): - import pdb - pdb.post_mortem(info[2]) - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py index 506a07d0295..b31736ba998 100644 --- a/openerp/service/wsgi_server.py +++ b/openerp/service/wsgi_server.py @@ -72,7 +72,7 @@ def xmlrpc_return(start_response, service, method, params, string_faultcode=Fals # This also mimics SimpleXMLRPCDispatcher._marshaled_dispatch() for # exception handling. try: - result = openerp.netsvc.dispatch_rpc(service, method, params) + result = openerp.http.dispatch_rpc(service, method, params) response = xmlrpclib.dumps((result,), methodresponse=1, allow_none=False, encoding=None) except Exception, e: if string_faultcode: diff --git a/openerp/tools/__init__.py b/openerp/tools/__init__.py index 18dab088440..6ad2cca4724 100644 --- a/openerp/tools/__init__.py +++ b/openerp/tools/__init__.py @@ -34,7 +34,7 @@ from yaml_import import * from sql import * from float_utils import * from mail import * - +from debugger import * # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tools/debugger.py b/openerp/tools/debugger.py new file mode 100644 index 00000000000..293d4052513 --- /dev/null +++ b/openerp/tools/debugger.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright: 2014 - OpenERP S.A. +import types + +def post_mortem(config, info): + if config['debug_mode'] and isinstance(info[2], types.TracebackType): + import pdb + pdb.post_mortem(info[2]) From 3f779a5cae7bd4ca0399cf06e331f5047ec0e343 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 28 Feb 2014 17:10:55 +0100 Subject: [PATCH 10/86] [IMP] Add the ir_logging object where we will store the log messages (exception, error and warning) bzr revid: stw@openerp.com-20140228161055-98897xl1r46mf9mr --- openerp/addons/base/ir/__init__.py | 1 + openerp/addons/base/ir/ir_logging.py | 52 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 openerp/addons/base/ir/ir_logging.py diff --git a/openerp/addons/base/ir/__init__.py b/openerp/addons/base/ir/__init__.py index b3df5bcbccb..9aa1e7c42a2 100644 --- a/openerp/addons/base/ir/__init__.py +++ b/openerp/addons/base/ir/__init__.py @@ -39,6 +39,7 @@ import ir_mail_server import ir_fields import ir_qweb import ir_http +import ir_logging # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/base/ir/ir_logging.py b/openerp/addons/base/ir/ir_logging.py new file mode 100644 index 00000000000..66708aa729a --- /dev/null +++ b/openerp/addons/base/ir/ir_logging.py @@ -0,0 +1,52 @@ +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2014 OpenERP SA () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +import logging + +from openerp.osv import osv, fields +from openerp.tools.translate import _ + +class ir_logging(osv.Model): + _name = 'ir.logging' + + EXCEPTIONS_TYPE = [ + ('client', 'Client'), + ('server', 'Server') + ] + + _columns = { + 'name': fields.char('Name', required=True), + 'type': fields.selection(EXCEPTIONS_TYPE, string='Type', required=True, select=True), + 'dbname': fields.char('Database Name'), + 'level': fields.char('Level'), + 'message': fields.text('Message', required=True), + 'exception': fields.text('Exception'), + 'path': fields.char('Path', required=True), + 'func': fields.char('Function', required=True), + 'line': fields.char('Line', required=True), + } + + def call_function(self, cr, uid, ids, context=None): + logger = logging.getLogger() + logger.error("I think there is an error") + try: + raise Exception("I want to kill your process...") + except Exception, ex: + logger.exception("Please log me into the database") + return True \ No newline at end of file From 047d071a48630e3db2922d404d3d7f5395a88245 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 28 Feb 2014 17:11:47 +0100 Subject: [PATCH 11/86] [IMP] Add a new Logging Handler, where we will store in the database of OpenERP. this database can be overrided via the --log-pgsql-database bzr revid: stw@openerp.com-20140228161147-s9nnrfq2tc94vq5p --- openerp/http.py | 12 ++--- openerp/loggers/__init__.py | 1 + openerp/loggers/handlers.py | 100 ++++++++++++++++++++++++++++++++++++ openerp/netsvc.py | 51 ++++++++++++++---- openerp/tools/config.py | 24 ++++++--- 5 files changed, 162 insertions(+), 26 deletions(-) create mode 100644 openerp/loggers/__init__.py create mode 100644 openerp/loggers/handlers.py diff --git a/openerp/http.py b/openerp/http.py index ea1e82ed4c6..92fe03a6232 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -111,15 +111,9 @@ def dispatch_rpc(service_name, method, params): log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1) return result - except openerp.osv.orm.except_orm: - raise - except openerp.exceptions.AccessError: - raise - except openerp.exceptions.AccessDenied: - raise - except openerp.exceptions.Warning: - raise - except openerp.exceptions.RedirectWarning: + except (openerp.osv.orm.except_orm, openerp.exceptions.AccessError, \ + openerp.exceptions.AccessDenied, openerp.exceptions.Warning, \ + openerp.exceptions.RedirectWarning): raise except openerp.exceptions.DeferredException, e: _logger.exception(openerp.tools.exception_to_unicode(e)) diff --git a/openerp/loggers/__init__.py b/openerp/loggers/__init__.py new file mode 100644 index 00000000000..19b76b55b64 --- /dev/null +++ b/openerp/loggers/__init__.py @@ -0,0 +1 @@ +from handlers import PostgreSQLHandler \ No newline at end of file diff --git a/openerp/loggers/handlers.py b/openerp/loggers/handlers.py new file mode 100644 index 00000000000..763aa079195 --- /dev/null +++ b/openerp/loggers/handlers.py @@ -0,0 +1,100 @@ +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2014 OpenERP SA () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import contextlib +import datetime +import logging +import threading + +import psycopg2 + +from openerp import tools + +# The PostgreSQL Handler for the logging module, will be used by OpenERP to store the logs +# in the database, --log-pgsql-database=YOUR_DBNAME +# By default the system will use the current database + +class PostgreSQLHandler(logging.Handler): + @contextlib.contextmanager + def create_connection(self): + db_name = None + + db_name_from_cli = tools.config['log_pgsql_database'] + if not db_name_from_cli: + # If there is no database, and only in this case, we are going to use the database + # from the current thread and create a connection to this database. + + current_thread = threading.current_thread() + + db_name_from_thread = getattr(current_thread, 'dbname', None) + if isinstance(db_name_from_thread, basestring): + db_name = db_name_from_thread + else: + db_name = db_name_from_cli + + if not db_name: + return + + parameters = { + 'user': tools.config['db_user'] or None, + 'password': tools.config['db_password'] or None, + 'host': tools.config['db_host'] or None, + 'port': tools.config['db_port'] or None, + 'database': db_name, + } + try: + connection = psycopg2.connect(**parameters) + + if connection: + yield connection + except Exception, ex: # Use a specific exception + print ex + + def emit(self, record): + # We use a context manager to be tolerant to the errors (error of connections,...) + with self.create_connection() as conn: + exception = False + if record.exc_info: + exception = record.exc_text + + now = datetime.datetime.utcnow() + + current_thread = threading.current_thread() + uid = getattr(current_thread, 'uid', False) + dbname = getattr(current_thread, 'dbname', False) + + parameters = ( + now, uid, now, uid, 'server', dbname, record.name, + logging.getLevelName(record.levelno), record.msg, exception, + record.filename, record.funcName, record.lineno + ) + + with conn.cursor() as cursor: + cursor.execute(""" + INSERT INTO ir_logging( + create_date, create_uid, write_date, write_uid, + type, dbname, name, level, message, exception, path, func, + line + ) + VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """, + parameters + ) + conn.commit() diff --git a/openerp/netsvc.py b/openerp/netsvc.py index f19162cde5a..250cd52a1d1 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -29,6 +29,7 @@ import threading import tools import openerp +import openerp.loggers _logger = logging.getLogger(__name__) @@ -85,6 +86,19 @@ class ColoredFormatter(DBFormatter): record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname) return DBFormatter.format(self, record) +import platform +def is_posix_operating_system(): + return os.name == 'posix' + +def is_windows_operating_system(): + return os.name == 'nt' + +def is_linux_operating_system(): + return is_posix_operating_system() and platform.system() == 'Linux' + +def is_macosx_operating_system(): + return is_posix_operating_system() and platform.system() == 'Darwin' + def init_logger(): from tools.translate import resetlocale resetlocale() @@ -94,24 +108,32 @@ def init_logger(): if tools.config['syslog']: # SysLog Handler - if os.name == 'nt': + if is_windows_operating_system(): handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version)) - else: + elif is_linux_operating_system(): handler = logging.handlers.SysLogHandler('/dev/log') - format = '%s %s' % (release.description, release.version) \ - + ':%(dbname)s:%(levelname)s:%(name)s:%(message)s' + elif is_macosx_operating_system(): # There is no /dev/log on OSX + handler = logging.handlers.SysLogHandler('/var/run/log') + else: + raise Exception("There is no syslog handler for this Operating System: %s", platform.system()) + + format = '%s %s' % (release.description, release.version) + ':%(dbname)s:%(levelname)s:%(name)s:%(message)s' elif tools.config['logfile']: # LogFile Handler logf = tools.config['logfile'] try: + # We check we have the right location for the log files dirname = os.path.dirname(logf) if dirname and not os.path.isdir(dirname): os.makedirs(dirname) + if tools.config['logrotate'] is not False: - handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30) - elif os.name == 'posix': + handler = logging.handlers.TimedRotatingFileHandler(filename=logf, when='D', interval=1, backupCount=30) + + elif is_posix_operating_system(): handler = logging.handlers.WatchedFileHandler(logf) + else: handler = logging.handlers.FileHandler(logf) except Exception: @@ -125,12 +147,14 @@ def init_logger(): # behind Apache with mod_wsgi, handler.stream will have type mod_wsgi.Log, # which has no fileno() method. (mod_wsgi.Log is what is being bound to # sys.stderr when the logging.StreamHandler is being constructed above.) - if isinstance(handler, logging.StreamHandler) \ - and hasattr(handler.stream, 'fileno') \ - and os.isatty(handler.stream.fileno()): + def has_fileno(stream): + return hasattr(stream, 'fileno') and os.isatty(stream.fileno()) + + if isinstance(handler, logging.StreamHandler) and has_fileno(handler.stream): formatter = ColoredFormatter(format) else: formatter = DBFormatter(format) + handler.setFormatter(formatter) # Configure handlers @@ -141,7 +165,9 @@ def init_logger(): logging_configurations = DEFAULT_LOG_CONFIGURATION + pseudo_config + logconfig for logconfig_item in logging_configurations: loggername, level = logconfig_item.split(':') + level = getattr(logging, level, logging.INFO) + logger = logging.getLogger(loggername) logger.handlers = [] logger.setLevel(level) @@ -149,6 +175,13 @@ def init_logger(): if loggername != '': logger.propagate = False + # magic ;-) + # we manage the connection in the postgresqlhandler + postgresqlHandler = openerp.loggers.handlers.PostgreSQLHandler() + postgresqlHandler.setLevel(logging.WARNING) + logger = logging.getLogger() + logger.addHandler(postgresqlHandler) + for logconfig_item in logging_configurations: _logger.debug('logger level set: "%s"', logconfig_item) diff --git a/openerp/tools/config.py b/openerp/tools/config.py index 07435075f8d..ab1eab8a52a 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -72,9 +72,10 @@ class configmanager(object): } # Not exposed in the configuration file. - self.blacklist_for_save = set( - ['publisher_warranty_url', 'load_language', 'root_path', - 'init', 'save', 'config', 'update', 'stop_after_init']) + self.blacklist_for_save = set([ + 'publisher_warranty_url', 'load_language', 'root_path', + 'init', 'save', 'config', 'update', 'stop_after_init' + ]) # dictionary mapping option destination (keys in self.options) to MyOptions. self.casts = {} @@ -83,7 +84,10 @@ class configmanager(object): self.config_file = fname self.has_ssl = check_ssl() - self._LOGLEVELS = dict([(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x)) for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')]) + self._LOGLEVELS = dict([ + (getattr(loglevels, 'LOG_%s' % x), getattr(logging, x)) + for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET') + ]) version = "%s %s" % (release.description, release.version) self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption) @@ -176,12 +180,16 @@ class configmanager(object): group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.response:DEBUG') group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.http:DEBUG') group.add_option('--log-sql', action="append_const", dest="log_handler", const="openerp.sql_db:DEBUG", help='shortcut for --log-handler=openerp.sql_db:DEBUG') + group.add_option('--log-pgsql-database', dest='log_pgsql_database', help="database where OpenERP will store the exceptions", my_default=False) # For backward-compatibility, map the old log levels to something # quite close. - levels = ['info', 'debug_rpc', 'warn', 'test', 'critical', - 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset'] - group.add_option('--log-level', dest='log_level', type='choice', choices=levels, - my_default='info', help='specify the level of the logging. Accepted values: ' + str(levels) + ' (deprecated option).') + levels = [ + 'info', 'debug_rpc', 'warn', 'test', 'critical', + 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset' + ] + group.add_option('--log-level', dest='log_level', type='choice', + choices=levels, my_default='info', + help='specify the level of the logging. Accepted values: %s (deprecated option).' % (levels,)) parser.add_option_group(group) From cff80a9a0c6c3824b138db533665eb1bc1031bd4 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Mon, 10 Mar 2014 12:11:37 +0100 Subject: [PATCH 12/86] [IMP] Add views for the ir.logging object bzr revid: stw@openerp.com-20140310111137-04axbgdajd9jloxg --- openerp/addons/base/__openerp__.py | 1 + openerp/addons/base/ir/ir_logging_view.xml | 65 ++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 openerp/addons/base/ir/ir_logging_view.xml diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py index 3c3c290f986..57ce6b31e32 100644 --- a/openerp/addons/base/__openerp__.py +++ b/openerp/addons/base/__openerp__.py @@ -57,6 +57,7 @@ The kernel of OpenERP, needed for all installation. 'ir/ir_values_view.xml', 'ir/osv_memory_autovacuum.xml', 'ir/ir_model_report.xml', + 'ir/ir_logging_view.xml', 'workflow/workflow_view.xml', 'module/module_view.xml', 'module/module_data.xml', diff --git a/openerp/addons/base/ir/ir_logging_view.xml b/openerp/addons/base/ir/ir_logging_view.xml new file mode 100644 index 00000000000..d23e45f1d14 --- /dev/null +++ b/openerp/addons/base/ir/ir_logging_view.xml @@ -0,0 +1,65 @@ + + + + + ir.logging + +
+ + + + + + + + + + + +
+
+
+ + ir.logging + + + + + + + + + + + + + + ir.logging + + + + + + + + + + + + + + + + + + Logging + ir.logging + form + tree,form + + + + + +
+
\ No newline at end of file From d9fcc1604500b6df079cd40beba7c368be992372 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 11 Mar 2014 18:13:56 +0100 Subject: [PATCH 13/86] [FIX] ir.logging: Raise an internal exception in the case where there is no defined database, but this exception is catched by the _internal_emit method and just ignore it to avoid a crash. Better solution, try to log on the standard handler. bzr revid: stw@openerp.com-20140311171356-jghlh2w2pnxvtu2e --- openerp/loggers/handlers.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/openerp/loggers/handlers.py b/openerp/loggers/handlers.py index 763aa079195..24be46085c8 100644 --- a/openerp/loggers/handlers.py +++ b/openerp/loggers/handlers.py @@ -31,6 +31,9 @@ from openerp import tools # in the database, --log-pgsql-database=YOUR_DBNAME # By default the system will use the current database +class NoDatabaseError(Exception): + pass + class PostgreSQLHandler(logging.Handler): @contextlib.contextmanager def create_connection(self): @@ -50,7 +53,7 @@ class PostgreSQLHandler(logging.Handler): db_name = db_name_from_cli if not db_name: - return + raise NoDatabaseError("There is no defined database on this request") parameters = { 'user': tools.config['db_user'] or None, @@ -67,8 +70,7 @@ class PostgreSQLHandler(logging.Handler): except Exception, ex: # Use a specific exception print ex - def emit(self, record): - # We use a context manager to be tolerant to the errors (error of connections,...) + def _internal_emit(self, record): with self.create_connection() as conn: exception = False if record.exc_info: @@ -81,7 +83,7 @@ class PostgreSQLHandler(logging.Handler): dbname = getattr(current_thread, 'dbname', False) parameters = ( - now, uid, now, uid, 'server', dbname, record.name, + now, uid, now, uid, 'server', dbname, record.name, logging.getLevelName(record.levelno), record.msg, exception, record.filename, record.funcName, record.lineno ) @@ -89,12 +91,17 @@ class PostgreSQLHandler(logging.Handler): with conn.cursor() as cursor: cursor.execute(""" INSERT INTO ir_logging( - create_date, create_uid, write_date, write_uid, + create_date, create_uid, write_date, write_uid, type, dbname, name, level, message, exception, path, func, line ) VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, - parameters - ) + """, parameters) conn.commit() + + def emit(self, record): + # We use a context manager to be tolerant to the errors (error of connections,...) + try: + self._internal_emit(record) + except NoDatabaseError: + pass From 27405c0c83b05e9239eb76550004732776fc5e6a Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Mar 2014 21:52:20 +0100 Subject: [PATCH 14/86] [FIX] ensure_db() werkzeug.BaseResponse.url usage leads to encoded hashes loss bzr revid: fme@openerp.com-20140311205220-kk0pal10lodurcst --- addons/web/controllers/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index d13226ca731..0074a88ba9c 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -141,7 +141,12 @@ def ensure_db(redirect='/web/database/selector'): # Thus, we redirect the user to the same page but with the session cookie set. # This will force using the database route dispatcher... r = request.httprequest - response = werkzeug.utils.redirect(r.url, 302) + url_redirect = r.base_url + if r.query_string: + # Can't use werkzeug.wrappers.BaseRequest.url with encoded hashes: + # https://github.com/amigrave/werkzeug/commit/b4a62433f2f7678c234cdcac6247a869f90a7eb7 + url_redirect += '?' + r.query_string + response = werkzeug.utils.redirect(url_redirect, 302) request.session.db = db response = r.app.get_response(r, response, explicit_session=False) werkzeug.exceptions.abort(response) From 9684f077358091c0eccf30abb2dfecd83fb0eb4a Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 11 Mar 2014 21:53:37 +0100 Subject: [PATCH 15/86] [FIX] signup_url_for_action(), use `redirect` url parameter and bring back `action` in fragment bzr revid: fme@openerp.com-20140311205337-9vasnqx5cudbk3f3 --- addons/auth_signup/res_users.py | 11 ++++++++--- addons/portal/mail_mail.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py index e91af657cc2..32aa22b2744 100644 --- a/addons/auth_signup/res_users.py +++ b/addons/auth_signup/res_users.py @@ -20,8 +20,8 @@ ############################################################################## from datetime import datetime, timedelta import random -from urllib import urlencode from urlparse import urljoin +import werkzeug from openerp.addons.base.ir.ir_mail_server import MailDeliveryException from openerp.osv import osv, fields @@ -53,7 +53,7 @@ class res_partner(osv.Model): (not partner.signup_expiration or dt <= partner.signup_expiration) return res - def _get_signup_url_for_action(self, cr, uid, ids, action='login', view_type=None, menu_id=None, res_id=None, model=None, context=None): + def _get_signup_url_for_action(self, cr, uid, ids, action=None, view_type=None, menu_id=None, res_id=None, model=None, context=None): """ generate a signup url for the given partner ids and action, possibly overriding the url state components (menu_id, id, view_type) """ if context is None: @@ -80,6 +80,8 @@ class res_partner(osv.Model): continue # no signup token, no user, thus no signup url! fragment = dict() + if action: + fragment['action'] = action if view_type: fragment['view_type'] = view_type if menu_id: @@ -89,7 +91,10 @@ class res_partner(osv.Model): if res_id: fragment['id'] = res_id - res[partner.id] = urljoin(base_url, "/web/login?%s#%s" % (urlencode(query), urlencode(fragment))) + if fragment: + query['redirect'] = '/web#' + werkzeug.url_encode(fragment) + + res[partner.id] = urljoin(base_url, "/web/login?%s" % werkzeug.url_encode(query)) return res diff --git a/addons/portal/mail_mail.py b/addons/portal/mail_mail.py index 18eb15361a3..0fe2081fa45 100644 --- a/addons/portal/mail_mail.py +++ b/addons/portal/mail_mail.py @@ -39,7 +39,7 @@ class mail_mail(osv.Model): if partner and not partner.user_ids: contex_signup = dict(context, signup_valid=True) signup_url = partner_obj._get_signup_url_for_action(cr, SUPERUSER_ID, [partner.id], - action='login', model=mail.model, res_id=mail.res_id, + model=mail.model, res_id=mail.res_id, context=contex_signup)[partner.id] return _("""Access your messages and documents through our Customer Portal""") % signup_url else: From cf68961f668b058952b205b5ba7991ea8c5f6b8b Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 12 Mar 2014 11:32:39 +0100 Subject: [PATCH 16/86] [IMP] /web/login redirects if already logged in bzr revid: fme@openerp.com-20140312103239-deyoc7t00g8kusd1 --- addons/web/controllers/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 0074a88ba9c..c30cd052bd6 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -663,6 +663,9 @@ class Home(http.Controller): def web_login(self, redirect=None, **kw): ensure_db() + if request.httprequest.method == 'GET' and redirect and request.session.uid: + return http.redirect_with_hash(redirect) + values = request.params.copy() if not redirect: redirect = '/web?' + request.httprequest.query_string From 2ed053642abfbf20678c02955b8b0bb575cf8d30 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 12 Mar 2014 12:21:41 +0100 Subject: [PATCH 17/86] [IMP] oaut & signup, redirect if already logged in bzr revid: fme@openerp.com-20140312112141-uvo89w4ovmzq5ozq --- addons/auth_oauth/controllers/main.py | 3 +++ addons/auth_signup/controllers/main.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index 53a6ffd5462..a338927c3a1 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -68,6 +68,9 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home): @http.route() def web_login(self, *args, **kw): + if request.httprequest.method == 'GET' and request.session.uid and request.params.get('redirect'): + # Redirect if already logged in and redirect param is present + return http.redirect_with_hash(request.params.get('redirect')) providers = self.list_providers() response = super(OAuthLogin, self).web_login(*args, **kw) diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 08b67108c3f..92ae6364954 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -37,6 +37,9 @@ class AuthSignup(openerp.addons.web.controllers.main.Home): mode = request.params.get('mode') qcontext = request.params.copy() super_response = None + if request.httprequest.method == 'GET' and request.session.uid and request.params.get('redirect'): + # Redirect if already logged in and redirect param is present + return http.redirect_with_hash(request.params.get('redirect')) if request.httprequest.method != 'POST' or mode not in ('reset', 'signup'): # Default behavior is to try to login, which in reset or signup mode in a non-sense. super_response = super(AuthSignup, self).web_login(*args, **kw) From 77e56eea05948e8bde8a42f6cdf1f5a2e10085ae Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Thu, 13 Mar 2014 14:34:59 +0100 Subject: [PATCH 18/86] [REF] Changed seek(0) to flush when manipulating files and reordered the impors bzr revid: sle@openerp.com-20140313133459-y3zvhhjby8pysew1 --- addons/report/controllers/main.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/addons/report/controllers/main.py b/addons/report/controllers/main.py index 0dae5f41288..e057e80beb7 100644 --- a/addons/report/controllers/main.py +++ b/addons/report/controllers/main.py @@ -41,20 +41,20 @@ import signal import os from distutils.version import LooseVersion - from werkzeug import exceptions from werkzeug.test import Client from werkzeug.wrappers import BaseResponse from werkzeug.datastructures import Headers from reportlab.graphics.barcode import createBarcodeDrawing - - -_logger = logging.getLogger(__name__) try: from pyPdf import PdfFileWriter, PdfFileReader except ImportError: PdfFileWriter = PdfFileReader = None + +_logger = logging.getLogger(__name__) + + class Report(http.Controller): @http.route(['/report//'], type='http', auth='user', website=True, multilang=True) @@ -303,7 +303,7 @@ class Report(http.Controller): # Directly load the document if we have it if save_in_attachment and save_in_attachment['loaded_documents'].get(reporthtml[0]): pdfreport.write(save_in_attachment['loaded_documents'].get(reporthtml[0])) - pdfreport.seek(0) + pdfreport.flush() pdfdocuments.append(pdfreport) continue @@ -312,7 +312,7 @@ class Report(http.Controller): head_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.header.tmp.', dir=tmp_dir, mode='w+') head_file.write(headers[index]) - head_file.seek(0) + head_file.flush() command_arg_local.extend(['--header-html', head_file.name]) # Footer stuff @@ -320,14 +320,14 @@ class Report(http.Controller): foot_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.footer.tmp.', dir=tmp_dir, mode='w+') foot_file.write(footers[index]) - foot_file.seek(0) + foot_file.flush() command_arg_local.extend(['--footer-html', foot_file.name]) # Body stuff content_file = tempfile.NamedTemporaryFile(suffix='.html', prefix='report.body.tmp.', dir=tmp_dir, mode='w+') content_file.write(reporthtml[1]) - content_file.seek(0) + content_file.flush() try: # If the server is running with only one worker, increase it to two to be able @@ -364,7 +364,7 @@ class Report(http.Controller): _logger.info('The PDF document %s is now saved in the ' 'database' % attachment['name']) - pdfreport.seek(0) + pdfreport.flush() pdfdocuments.append(pdfreport) if headers: @@ -461,7 +461,7 @@ class Report(http.Controller): document.close() merged = StringIO.StringIO() writer.write(merged) - merged.seek(0) + merged.flush() content = merged.read() merged.close() return content From e9d047e6119d8065a8d31a7d3b374630ab90c40d Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 16 Mar 2014 19:29:33 +0100 Subject: [PATCH 19/86] sql loggin cleanups - prepare for netsvc will be renamed to logging - move back log from http.py has it's used by the cron - move sql handler to netsvc, simplify to use sql_db - remove unused handler - close plaform specific #ifdef pandora's box bzr revid: al@openerp.com-20140316182933-jkcji9yqfbsokcmg --- openerp/http.py | 16 +----- openerp/loggers/__init__.py | 1 - openerp/loggers/handlers.py | 107 ------------------------------------ openerp/netsvc.py | 97 +++++++++++++++++--------------- openerp/tools/config.py | 4 +- 5 files changed, 58 insertions(+), 167 deletions(-) delete mode 100644 openerp/loggers/__init__.py delete mode 100644 openerp/loggers/handlers.py diff --git a/openerp/http.py b/openerp/http.py index ac5ad3495da..b6e87e26f81 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -21,7 +21,6 @@ import time import traceback import urlparse import warnings -from pprint import pformat import babel.core import psutil @@ -36,9 +35,7 @@ import werkzeug.wrappers import werkzeug.wsgi import openerp -import openerp.netsvc from openerp.service import security, model as service_model -import openerp.tools _logger = logging.getLogger(__name__) @@ -61,13 +58,6 @@ def replace_request_password(args): args[2] = '*' return tuple(args) -def log(logger, level, prefix, msg, depth=None): - indent='' - indent_after=' '*len(prefix) - for line in (prefix+pformat(msg, depth=depth)).split('\n'): - logger.log(level, indent+line) - indent=indent_after - def dispatch_rpc(service_name, method, params): """ Handle a RPC call. @@ -84,7 +74,7 @@ def dispatch_rpc(service_name, method, params): start_rss, start_vms = 0, 0 start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info() if rpc_request and rpc_response_flag: - log(rpc_request, logging.DEBUG, '%s.%s' % (service_name, method), replace_request_password(params)) + openerp.netsvc.log(rpc_request, logging.DEBUG, '%s.%s' % (service_name, method), replace_request_password(params)) threading.current_thread().uid = None threading.current_thread().dbname = None @@ -106,9 +96,9 @@ def dispatch_rpc(service_name, method, params): end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info() logline = '%s.%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (service_name, method, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024) if rpc_response_flag: - log(rpc_response, logging.DEBUG, logline, result) + openerp.netsvc.log(rpc_response, logging.DEBUG, logline, result) else: - log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1) + openerp.netsvc.log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1) return result except (openerp.osv.orm.except_orm, openerp.exceptions.AccessError, \ diff --git a/openerp/loggers/__init__.py b/openerp/loggers/__init__.py deleted file mode 100644 index 19b76b55b64..00000000000 --- a/openerp/loggers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from handlers import PostgreSQLHandler \ No newline at end of file diff --git a/openerp/loggers/handlers.py b/openerp/loggers/handlers.py deleted file mode 100644 index 24be46085c8..00000000000 --- a/openerp/loggers/handlers.py +++ /dev/null @@ -1,107 +0,0 @@ -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2014 OpenERP SA () -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import contextlib -import datetime -import logging -import threading - -import psycopg2 - -from openerp import tools - -# The PostgreSQL Handler for the logging module, will be used by OpenERP to store the logs -# in the database, --log-pgsql-database=YOUR_DBNAME -# By default the system will use the current database - -class NoDatabaseError(Exception): - pass - -class PostgreSQLHandler(logging.Handler): - @contextlib.contextmanager - def create_connection(self): - db_name = None - - db_name_from_cli = tools.config['log_pgsql_database'] - if not db_name_from_cli: - # If there is no database, and only in this case, we are going to use the database - # from the current thread and create a connection to this database. - - current_thread = threading.current_thread() - - db_name_from_thread = getattr(current_thread, 'dbname', None) - if isinstance(db_name_from_thread, basestring): - db_name = db_name_from_thread - else: - db_name = db_name_from_cli - - if not db_name: - raise NoDatabaseError("There is no defined database on this request") - - parameters = { - 'user': tools.config['db_user'] or None, - 'password': tools.config['db_password'] or None, - 'host': tools.config['db_host'] or None, - 'port': tools.config['db_port'] or None, - 'database': db_name, - } - try: - connection = psycopg2.connect(**parameters) - - if connection: - yield connection - except Exception, ex: # Use a specific exception - print ex - - def _internal_emit(self, record): - with self.create_connection() as conn: - exception = False - if record.exc_info: - exception = record.exc_text - - now = datetime.datetime.utcnow() - - current_thread = threading.current_thread() - uid = getattr(current_thread, 'uid', False) - dbname = getattr(current_thread, 'dbname', False) - - parameters = ( - now, uid, now, uid, 'server', dbname, record.name, - logging.getLevelName(record.levelno), record.msg, exception, - record.filename, record.funcName, record.lineno - ) - - with conn.cursor() as cursor: - cursor.execute(""" - INSERT INTO ir_logging( - create_date, create_uid, write_date, write_uid, - type, dbname, name, level, message, exception, path, func, - line - ) - VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, parameters) - conn.commit() - - def emit(self, record): - # We use a context manager to be tolerant to the errors (error of connections,...) - try: - self._internal_emit(record) - except NoDatabaseError: - pass diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 250cd52a1d1..a2c8f687f0f 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -19,20 +19,32 @@ # ############################################################################## - +import contextlib import logging import logging.handlers import os +import platform import release import sys import threading +from pprint import pformat + +import psycopg2 import tools import openerp -import openerp.loggers +import sql_db + _logger = logging.getLogger(__name__) +def log(logger, level, prefix, msg, depth=None): + indent='' + indent_after=' '*len(prefix) + for line in (prefix+pformat(msg, depth=depth)).split('\n'): + logger.log(level, indent+line) + indent=indent_after + def LocalService(name): """ The openerp.netsvc.LocalService() function is deprecated. It still works @@ -59,6 +71,38 @@ def LocalService(name): with registry.cursor() as cr: return registry['ir.actions.report.xml']._lookup_report(cr, name[len('report.'):]) +class PostgreSQLHandler(logging.Handler): + """ PostgreSQL Loggin Handler will store logs in the database, by default + the current database, can be set using --log-db=DBNAME + """ + def emit(self, record): + print "Emit PG", record + ct = threading.current_thread() + ct_db = getattr(ct, 'dbname') + ct_uid = getattr(ct, 'uid') + dbname = tools.config['log_db'] or ct_db + if dbname: + cr = None + try: + cr = sql_db.db_connect(dbname).cursor() + exception = False + if record.exc_info: + exception = record.exc_text + level = logging.getLevelName(record.levelno) + val = (uid, uid, 'server', dbname, record.name, level, record.msg, exception, record.filename, record.funcName, record.lineno) + cr.execute(""" + INSERT INTO ir_logging(create_date, write_date, create_uid, write_uid, type, dbname, name, level, message, exception, path, func, line) + VALUES (NOW() at time zone 'UTC', NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """, val ) + cr.commit() + except Exception, e: + print "Exception",e + print repr(e) + pass + finally: + if cr: + cr.close() + BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10) #The background is set with 40 plus the number of the color, and the foreground with 30 #These are the sequences need to get colored ouput @@ -86,19 +130,6 @@ class ColoredFormatter(DBFormatter): record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname) return DBFormatter.format(self, record) -import platform -def is_posix_operating_system(): - return os.name == 'posix' - -def is_windows_operating_system(): - return os.name == 'nt' - -def is_linux_operating_system(): - return is_posix_operating_system() and platform.system() == 'Linux' - -def is_macosx_operating_system(): - return is_posix_operating_system() and platform.system() == 'Darwin' - def init_logger(): from tools.translate import resetlocale resetlocale() @@ -108,15 +139,10 @@ def init_logger(): if tools.config['syslog']: # SysLog Handler - if is_windows_operating_system(): + if os.name == 'nt': handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version)) - elif is_linux_operating_system(): - handler = logging.handlers.SysLogHandler('/dev/log') - elif is_macosx_operating_system(): # There is no /dev/log on OSX - handler = logging.handlers.SysLogHandler('/var/run/log') else: - raise Exception("There is no syslog handler for this Operating System: %s", platform.system()) - + handler = logging.handlers.SysLogHandler() format = '%s %s' % (release.description, release.version) + ':%(dbname)s:%(levelname)s:%(name)s:%(message)s' elif tools.config['logfile']: @@ -130,12 +156,11 @@ def init_logger(): if tools.config['logrotate'] is not False: handler = logging.handlers.TimedRotatingFileHandler(filename=logf, when='D', interval=1, backupCount=30) - - elif is_posix_operating_system(): + elif os.name == 'posix': handler = logging.handlers.WatchedFileHandler(logf) - else: handler = logging.handlers.FileHandler(logf) + except Exception: sys.stderr.write("ERROR: couldn't create the logfile directory. Logging to the standard output.\n") handler = logging.StreamHandler(sys.stdout) @@ -147,10 +172,10 @@ def init_logger(): # behind Apache with mod_wsgi, handler.stream will have type mod_wsgi.Log, # which has no fileno() method. (mod_wsgi.Log is what is being bound to # sys.stderr when the logging.StreamHandler is being constructed above.) - def has_fileno(stream): + def is_a_tty(stream): return hasattr(stream, 'fileno') and os.isatty(stream.fileno()) - if isinstance(handler, logging.StreamHandler) and has_fileno(handler.stream): + if isinstance(handler, logging.StreamHandler) and is_a_tty(handler.stream): formatter = ColoredFormatter(format) else: formatter = DBFormatter(format) @@ -165,9 +190,7 @@ def init_logger(): logging_configurations = DEFAULT_LOG_CONFIGURATION + pseudo_config + logconfig for logconfig_item in logging_configurations: loggername, level = logconfig_item.split(':') - level = getattr(logging, level, logging.INFO) - logger = logging.getLogger(loggername) logger.handlers = [] logger.setLevel(level) @@ -175,9 +198,8 @@ def init_logger(): if loggername != '': logger.propagate = False - # magic ;-) # we manage the connection in the postgresqlhandler - postgresqlHandler = openerp.loggers.handlers.PostgreSQLHandler() + postgresqlHandler = PostgreSQLHandler() postgresqlHandler.setLevel(logging.WARNING) logger = logging.getLogger() logger.addHandler(postgresqlHandler) @@ -204,17 +226,4 @@ PSEUDOCONFIG_MAPPER = { 'critical': ['openerp:CRITICAL'], } -# A alternative logging scheme for automated runs of the -# server intended to test it. -def init_alternative_logger(): - class H(logging.Handler): - def emit(self, record): - if record.levelno > 20: - print record.levelno, record.pathname, record.msg - handler = H() - # Add the handler to the 'openerp' logger. - logger = logging.getLogger('openerp') - logger.addHandler(handler) - logger.setLevel(logging.ERROR) - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tools/config.py b/openerp/tools/config.py index 998e3163355..8963720fda0 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +#openerp.loggers.handlers. -*- coding: utf-8 -*- ############################################################################## # # OpenERP, Open Source Management Solution @@ -187,7 +187,7 @@ class configmanager(object): group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.response:DEBUG') group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.http:DEBUG') group.add_option('--log-sql', action="append_const", dest="log_handler", const="openerp.sql_db:DEBUG", help='shortcut for --log-handler=openerp.sql_db:DEBUG') - group.add_option('--log-pgsql-database', dest='log_pgsql_database', help="database where OpenERP will store the exceptions", my_default=False) + group.add_option('--log-db', dest='log_db', help="database where OpenERP will store the exceptions", my_default=False) # For backward-compatibility, map the old log levels to something # quite close. levels = [ From eaacd8a4d88877008edf7800357e55a62c9e0a76 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 16 Mar 2014 20:16:22 +0100 Subject: [PATCH 20/86] ir_logging make it work, and make it usable bzr revid: al@openerp.com-20140316191622-l59vkjeu2e8nbh75 --- openerp/addons/base/ir/ir_logging.py | 11 ++------ openerp/addons/base/ir/ir_logging_view.xml | 31 ++++++++++----------- openerp/netsvc.py | 32 ++++++++++------------ 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/openerp/addons/base/ir/ir_logging.py b/openerp/addons/base/ir/ir_logging.py index 66708aa729a..a2b4e606290 100644 --- a/openerp/addons/base/ir/ir_logging.py +++ b/openerp/addons/base/ir/ir_logging.py @@ -24,6 +24,7 @@ from openerp.tools.translate import _ class ir_logging(osv.Model): _name = 'ir.logging' + _order = 'id DESC' EXCEPTIONS_TYPE = [ ('client', 'Client'), @@ -31,22 +32,14 @@ class ir_logging(osv.Model): ] _columns = { + 'create_date': fields.datetime('Create Date', readonly=True), 'name': fields.char('Name', required=True), 'type': fields.selection(EXCEPTIONS_TYPE, string='Type', required=True, select=True), 'dbname': fields.char('Database Name'), 'level': fields.char('Level'), 'message': fields.text('Message', required=True), - 'exception': fields.text('Exception'), 'path': fields.char('Path', required=True), 'func': fields.char('Function', required=True), 'line': fields.char('Line', required=True), } - def call_function(self, cr, uid, ids, context=None): - logger = logging.getLogger() - logger.error("I think there is an error") - try: - raise Exception("I want to kill your process...") - except Exception, ex: - logger.exception("Please log me into the database") - return True \ No newline at end of file diff --git a/openerp/addons/base/ir/ir_logging_view.xml b/openerp/addons/base/ir/ir_logging_view.xml index d23e45f1d14..1fa5170387a 100644 --- a/openerp/addons/base/ir/ir_logging_view.xml +++ b/openerp/addons/base/ir/ir_logging_view.xml @@ -5,17 +5,17 @@ ir.logging
- - - + + + + - - - - + + + @@ -23,13 +23,14 @@ ir.logging - - + + + - + @@ -37,11 +38,11 @@ ir.logging - - - + + + @@ -50,7 +51,6 @@ - Logging ir.logging @@ -58,8 +58,7 @@ tree,form - - \ No newline at end of file + diff --git a/openerp/netsvc.py b/openerp/netsvc.py index a2c8f687f0f..af45301dbb4 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -76,28 +76,26 @@ class PostgreSQLHandler(logging.Handler): the current database, can be set using --log-db=DBNAME """ def emit(self, record): - print "Emit PG", record ct = threading.current_thread() - ct_db = getattr(ct, 'dbname') - ct_uid = getattr(ct, 'uid') + ct_db = getattr(ct, 'dbname', None) + ct_uid = getattr(ct, 'uid', None) dbname = tools.config['log_db'] or ct_db if dbname: cr = None try: cr = sql_db.db_connect(dbname).cursor() - exception = False - if record.exc_info: - exception = record.exc_text + msg = record.msg + traceback = getattr(record, 'exc_text', '') + if traceback: + msg = "%s\n%s" % (msg, traceback) level = logging.getLevelName(record.levelno) - val = (uid, uid, 'server', dbname, record.name, level, record.msg, exception, record.filename, record.funcName, record.lineno) + val = (ct_uid, ct_uid, 'server', dbname, record.name, level, msg, record.pathname, record.lineno, record.funcName) cr.execute(""" - INSERT INTO ir_logging(create_date, write_date, create_uid, write_uid, type, dbname, name, level, message, exception, path, func, line) - VALUES (NOW() at time zone 'UTC', NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + INSERT INTO ir_logging(create_date, write_date, create_uid, write_uid, type, dbname, name, level, message, path, line, func) + VALUES (NOW() at time zone 'UTC', NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """, val ) cr.commit() except Exception, e: - print "Exception",e - print repr(e) pass finally: if cr: @@ -187,6 +185,9 @@ def init_logger(): logconfig = tools.config['log_handler'] + postgresqlHandler = PostgreSQLHandler() + postgresqlHandler.setLevel(logging.WARNING) + logging_configurations = DEFAULT_LOG_CONFIGURATION + pseudo_config + logconfig for logconfig_item in logging_configurations: loggername, level = logconfig_item.split(':') @@ -195,18 +196,15 @@ def init_logger(): logger.handlers = [] logger.setLevel(level) logger.addHandler(handler) + logger.addHandler(postgresqlHandler) if loggername != '': logger.propagate = False - # we manage the connection in the postgresqlhandler - postgresqlHandler = PostgreSQLHandler() - postgresqlHandler.setLevel(logging.WARNING) - logger = logging.getLogger() - logger.addHandler(postgresqlHandler) - for logconfig_item in logging_configurations: _logger.debug('logger level set: "%s"', logconfig_item) + + DEFAULT_LOG_CONFIGURATION = [ 'openerp.workflow.workitem:WARNING', 'openerp.netsvc.rpc.request:INFO', From 58fb28746a6f7921f4334694a37d31dd6bb376a6 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 16 Mar 2014 20:22:23 +0100 Subject: [PATCH 21/86] ir_logging, cleanup and minimize merge diff bzr revid: al@openerp.com-20140316192223-gfxzbo83j5hpm527 --- openerp/netsvc.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/openerp/netsvc.py b/openerp/netsvc.py index af45301dbb4..579f495d56b 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -19,11 +19,9 @@ # ############################################################################## -import contextlib import logging import logging.handlers import os -import platform import release import sys import threading @@ -35,7 +33,6 @@ import tools import openerp import sql_db - _logger = logging.getLogger(__name__) def log(logger, level, prefix, msg, depth=None): @@ -141,7 +138,8 @@ def init_logger(): handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version)) else: handler = logging.handlers.SysLogHandler() - format = '%s %s' % (release.description, release.version) + ':%(dbname)s:%(levelname)s:%(name)s:%(message)s' + format = '%s %s' % (release.description, release.version) \ + + ':%(dbname)s:%(levelname)s:%(name)s:%(message)s' elif tools.config['logfile']: # LogFile Handler @@ -151,14 +149,12 @@ def init_logger(): dirname = os.path.dirname(logf) if dirname and not os.path.isdir(dirname): os.makedirs(dirname) - if tools.config['logrotate'] is not False: handler = logging.handlers.TimedRotatingFileHandler(filename=logf, when='D', interval=1, backupCount=30) elif os.name == 'posix': handler = logging.handlers.WatchedFileHandler(logf) else: handler = logging.handlers.FileHandler(logf) - except Exception: sys.stderr.write("ERROR: couldn't create the logfile directory. Logging to the standard output.\n") handler = logging.StreamHandler(sys.stdout) @@ -177,7 +173,6 @@ def init_logger(): formatter = ColoredFormatter(format) else: formatter = DBFormatter(format) - handler.setFormatter(formatter) # Configure handlers @@ -203,8 +198,6 @@ def init_logger(): for logconfig_item in logging_configurations: _logger.debug('logger level set: "%s"', logconfig_item) - - DEFAULT_LOG_CONFIGURATION = [ 'openerp.workflow.workitem:WARNING', 'openerp.netsvc.rpc.request:INFO', From 99a89152b4b21ab4eee46408ccaffb9fb7db619d Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 16 Mar 2014 20:29:37 +0100 Subject: [PATCH 22/86] ir_logging, improve search view bzr revid: al@openerp.com-20140316192937-q1rxct0hhlwpm2t0 --- openerp/addons/base/ir/ir_logging_view.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openerp/addons/base/ir/ir_logging_view.xml b/openerp/addons/base/ir/ir_logging_view.xml index 1fa5170387a..afc7a07f3aa 100644 --- a/openerp/addons/base/ir/ir_logging_view.xml +++ b/openerp/addons/base/ir/ir_logging_view.xml @@ -47,6 +47,8 @@ + +
From aa7d42acfc43465049665405dfec81d185e23403 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 16 Mar 2014 20:43:28 +0100 Subject: [PATCH 23/86] ir_logging fix msg, add uid in form view bzr revid: al@openerp.com-20140316194328-3tooio3km8ev26wj --- openerp/addons/base/ir/ir_logging_view.xml | 2 +- openerp/netsvc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/addons/base/ir/ir_logging_view.xml b/openerp/addons/base/ir/ir_logging_view.xml index afc7a07f3aa..f963fc0d393 100644 --- a/openerp/addons/base/ir/ir_logging_view.xml +++ b/openerp/addons/base/ir/ir_logging_view.xml @@ -24,6 +24,7 @@ + @@ -61,6 +62,5 @@ - diff --git a/openerp/netsvc.py b/openerp/netsvc.py index 579f495d56b..3de345a0adf 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -81,7 +81,7 @@ class PostgreSQLHandler(logging.Handler): cr = None try: cr = sql_db.db_connect(dbname).cursor() - msg = record.msg + msg = unicode(record.msg) traceback = getattr(record, 'exc_text', '') if traceback: msg = "%s\n%s" % (msg, traceback) From e6a5d82036a53ff3d4bdb8aed6f754e6c5b56a63 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Mon, 17 Mar 2014 01:08:51 +0100 Subject: [PATCH 24/86] [FIX] ir_logging missing field bzr revid: al@openerp.com-20140317000851-3vyo7m3kc9k8jki5 --- openerp/addons/base/ir/ir_logging.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/addons/base/ir/ir_logging.py b/openerp/addons/base/ir/ir_logging.py index a2b4e606290..dd06a19c0ae 100644 --- a/openerp/addons/base/ir/ir_logging.py +++ b/openerp/addons/base/ir/ir_logging.py @@ -33,6 +33,7 @@ class ir_logging(osv.Model): _columns = { 'create_date': fields.datetime('Create Date', readonly=True), + 'create_uid': fields.integer('Uid', readonly=True), # Integer not m2o is intentionnal 'name': fields.char('Name', required=True), 'type': fields.selection(EXCEPTIONS_TYPE, string='Type', required=True, select=True), 'dbname': fields.char('Database Name'), From ce1b0ad243afd9f7251676a7a22571a232a73c3b Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Tue, 18 Mar 2014 01:29:54 +0100 Subject: [PATCH 25/86] [FIX] ir_logging acl bzr revid: al@openerp.com-20140318002954-3vc2wj6llc2017bu --- openerp/addons/base/security/ir.model.access.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index 25df6e117d0..6113f17c5aa 100644 --- a/openerp/addons/base/security/ir.model.access.csv +++ b/openerp/addons/base/security/ir.model.access.csv @@ -113,3 +113,4 @@ "access_ir_needaction_mixin","ir_needaction_mixin","model_ir_needaction_mixin",,1,1,1,1 "access_res_font_all","res_res_font all","model_res_font",,1,0,0,0 "access_res_font_group_user","res_res_font group_user","model_res_font","group_user",1,1,1,1 +"access_ir_logging","ir_logging admin","model_ir_logging","group_erp_manager",1,1,1,1 From f2b51c7b69910e7962715f33fc4f89110b057b46 Mon Sep 17 00:00:00 2001 From: "ajay javiya (OpenERP)" Date: Tue, 18 Mar 2014 14:05:59 +0530 Subject: [PATCH 26/86] [FIX] : subscribe/unsubscribe button not work [IMP]: layout bzr revid: aja@tinyerp.com-20140318083559-jwc196mypc67p5x4 --- addons/website_mail/static/src/js/website_mail.js | 3 +-- addons/website_mail_group/controllers/main.py | 2 +- addons/website_mail_group/views/website_mail_group.xml | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/addons/website_mail/static/src/js/website_mail.js b/addons/website_mail/static/src/js/website_mail.js index 672a21324a8..5fac61c3d36 100644 --- a/addons/website_mail/static/src/js/website_mail.js +++ b/addons/website_mail/static/src/js/website_mail.js @@ -39,8 +39,7 @@ return false; } this.$target.removeClass('has-error'); - - openerp.jsonRpc('/website_mail/follow', 'call', { + openerp.jsonRpc('/website_mail/follow/', 'call', { 'id': +this.$target.data('id'), 'object': this.$target.data('object'), 'message_is_follower': this.$target.attr("data-follow") || "off", diff --git a/addons/website_mail_group/controllers/main.py b/addons/website_mail_group/controllers/main.py index a6f3adcd522..fe133434abc 100644 --- a/addons/website_mail_group/controllers/main.py +++ b/addons/website_mail_group/controllers/main.py @@ -59,7 +59,7 @@ class MailGroup(http.Controller): domain.append(('parent_id','=',False)) thread_count = thread_obj.search_count(cr, uid, domain, context=context) pager = request.website.pager( - url='/groups/%s/%s' % (group.id, mode), + url='/groups/%s/%s/' % (group.id, mode), total=thread_count, page=page, step=self._thread_per_page, diff --git a/addons/website_mail_group/views/website_mail_group.xml b/addons/website_mail_group/views/website_mail_group.xml index 9e4b41329b4..fb18ee9f3fd 100644 --- a/addons/website_mail_group/views/website_mail_group.xml +++ b/addons/website_mail_group/views/website_mail_group.xml @@ -45,13 +45,13 @@
-
-
From e50b3bf7fffd61a632f757970bd23f28417d4bd3 Mon Sep 17 00:00:00 2001 From: Kersten Jeremy Date: Tue, 18 Mar 2014 17:56:22 +0100 Subject: [PATCH 28/86] [FIX] Only set table-row for kanban view (for drag and drop), Else the switch between eg view Calendar and view form (without pass by another view (on IE)) does not work properly. lp bug: https://launchpad.net/bugs/1294059 fixed lp bug: https://launchpad.net/bugs/1097219 fixed bzr revid: jke@openerp.com-20140318165622-kb4j5zz899lciz8v --- addons/web/static/src/css/base.css | 4 +++- addons/web/static/src/css/base.sass | 4 +++- addons/web/static/src/js/views.js | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 6a20442ed41..8c2da02d969 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1248,12 +1248,14 @@ width: 100%; } .openerp .oe_view_manager .oe_view_manager_body { - display: table-row; height: inherit; } .openerp .oe_view_manager .oe_view_manager_view_kanban:not(:empty) { height: inherit; } +.openerp .oe_view_manager[data-view-type=kanban] .oe_view_manager_body { + display: table-row; +} .openerp .oe_view_manager table.oe_view_manager_header { border-collapse: separate; width: 100%; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 38a47eed1c5..b18e4fc65d8 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1037,10 +1037,12 @@ $sheet-padding: 16px height: inherit width: 100% .oe_view_manager_body - display: table-row height: inherit .oe_view_manager_view_kanban:not(:empty) height: inherit + &[data-view-type=kanban] + .oe_view_manager_body + display: table-row table.oe_view_manager_header border-collapse: separate diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 23390df4bb4..5f85b581cea 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -644,7 +644,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.$el .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]') .parent().addClass('active'); - + this.$el.attr("data-view-type", view_type); return $.when(view_promise).done(function () { _.each(_.keys(self.views), function(view_name) { var controller = self.views[view_name].controller; From 66918d6e3266a8cc87d83f44a06b88103584976b Mon Sep 17 00:00:00 2001 From: Kersten Jeremy Date: Tue, 18 Mar 2014 18:44:45 +0100 Subject: [PATCH 29/86] [FIX] Force recompute the size of column for calendar, else on small screen, the size of columns for the last day is not the same that others. lp bug: https://launchpad.net/bugs/1290942 fixed bzr revid: jke@openerp.com-20140318174445-rjv3xq2rcxr3n5y9 --- addons/web_calendar/static/src/js/web_calendar.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web_calendar/static/src/js/web_calendar.js b/addons/web_calendar/static/src/js/web_calendar.js index 4c5473a2ed1..6db2d97cfeb 100644 --- a/addons/web_calendar/static/src/js/web_calendar.js +++ b/addons/web_calendar/static/src/js/web_calendar.js @@ -217,6 +217,7 @@ openerp.web_calendar = function(instance) { .then(function (create_right) { self.create_right = create_right; self.init_calendar().then(function() { + $(window).trigger('resize'); self.trigger('calendar_view_loaded', fv); self.ready.resolve(); }); From 758d467982a6cff85f6f7567292e2e345ca0abf6 Mon Sep 17 00:00:00 2001 From: Kersten Jeremy Date: Tue, 18 Mar 2014 21:30:51 +0100 Subject: [PATCH 30/86] [FIX] In mail text2Html, replace url (http or ftp) before the replacement of return line, else some return line was sometimes considered as the link. Eg : http://www.google.com


Puis
bzr revid: jke@openerp.com-20140318203051-lk92fjgwltkwbk68 --- addons/mail/static/src/js/mail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index b8bf703f604..17dd4b57cd0 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -76,8 +76,8 @@ openerp.mail = function (session) { */ get_text2html: function (text) { return text - .replace(/[\n\r]/g,'
') .replace(/((?:https?|ftp):\/\/[\S]+)/g,'$1 ') + .replace(/[\n\r]/g,'
') }, /* Returns the complete domain with "&" From edf0f26d5963a5a9736d9358bbf37b9fd7b9d5d7 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of openerp <> Date: Wed, 19 Mar 2014 05:52:26 +0000 Subject: [PATCH 31/86] Launchpad automatic translations update. bzr revid: launchpad_translations_on_behalf_of_openerp-20140319055226-bk4bs5tp612t236j --- addons/hr/i18n/am.po | 963 +++++++++++++++++++++++++++++++ addons/product_margin/i18n/am.po | 283 +++++++++ 2 files changed, 1246 insertions(+) create mode 100644 addons/hr/i18n/am.po create mode 100644 addons/product_margin/i18n/am.po diff --git a/addons/hr/i18n/am.po b/addons/hr/i18n/am.po new file mode 100644 index 00000000000..3b86e0a2a06 --- /dev/null +++ b/addons/hr/i18n/am.po @@ -0,0 +1,963 @@ +# Amharic translation for openobject-addons +# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-12-21 17:04+0000\n" +"PO-Revision-Date: 2014-03-18 13:39+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Amharic \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2014-03-19 05:52+0000\n" +"X-Generator: Launchpad (build 16963)\n" + +#. module: hr +#: model:process.node,name:hr.process_node_openerpuser0 +msgid "Openerp user" +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_hr_timesheet_sheet:0 +msgid "Allow timesheets validation by managers" +msgstr "" + +#. module: hr +#: field:hr.job,requirements:0 +msgid "Requirements" +msgstr "አስፈላጊ" + +#. module: hr +#: model:process.transition,name:hr.process_transition_contactofemployee0 +msgid "Link the employee to information" +msgstr "የሰራተኞች መረጃ ማገናኘት" + +#. module: hr +#: field:hr.employee,sinid:0 +msgid "SIN No" +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,name:hr.open_board_hr +#: model:ir.ui.menu,name:hr.menu_hr_dashboard +#: model:ir.ui.menu,name:hr.menu_hr_main +#: model:ir.ui.menu,name:hr.menu_hr_reporting +#: model:ir.ui.menu,name:hr.menu_hr_root +#: model:ir.ui.menu,name:hr.menu_human_resources_configuration +msgid "Human Resources" +msgstr "የሰው ሀይል አስተዳደር" + +#. module: hr +#: help:hr.employee,image_medium:0 +msgid "" +"Medium-sized photo of the employee. It is automatically resized as a " +"128x128px image, with aspect ratio preserved. Use this field in form views " +"or some kanban views." +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +msgid "Time Tracking" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +#: view:hr.job:0 +msgid "Group By..." +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,name:hr.view_department_form_installer +msgid "Create Your Departments" +msgstr "" + +#. module: hr +#: help:hr.job,no_of_employee:0 +msgid "Number of employees currently occupying this job position." +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_hr_evaluation:0 +msgid "Organize employees periodic evaluation" +msgstr "" + +#. module: hr +#: view:hr.department:0 +#: view:hr.employee:0 +#: field:hr.employee,department_id:0 +#: view:hr.job:0 +#: field:hr.job,department_id:0 +#: model:ir.model,name:hr.model_hr_department +msgid "Department" +msgstr "" + +#. module: hr +#: field:hr.employee,work_email:0 +msgid "Work Email" +msgstr "" + +#. module: hr +#: help:hr.employee,image:0 +msgid "" +"This field holds the image used as photo for the employee, limited to " +"1024x1024px." +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_hr_holidays:0 +msgid "This installs the module hr_holidays." +msgstr "" + +#. module: hr +#: view:hr.job:0 +msgid "Jobs" +msgstr "" + +#. module: hr +#: view:hr.job:0 +msgid "In Recruitment" +msgstr "" + +#. module: hr +#: field:hr.job,message_unread:0 +msgid "Unread Messages" +msgstr "" + +#. module: hr +#: field:hr.department,company_id:0 +#: view:hr.employee:0 +#: view:hr.job:0 +#: field:hr.job,company_id:0 +msgid "Company" +msgstr "" + +#. module: hr +#: field:hr.job,no_of_recruitment:0 +msgid "Expected in Recruitment" +msgstr "" + +#. module: hr +#: field:res.users,employee_ids:0 +msgid "Related employees" +msgstr "" + +#. module: hr +#: constraint:hr.employee.category:0 +msgid "Error! You cannot create recursive Categories." +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_hr_recruitment:0 +msgid "This installs the module hr_recruitment." +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Birth" +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,name:hr.open_view_categ_form +#: model:ir.ui.menu,name:hr.menu_view_employee_category_form +msgid "Employee Tags" +msgstr "" + +#. module: hr +#: view:hr.job:0 +msgid "Launch Recruitement" +msgstr "" + +#. module: hr +#: model:process.transition,name:hr.process_transition_employeeuser0 +msgid "Link a user to an employee" +msgstr "" + +#. module: hr +#: field:hr.department,parent_id:0 +msgid "Parent Department" +msgstr "" + +#. module: hr +#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_config +msgid "Leaves" +msgstr "" + +#. module: hr +#: selection:hr.employee,marital:0 +msgid "Married" +msgstr "" + +#. module: hr +#: field:hr.job,message_ids:0 +msgid "Messages" +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +msgid "Talent Management" +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_hr_timesheet_sheet:0 +msgid "This installs the module hr_timesheet_sheet." +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Mobile:" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Position" +msgstr "" + +#. module: hr +#: help:hr.job,message_unread:0 +msgid "If checked new messages require your attention." +msgstr "" + +#. module: hr +#: field:hr.employee,color:0 +msgid "Color Index" +msgstr "" + +#. module: hr +#: model:process.transition,note:hr.process_transition_employeeuser0 +msgid "" +"The Related user field on the Employee form allows to link the OpenERP user " +"(and her rights) to the employee." +msgstr "" + +#. module: hr +#: field:hr.employee,image_medium:0 +msgid "Medium-sized photo" +msgstr "" + +#. module: hr +#: field:hr.employee,identification_id:0 +msgid "Identification No" +msgstr "" + +#. module: hr +#: selection:hr.employee,gender:0 +msgid "Female" +msgstr "" + +#. module: hr +#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_new_config +msgid "Attendance" +msgstr "" + +#. module: hr +#: field:hr.employee,work_phone:0 +msgid "Work Phone" +msgstr "" + +#. module: hr +#: field:hr.employee.category,child_ids:0 +msgid "Child Categories" +msgstr "" + +#. module: hr +#: field:hr.job,description:0 +#: model:ir.model,name:hr.model_hr_job +msgid "Job Description" +msgstr "" + +#. module: hr +#: field:hr.employee,work_location:0 +msgid "Office Location" +msgstr "" + +#. module: hr +#: field:hr.job,message_follower_ids:0 +msgid "Followers" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +#: model:ir.model,name:hr.model_hr_employee +#: model:process.node,name:hr.process_node_employee0 +msgid "Employee" +msgstr "" + +#. module: hr +#: model:process.node,note:hr.process_node_employeecontact0 +msgid "Other information" +msgstr "" + +#. module: hr +#: help:hr.employee,image_small:0 +msgid "" +"Small-sized photo of the employee. It is automatically resized as a 64x64px " +"image, with aspect ratio preserved. Use this field anywhere a small image is " +"required." +msgstr "" + +#. module: hr +#: field:hr.employee,birthday:0 +msgid "Date of Birth" +msgstr "" + +#. module: hr +#: help:hr.job,no_of_recruitment:0 +msgid "Number of new employees you expect to recruit." +msgstr "" + +#. module: hr +#: model:ir.actions.client,name:hr.action_client_hr_menu +msgid "Open HR Menu" +msgstr "" + +#. module: hr +#: help:hr.job,message_summary:0 +msgid "" +"Holds the Chatter summary (number of messages, ...). This summary is " +"directly in html format in order to be inserted in kanban views." +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_account_analytic_analysis:0 +msgid "" +"This installs the module account_analytic_analysis, which will install sales " +"management too." +msgstr "" + +#. module: hr +#: view:board.board:0 +msgid "Human Resources Dashboard" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +#: field:hr.employee,job_id:0 +#: view:hr.job:0 +msgid "Job" +msgstr "" + +#. module: hr +#: field:hr.job,no_of_employee:0 +msgid "Current Number of Employees" +msgstr "" + +#. module: hr +#: field:hr.department,member_ids:0 +msgid "Members" +msgstr "" + +#. module: hr +#: model:ir.ui.menu,name:hr.menu_hr_configuration +msgid "Configuration" +msgstr "" + +#. module: hr +#: model:process.node,note:hr.process_node_employee0 +msgid "Employee form and structure" +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_hr_expense:0 +msgid "Manage employees expenses" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Tel:" +msgstr "" + +#. module: hr +#: selection:hr.employee,marital:0 +msgid "Divorced" +msgstr "" + +#. module: hr +#: field:hr.employee.category,parent_id:0 +msgid "Parent Category" +msgstr "" + +#. module: hr +#: view:hr.department:0 +#: model:ir.actions.act_window,name:hr.open_module_tree_department +#: model:ir.ui.menu,name:hr.menu_hr_department_tree +msgid "Departments" +msgstr "" + +#. module: hr +#: model:process.node,name:hr.process_node_employeecontact0 +msgid "Employee Contact" +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,help:hr.action_hr_job +msgid "" +"

\n" +" Click to define a new job position.\n" +"

\n" +" Job Positions are used to define jobs and their " +"requirements.\n" +" You can keep track of the number of employees you have per " +"job\n" +" position and follow the evolution according to what you " +"planned\n" +" for the future.\n" +"

\n" +" You can attach a survey to a job position. It will be used " +"in\n" +" the recruitment process to evaluate the applicants for this " +"job\n" +" position.\n" +"

\n" +" " +msgstr "" + +#. module: hr +#: selection:hr.employee,gender:0 +msgid "Male" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "" +"$('.oe_employee_picture').load(function() { if($(this).width() > " +"$(this).height()) { $(this).addClass('oe_employee_picture_wide') } });" +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_hr_evaluation:0 +msgid "This installs the module hr_evaluation." +msgstr "" + +#. module: hr +#: constraint:hr.employee:0 +msgid "Error! You cannot create recursive hierarchy of Employee(s)." +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_hr_attendance:0 +msgid "This installs the module hr_attendance." +msgstr "" + +#. module: hr +#: field:hr.employee,image_small:0 +msgid "Smal-sized photo" +msgstr "" + +#. module: hr +#: view:hr.employee.category:0 +#: model:ir.model,name:hr.model_hr_employee_category +msgid "Employee Category" +msgstr "" + +#. module: hr +#: field:hr.employee,category_ids:0 +msgid "Tags" +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_hr_contract:0 +msgid "This installs the module hr_contract." +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Related User" +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +msgid "or" +msgstr "" + +#. module: hr +#: field:hr.employee.category,name:0 +msgid "Category" +msgstr "" + +#. module: hr +#: view:hr.job:0 +msgid "Stop Recruitment" +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_hr_attendance:0 +msgid "Install attendances feature" +msgstr "" + +#. module: hr +#: help:hr.employee,bank_account_id:0 +msgid "Employee bank salary account" +msgstr "" + +#. module: hr +#: field:hr.department,note:0 +msgid "Note" +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,name:hr.open_view_employee_tree +msgid "Employees Structure" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Contact Information" +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_hr_holidays:0 +msgid "Manage holidays, leaves and allocation requests" +msgstr "" + +#. module: hr +#: field:hr.department,child_ids:0 +msgid "Child Departments" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +#: view:hr.job:0 +#: field:hr.job,state:0 +msgid "Status" +msgstr "" + +#. module: hr +#: field:hr.employee,otherid:0 +msgid "Other Id" +msgstr "" + +#. module: hr +#: model:process.process,name:hr.process_process_employeecontractprocess0 +msgid "Employee Contract" +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +msgid "Contracts" +msgstr "" + +#. module: hr +#: help:hr.job,message_ids:0 +msgid "Messages and communication history" +msgstr "" + +#. module: hr +#: field:hr.employee,ssnid:0 +msgid "SSN No" +msgstr "" + +#. module: hr +#: field:hr.job,message_is_follower:0 +msgid "Is a Follower" +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_hr_recruitment:0 +msgid "Manage the recruitment process" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Active" +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +msgid "Human Resources Management" +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +msgid "Install your country's payroll" +msgstr "" + +#. module: hr +#: field:hr.employee,bank_account_id:0 +msgid "Bank Account Number" +msgstr "" + +#. module: hr +#: view:hr.department:0 +msgid "Companies" +msgstr "" + +#. module: hr +#: field:hr.job,message_summary:0 +msgid "Summary" +msgstr "" + +#. module: hr +#: model:process.transition,note:hr.process_transition_contactofemployee0 +msgid "" +"In the Employee form, there are different kind of information like Contact " +"information." +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,help:hr.open_view_employee_list_my +msgid "" +"

\n" +" Click to add a new employee.\n" +"

\n" +" With just a quick glance on the OpenERP employee screen, " +"you\n" +" can easily find all the information you need for each " +"person;\n" +" contact data, job position, availability, etc.\n" +"

\n" +" " +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "HR Settings" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Citizenship & Other Info" +msgstr "" + +#. module: hr +#: constraint:hr.department:0 +msgid "Error! You cannot create recursive departments." +msgstr "" + +#. module: hr +#: field:hr.employee,address_id:0 +msgid "Working Address" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Public Information" +msgstr "" + +#. module: hr +#: field:hr.employee,marital:0 +msgid "Marital Status" +msgstr "" + +#. module: hr +#: model:ir.model,name:hr.model_ir_actions_act_window +msgid "ir.actions.act_window" +msgstr "" + +#. module: hr +#: field:hr.employee,last_login:0 +msgid "Latest Connection" +msgstr "" + +#. module: hr +#: field:hr.employee,image:0 +msgid "Photo" +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +msgid "Cancel" +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,help:hr.open_module_tree_department +msgid "" +"

\n" +" Click to create a department.\n" +"

\n" +" OpenERP's department structure is used to manage all " +"documents\n" +" related to employees by departments: expenses, timesheets,\n" +" leaves and holidays, recruitments, etc.\n" +"

\n" +" " +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_hr_timesheet:0 +msgid "This installs the module hr_timesheet." +msgstr "" + +#. module: hr +#: help:hr.job,expected_employees:0 +msgid "" +"Expected number of employees for this job position after new recruitment." +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,help:hr.view_department_form_installer +msgid "" +"

\n" +" Click to define a new department.\n" +"

\n" +" Your departments structure is used to manage all documents\n" +" related to employees by departments: expenses and " +"timesheets,\n" +" leaves and holidays, recruitments, etc.\n" +"

\n" +" " +msgstr "" + +#. module: hr +#: view:hr.employee:0 +msgid "Personal Information" +msgstr "" + +#. module: hr +#: field:hr.employee,city:0 +msgid "City" +msgstr "" + +#. module: hr +#: field:hr.employee,passport_id:0 +msgid "Passport No" +msgstr "" + +#. module: hr +#: field:hr.employee,mobile_phone:0 +msgid "Work Mobile" +msgstr "" + +#. module: hr +#: selection:hr.job,state:0 +msgid "Recruitement in Progress" +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_account_analytic_analysis:0 +msgid "" +"Allow invoicing based on timesheets (the sale application will be installed)" +msgstr "" + +#. module: hr +#: view:hr.employee.category:0 +msgid "Employees Categories" +msgstr "" + +#. module: hr +#: field:hr.employee,address_home_id:0 +msgid "Home Address" +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_hr_timesheet:0 +msgid "Manage timesheets" +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,name:hr.open_payroll_modules +msgid "Payroll" +msgstr "" + +#. module: hr +#: selection:hr.employee,marital:0 +msgid "Single" +msgstr "" + +#. module: hr +#: field:hr.job,name:0 +msgid "Job Name" +msgstr "" + +#. module: hr +#: view:hr.job:0 +msgid "In Position" +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_hr_payroll:0 +msgid "This installs the module hr_payroll." +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_hr_contract:0 +msgid "Record contracts per employee" +msgstr "" + +#. module: hr +#: view:hr.department:0 +msgid "department" +msgstr "" + +#. module: hr +#: field:hr.employee,country_id:0 +msgid "Nationality" +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +msgid "Additional Features" +msgstr "" + +#. module: hr +#: field:hr.employee,notes:0 +msgid "Notes" +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,name:hr.action2 +msgid "Subordinate Hierarchy" +msgstr "" + +#. module: hr +#: field:hr.employee,resource_id:0 +msgid "Resource" +msgstr "" + +#. module: hr +#: field:hr.department,complete_name:0 +#: field:hr.employee,name_related:0 +#: field:hr.employee.category,complete_name:0 +msgid "Name" +msgstr "" + +#. module: hr +#: field:hr.employee,gender:0 +msgid "Gender" +msgstr "" + +#. module: hr +#: view:hr.employee:0 +#: field:hr.employee.category,employee_ids:0 +#: field:hr.job,employee_ids:0 +#: model:ir.actions.act_window,name:hr.hr_employee_normal_action_tree +#: model:ir.actions.act_window,name:hr.open_view_employee_list +#: model:ir.actions.act_window,name:hr.open_view_employee_list_my +#: model:ir.ui.menu,name:hr.menu_open_view_employee_list_my +msgid "Employees" +msgstr "" + +#. module: hr +#: help:hr.employee,sinid:0 +msgid "Social Insurance Number" +msgstr "" + +#. module: hr +#: field:hr.department,name:0 +msgid "Department Name" +msgstr "" + +#. module: hr +#: model:ir.ui.menu,name:hr.menu_hr_reporting_timesheet +msgid "Reports" +msgstr "" + +#. module: hr +#: field:hr.config.settings,module_hr_payroll:0 +msgid "Manage payroll" +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +#: model:ir.actions.act_window,name:hr.action_human_resources_configuration +msgid "Configure Human Resources" +msgstr "" + +#. module: hr +#: selection:hr.job,state:0 +msgid "No Recruitment" +msgstr "" + +#. module: hr +#: help:hr.employee,ssnid:0 +msgid "Social Security Number" +msgstr "" + +#. module: hr +#: model:process.node,note:hr.process_node_openerpuser0 +msgid "Creation of a OpenERP user" +msgstr "" + +#. module: hr +#: field:hr.employee,login:0 +msgid "Login" +msgstr "" + +#. module: hr +#: field:hr.job,expected_employees:0 +msgid "Total Forecasted Employees" +msgstr "" + +#. module: hr +#: help:hr.job,state:0 +msgid "" +"By default 'In position', set it to 'In Recruitment' if recruitment process " +"is going on for this job position." +msgstr "" + +#. module: hr +#: model:ir.model,name:hr.model_res_users +msgid "Users" +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,name:hr.action_hr_job +#: model:ir.ui.menu,name:hr.menu_hr_job +msgid "Job Positions" +msgstr "" + +#. module: hr +#: model:ir.actions.act_window,help:hr.open_board_hr +msgid "" +"
\n" +"

\n" +" Human Resources dashboard is empty.\n" +"

\n" +" To add your first report into this dashboard, go to any\n" +" menu, switch to list or graph view, and click 'Add " +"to\n" +" Dashboard' in the extended search options.\n" +"

\n" +" You can filter and group data before inserting into the\n" +" dashboard using the search options.\n" +"

\n" +"
\n" +" " +msgstr "" + +#. module: hr +#: view:hr.employee:0 +#: field:hr.employee,coach_id:0 +msgid "Coach" +msgstr "" + +#. module: hr +#: sql_constraint:hr.job:0 +msgid "The name of the job position must be unique per company!" +msgstr "" + +#. module: hr +#: help:hr.config.settings,module_hr_expense:0 +msgid "This installs the module hr_expense." +msgstr "" + +#. module: hr +#: model:ir.model,name:hr.model_hr_config_settings +msgid "hr.config.settings" +msgstr "" + +#. module: hr +#: field:hr.department,manager_id:0 +#: view:hr.employee:0 +#: field:hr.employee,parent_id:0 +msgid "Manager" +msgstr "" + +#. module: hr +#: selection:hr.employee,marital:0 +msgid "Widower" +msgstr "" + +#. module: hr +#: field:hr.employee,child_ids:0 +msgid "Subordinates" +msgstr "" + +#. module: hr +#: view:hr.config.settings:0 +msgid "Apply" +msgstr "" diff --git a/addons/product_margin/i18n/am.po b/addons/product_margin/i18n/am.po new file mode 100644 index 00000000000..c9eea96959e --- /dev/null +++ b/addons/product_margin/i18n/am.po @@ -0,0 +1,283 @@ +# Amharic translation for openobject-addons +# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-12-21 17:06+0000\n" +"PO-Revision-Date: 2014-03-18 08:01+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Amharic \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2014-03-19 05:52+0000\n" +"X-Generator: Launchpad (build 16963)\n" + +#. module: product_margin +#: view:product.product:0 +#: field:product.product,turnover:0 +msgid "Turnover" +msgstr "ተመላሽ" + +#. module: product_margin +#: field:product.product,expected_margin_rate:0 +msgid "Expected Margin (%)" +msgstr "" + +#. module: product_margin +#: field:product.margin,from_date:0 +msgid "From" +msgstr "ከ" + +#. module: product_margin +#: help:product.product,total_cost:0 +msgid "" +"Sum of Multiplication of Invoice price and quantity of Supplier Invoices " +msgstr "" + +#. module: product_margin +#: field:product.margin,to_date:0 +msgid "To" +msgstr "ለ" + +#. module: product_margin +#: help:product.product,total_margin:0 +msgid "Turnover - Standard price" +msgstr "የተመላሽ መደበኛ ዋጋ" + +#. module: product_margin +#: field:product.product,total_margin_rate:0 +msgid "Total Margin Rate(%)" +msgstr "" + +#. module: product_margin +#: selection:product.margin,invoice_state:0 +#: selection:product.product,invoice_state:0 +msgid "Draft, Open and Paid" +msgstr "ያልተከፈለና የተከፈለ ደረሰኞች" + +#. module: product_margin +#: code:addons/product_margin/wizard/product_margin.py:73 +#: model:ir.actions.act_window,name:product_margin.product_margin_act_window +#: model:ir.ui.menu,name:product_margin.menu_action_product_margin +#: view:product.product:0 +#, python-format +msgid "Product Margins" +msgstr "የእቃው አይነት በአንድ መጠን ሲጨምር" + +#. module: product_margin +#: field:product.product,purchase_avg_price:0 +#: field:product.product,sale_avg_price:0 +msgid "Avg. Unit Price" +msgstr "የእቃዎች መካከለኛ ዋጋ" + +#. module: product_margin +#: field:product.product,sale_num_invoiced:0 +msgid "# Invoiced in Sale" +msgstr "" + +#. module: product_margin +#: view:product.product:0 +msgid "Catalog Price" +msgstr "ቅናሽ ዋጋ" + +#. module: product_margin +#: selection:product.margin,invoice_state:0 +#: selection:product.product,invoice_state:0 +msgid "Paid" +msgstr "ተከፈል" + +#. module: product_margin +#: view:product.product:0 +#: field:product.product,sales_gap:0 +msgid "Sales Gap" +msgstr "የሽያጭ ክፍተት" + +#. module: product_margin +#: help:product.product,sales_gap:0 +msgid "Expected Sale - Turn Over" +msgstr "ሊሸጥ የሚችል እቃ" + +#. module: product_margin +#: field:product.product,sale_expected:0 +msgid "Expected Sale" +msgstr "ሊሸጥ የሚችል እቃ" + +#. module: product_margin +#: view:product.product:0 +msgid "Standard Price" +msgstr "የእቃው መደበኛ ዋጋ" + +#. module: product_margin +#: help:product.product,purchase_num_invoiced:0 +msgid "Sum of Quantity in Supplier Invoices" +msgstr "" + +#. module: product_margin +#: field:product.product,date_to:0 +msgid "Margin Date To" +msgstr "የእቃው መጠን የጨመረበት ቀን" + +#. module: product_margin +#: view:product.product:0 +msgid "Analysis Criteria" +msgstr "የመመዘኛ መስፈርት" + +#. module: product_margin +#: view:product.product:0 +#: field:product.product,total_cost:0 +msgid "Total Cost" +msgstr "አጠቃላይ ዋጋ" + +#. module: product_margin +#: help:product.product,normal_cost:0 +msgid "Sum of Multiplication of Cost price and quantity of Supplier Invoices" +msgstr "" + +#. module: product_margin +#: field:product.product,expected_margin:0 +msgid "Expected Margin" +msgstr "የሚጠበቅ ጭማሪ" + +#. module: product_margin +#: view:product.product:0 +msgid "#Purchased" +msgstr "" + +#. module: product_margin +#: help:product.product,expected_margin_rate:0 +msgid "Expected margin * 100 / Expected Sale" +msgstr "" + +#. module: product_margin +#: help:product.product,sale_avg_price:0 +msgid "Avg. Price in Customer Invoices." +msgstr "የመካከለኛ ዋጋ ለገዢዎች" + +#. module: product_margin +#: help:product.product,purchase_avg_price:0 +msgid "Avg. Price in Supplier Invoices " +msgstr "የመካከለኛ ዋጋ አቅራቢዎች " + +#. module: product_margin +#: field:product.margin,invoice_state:0 +#: field:product.product,invoice_state:0 +msgid "Invoice State" +msgstr "" + +#. module: product_margin +#: help:product.product,purchase_gap:0 +msgid "Normal Cost - Total Cost" +msgstr "" + +#. module: product_margin +#: help:product.product,sale_expected:0 +msgid "" +"Sum of Multiplication of Sale Catalog price and quantity of Customer Invoices" +msgstr "" + +#. module: product_margin +#: field:product.product,total_margin:0 +msgid "Total Margin" +msgstr "የሁሉም ዋጋ በአንድ መጠን ሲጨምር" + +#. module: product_margin +#: field:product.product,date_from:0 +msgid "Margin Date From" +msgstr "እቃው ከጨመረበት ቀን ጀምሮ" + +#. module: product_margin +#: help:product.product,turnover:0 +msgid "" +"Sum of Multiplication of Invoice price and quantity of Customer Invoices" +msgstr "" + +#. module: product_margin +#: field:product.product,normal_cost:0 +msgid "Normal Cost" +msgstr "መደበኛ ዋጋ" + +#. module: product_margin +#: view:product.product:0 +msgid "Purchases" +msgstr "ግዢዎች" + +#. module: product_margin +#: field:product.product,purchase_num_invoiced:0 +msgid "# Invoiced in Purchase" +msgstr "" + +#. module: product_margin +#: help:product.product,expected_margin:0 +msgid "Expected Sale - Normal Cost" +msgstr "" + +#. module: product_margin +#: view:product.margin:0 +msgid "Properties categories" +msgstr "በአይነታቸው መከፍፈል" + +#. module: product_margin +#: help:product.product,total_margin_rate:0 +msgid "Total margin * 100 / Turnover" +msgstr "" + +#. module: product_margin +#: view:product.margin:0 +msgid "Open Margins" +msgstr "" + +#. module: product_margin +#: selection:product.margin,invoice_state:0 +#: selection:product.product,invoice_state:0 +msgid "Open and Paid" +msgstr "የተከፈተና የትከፈል" + +#. module: product_margin +#: view:product.product:0 +msgid "Sales" +msgstr "ሽያጭ" + +#. module: product_margin +#: model:ir.model,name:product_margin.model_product_product +msgid "Product" +msgstr "ውጤት" + +#. module: product_margin +#: view:product.margin:0 +msgid "General Information" +msgstr "አጠቃላይ መርጃ" + +#. module: product_margin +#: field:product.product,purchase_gap:0 +msgid "Purchase Gap" +msgstr "የግዢ ክፍተት" + +#. module: product_margin +#: view:product.margin:0 +msgid "Cancel" +msgstr "መሰረዝ" + +#. module: product_margin +#: view:product.product:0 +msgid "Margins" +msgstr "በአንድ መጠን ሲጨምር" + +#. module: product_margin +#: help:product.product,sale_num_invoiced:0 +msgid "Sum of Quantity in Customer Invoices" +msgstr "" + +#. module: product_margin +#: view:product.margin:0 +msgid "or" +msgstr "ወይም" + +#. module: product_margin +#: model:ir.model,name:product_margin.model_product_margin +msgid "Product Margin" +msgstr "የእቃው መጨመር" From 0b237def4d95d40f739cd512beec1e761c0a1255 Mon Sep 17 00:00:00 2001 From: Kersten Jeremy Date: Wed, 19 Mar 2014 12:50:57 +0100 Subject: [PATCH 32/86] [FIX] Add tack_visibility on field 'name' from model : project. That's allow to know which Project is concerned by the subtype. bzr revid: jke@openerp.com-20140319115057-55h9fh7y7x2nu9ik --- addons/project/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/project/project.py b/addons/project/project.py index e5dcf5de2cf..f6bd55c2fb6 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -745,7 +745,7 @@ class task(osv.osv): _columns = { 'active': fields.function(_is_template, store=True, string='Not a Template Task', type='boolean', help="This field is computed automatically and have the same behavior than the boolean 'active' field: if the task is linked to a template or unactivated project, it will be hidden unless specifically asked."), - 'name': fields.char('Task Summary', size=128, required=True, select=True), + 'name': fields.char('Task Summary', track_visibility='onchange', size=128, required=True, select=True), 'description': fields.text('Description'), 'priority': fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Important'), ('0','Very important')], 'Priority', select=True), 'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."), From b245af63b5e62198af3ce1f67b908bb8cc169dd3 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 19 Mar 2014 15:11:30 +0100 Subject: [PATCH 33/86] [FIX] website_sale: do not display product categories with no products bzr revid: dle@openerp.com-20140319141130-dease119j9b2l2fh --- addons/website_sale/controllers/main.py | 16 ++++++++++++---- addons/website_sale/views/website_sale.xml | 2 ++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 3aaabb596f1..b345b810e32 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -226,7 +226,8 @@ class Ecommerce(http.Controller): def shop(self, category=None, page=0, filters='', search='', **post): cr, uid, context = request.cr, request.uid, request.context product_obj = request.registry.get('product.template') - domain = request.registry.get('website').ecommerce_get_product_domain() + base_domain = request.registry.get('website').ecommerce_get_product_domain() + domain = list(base_domain) if search: domain += ['|', ('name', 'ilike', search), @@ -265,9 +266,15 @@ class Ecommerce(http.Controller): pass category_obj = request.registry.get('product.public.category') - category_ids = category_obj.search(cr, uid, [], context=context) + category_ids = [product['public_categ_id'][0] for product in product_obj.read_group(cr, uid, base_domain, ['public_categ_id'], ['public_categ_id'], context=context) if product['public_categ_id']] categories = category_obj.browse(cr, uid, category_ids, context=context) - categs = filter(lambda x: not x.parent_id, categories) + all_categories = set(categories) + for cat in categories: + parent = cat.parent_id + while parent: + all_categories.add(parent) + parent = parent.parent_id + categories = list(all_categories) values = { 'products': products, @@ -282,7 +289,8 @@ class Ecommerce(http.Controller): 'pager': pager, 'styles': styles, 'category': category, - 'categories': categs, + 'categories': filter(lambda x: not x.parent_id, categories), + 'all_categories': categories, 'Ecommerce': self, # TODO fp: Should be removed 'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids], } diff --git a/addons/website_sale/views/website_sale.xml b/addons/website_sale/views/website_sale.xml index 247c9127222..264eab5c36b 100644 --- a/addons/website_sale/views/website_sale.xml +++ b/addons/website_sale/views/website_sale.xml @@ -33,12 +33,14 @@ From 310bcc4d689862143c688e53d9c1254f1d81a1ac Mon Sep 17 00:00:00 2001 From: Kersten Jeremy Date: Wed, 19 Mar 2014 15:33:46 +0100 Subject: [PATCH 34/86] [IMP] Aged Partner Balance - Add selection in wizard to allow to choose the target_move. Customer was unable to make an aged balance with all payments move but only with the move validated. bzr revid: jke@openerp.com-20140319143346-h3f4y77d736w5lc1 --- .../account/wizard/account_report_aged_partner_balance_view.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/account/wizard/account_report_aged_partner_balance_view.xml b/addons/account/wizard/account_report_aged_partner_balance_view.xml index be1d710c09d..09ae525c595 100644 --- a/addons/account/wizard/account_report_aged_partner_balance_view.xml +++ b/addons/account/wizard/account_report_aged_partner_balance_view.xml @@ -19,6 +19,7 @@ +