diff --git a/openerp/addons/base/__openerp__.py b/openerp/addons/base/__openerp__.py index 57ce6b31e32..3d0d3e4dda4 100644 --- a/openerp/addons/base/__openerp__.py +++ b/openerp/addons/base/__openerp__.py @@ -58,6 +58,7 @@ The kernel of OpenERP, needed for all installation. 'ir/osv_memory_autovacuum.xml', 'ir/ir_model_report.xml', 'ir/ir_logging_view.xml', + 'ir/ir_qweb.xml', 'workflow/workflow_view.xml', 'module/module_view.xml', 'module/module_data.xml', diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index d745174c371..41d076a03fb 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -141,12 +141,12 @@ class ir_actions_report_xml(osv.osv): 'model': fields.char('Model', required=True), 'report_type': fields.selection([('qweb-pdf', 'PDF'), ('qweb-html', 'HTML'), - ('other', 'Other'), + ('controller', 'Controller'), ('pdf', 'RML pdf (deprecated)'), ('sxw', 'RML sxw (deprecated)'), ('webkit', 'Webkit (deprecated)'), - ], 'Report Type', required=True, help="PDF will use wkhtmltopdf to render html to pdf, HTML will directly show html, Other will force download the controller output keeping the MIME type."), - 'report_name': fields.char('Controller Name', required=True, help="URL of the report will be /report//, the default controller also use this field to get the name of the qweb ir.ui.view to render. For RML reports, this is the LocalService name."), + ], 'Report Type', required=True, help="HTML will open the report directly in your browser, PDF will use wkhtmltopdf to render the HTML into a PDF file and let you download it, Controller allows you to define the url of a custom controller outputting any kind of report."), + 'report_name': fields.char('Template Name', required=True, help="For QWeb reports, name of the template used in the rendering. The method 'render_html' of the model 'report.template_name' will be called (if any) to give the html. For RML reports, this is the LocalService name."), 'groups_id': fields.many2many('res.groups', 'res_groups_report_rel', 'uid', 'gid', 'Groups'), # options @@ -163,7 +163,7 @@ class ir_actions_report_xml(osv.osv): 'report_xsl': fields.char('XSL Path'), 'report_xml': fields.char('XML Path'), - 'report_rml': fields.char('Main Report File Path', help="The path to the main report file (depending on Report Type) or NULL if the content is in another data field"), + 'report_rml': fields.char('Main Report File Path/controller', help="The path to the main report file/controller (depending on Report Type) or NULL if the content is in another data field"), 'report_file': fields.related('report_rml', type="char", required=False, readonly=False, string='Report File', help="The path to the main report file (depending on Report Type) or NULL if the content is in another field", store=True), 'report_sxw': fields.function(_report_sxw, type='char', string='SXW Path'), diff --git a/openerp/addons/base/ir/ir_actions.xml b/openerp/addons/base/ir/ir_actions.xml index 77d7eb23e8c..4eb869daa80 100644 --- a/openerp/addons/base/ir/ir_actions.xml +++ b/openerp/addons/base/ir/ir_actions.xml @@ -59,12 +59,13 @@ - + + - - + + diff --git a/openerp/addons/base/ir/ir_attachment.py b/openerp/addons/base/ir/ir_attachment.py index 992ac1b5ff0..186c02f00b8 100644 --- a/openerp/addons/base/ir/ir_attachment.py +++ b/openerp/addons/base/ir/ir_attachment.py @@ -69,7 +69,7 @@ class ir_attachment(osv.osv): @tools.ormcache() def _filestore(self, cr, uid, context=None): - return os.path.join(tools.config['data_dir'], 'filestore', cr.dbname) + return tools.config.filestore(cr.dbname) # 'data' field implementation def _full_path(self, cr, uid, location, path): diff --git a/openerp/addons/base/ir/ir_qweb.py b/openerp/addons/base/ir/ir_qweb.py index 0b3bf91dc1f..f19a1fa6746 100644 --- a/openerp/addons/base/ir/ir_qweb.py +++ b/openerp/addons/base/ir/ir_qweb.py @@ -808,6 +808,38 @@ class RelativeDatetimeConverter(osv.AbstractModel): return babel.dates.format_timedelta( value - reference, add_direction=True, locale=locale) + +class Contact(orm.AbstractModel): + _name = 'ir.qweb.field.contact' + _inherit = 'ir.qweb.field.many2one' + + def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None): + opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"] + + if not getattr(record, field_name): + return None + + id = getattr(record, field_name).id + field_browse = self.pool[column._obj].browse(cr, openerp.SUPERUSER_ID, id, context={"show_address": True}) + value = werkzeug.utils.escape( field_browse.name_get()[0][1] ) + + val = { + 'name': value.split("\n")[0], + 'address': werkzeug.utils.escape("\n".join(value.split("\n")[1:])), + 'phone': field_browse.phone, + 'mobile': field_browse.mobile, + 'fax': field_browse.fax, + 'city': field_browse.city, + 'country_id': field_browse.country_id and field_browse.country_id.name_get()[0][1], + 'email': field_browse.email, + 'fields': opf, + 'options': options + } + + html = self.pool["ir.ui.view"].render(cr, uid, "base.contact", val, engine='ir.qweb', context=context).decode('utf8') + + return HTMLSafe(html) + class HTMLSafe(object): """ HTMLSafe string wrapper, Werkzeug's escape() has special handling for objects with a ``__html__`` methods but AFAIK does not provide any such diff --git a/openerp/addons/base/ir/ir_qweb.xml b/openerp/addons/base/ir/ir_qweb.xml new file mode 100644 index 00000000000..25bb4ef8ef9 --- /dev/null +++ b/openerp/addons/base/ir/ir_qweb.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 4fe6ff1989e..5bbd377f29d 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -493,6 +493,10 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): return res def execute(self, cr, uid, ids, context=None): + if context is None: + context = {} + + context = dict(context, active_test=False) if uid != SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager'): raise openerp.exceptions.AccessError(_("Only administrators can change the settings")) @@ -500,7 +504,7 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): ir_module = self.pool['ir.module.module'] res_groups = self.pool['res.groups'] - classified = self._get_classified_fields(cr, uid, context) + classified = self._get_classified_fields(cr, uid, context=context) config = self.browse(cr, uid, ids[0], context) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 1668cbda3c5..b85c49abf15 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -586,7 +586,7 @@ class groups_implied(osv.osv): res = super(groups_implied, self).write(cr, uid, ids, values, context) if values.get('users') or values.get('implied_ids'): # add all implied groups (to all users of each group) - for g in self.browse(cr, uid, ids): + for g in self.browse(cr, uid, ids, context=context): gids = map(int, g.trans_implied_ids) vals = {'users': [(4, u.id) for u in g.users]} super(groups_implied, self).write(cr, uid, gids, vals, context) diff --git a/openerp/cli/server.py b/openerp/cli/server.py index 42d920da9aa..e7ba695f19c 100644 --- a/openerp/cli/server.py +++ b/openerp/cli/server.py @@ -127,7 +127,6 @@ def main(args): check_root_user() openerp.tools.config.parse_config(args) check_postgres_user() - openerp.netsvc.init_logger() report_configuration() config = openerp.tools.config diff --git a/openerp/http.py b/openerp/http.py index 5876cdc6a9a..a4fc08bacde 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -36,6 +36,7 @@ import werkzeug.wsgi import openerp from openerp.service import security, model as service_model +from openerp.tools.func import lazy_property _logger = logging.getLogger(__name__) @@ -286,7 +287,7 @@ class WebRequest(object): def checked_call(___dbname, *a, **kw): # The decorator can call us more than once if there is an database error. In this # case, the request cursor is unusable. Rollback transaction to create a new one. - if self._cr: + if self._cr and not openerp.tools.config['test_enable']: self._cr.rollback() return self.endpoint(*a, **kw) @@ -1077,22 +1078,25 @@ class Root(object): path = openerp.tools.config.session_dir _logger.debug('HTTP sessions stored in: %s', path) self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession) + self._loaded = False - # TODO should we move this to ir.http so that only configured modules are served ? - _logger.info("HTTP Configuring static files") - self.load_addons() - + @lazy_property + def nodb_routing_map(self): _logger.info("Generating nondb routing") - self.nodb_routing_map = routing_map([''] + openerp.conf.server_wide_modules, True) + return routing_map([''] + openerp.conf.server_wide_modules, True) def __call__(self, environ, start_response): """ Handle a WSGI request """ + if not self._loaded: + self._loaded = True + self.load_addons() return self.dispatch(environ, start_response) def load_addons(self): """ Load all addons from addons patch containg static files and controllers and configure them. """ + # TODO should we move this to ir.http so that only configured modules are served ? statics = {} for addons_path in openerp.modules.module.ad_paths: @@ -1106,12 +1110,16 @@ class Root(object): _logger.debug("Loading %s", module) if 'openerp.addons' in sys.modules: m = __import__('openerp.addons.' + module) + else: + m = None addons_module[module] = m addons_manifest[module] = manifest statics['/%s/static' % module] = path_static - app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, statics) - self.dispatch = DisableCacheMiddleware(app) + if statics: + _logger.info("HTTP Configuring static files") + app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, statics) + self.dispatch = DisableCacheMiddleware(app) def setup_session(self, httprequest): # recover or create session @@ -1284,11 +1292,8 @@ class CommonController(Controller): """ Method used by client APIs to contact OpenERP. """ return dispatch_rpc(service, method, args) -root = None - -def wsgi_postload(): - global root - root = Root() - openerp.service.wsgi_server.register_wsgi_handler(root) +# register main wsgi handler +root = Root() +openerp.service.wsgi_server.register_wsgi_handler(root) # vim:et:ts=4:sw=4: diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 681d8534c05..5635e42e780 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -42,6 +42,7 @@ from openerp import SUPERUSER_ID from openerp.tools.translate import _ from openerp.modules.module import initialize_sys_path, \ load_openerp_module, init_module_models, adapt_version +from module import runs_post_install _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('openerp.tests') @@ -425,8 +426,13 @@ def load_modules(db, force_demo=False, status=None, update_module=False): for model in registry.models.values(): model._register_hook(cr) + # STEP 9: Run the post-install tests + cr.commit() + if openerp.tools.config['test_enable']: + cr.execute("SELECT name FROM ir_module_module WHERE state='installed'") + for module_name in cr.fetchall(): + report.record_result(openerp.modules.module.run_unit_tests(module_name[0], cr.dbname, position=runs_post_install)) finally: cr.close() - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 8958813baa2..551b7a664b5 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -356,30 +356,6 @@ class TestStream(object): current_test = None -def run_unit_tests(module_name, dbname): - """ - :returns: ``True`` if all of ``module_name``'s tests succeeded, ``False`` - if any of them failed. - :rtype: bool - """ - global current_test - current_test = module_name - mods = get_test_modules(module_name) - r = True - for m in mods: - tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m)) - suite = unittest2.TestSuite(itertools.ifilter(runs_at_install, tests)) - _logger.info('running %s tests.', m.__name__) - - result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite) - - if not result.wasSuccessful(): - r = False - _logger.error("Module %s: %d failures, %d errors", - module_name, len(result.failures), len(result.errors)) - current_test = None - return r - def runs_at(test, hook, default): # by default, tests do not run post install test_runs = getattr(test, hook, default) @@ -396,6 +372,30 @@ def runs_at(test, hook, default): runs_at_install = functools.partial(runs_at, hook='at_install', default=True) runs_post_install = functools.partial(runs_at, hook='post_install', default=False) +def run_unit_tests(module_name, dbname, position=runs_at_install): + """ + :returns: ``True`` if all of ``module_name``'s tests succeeded, ``False`` + if any of them failed. + :rtype: bool + """ + global current_test + current_test = module_name + mods = get_test_modules(module_name) + r = True + for m in mods: + tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m)) + suite = unittest2.TestSuite(itertools.ifilter(position, tests)) + _logger.info('running %s tests.', m.__name__) + + result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite) + + if not result.wasSuccessful(): + r = False + _logger.error("Module %s: %d failures, %d errors", + module_name, len(result.failures), len(result.errors)) + current_test = None + return r + def unwrap_suite(test): """ Attempts to unpack testsuites (holding suites or cases) in order to diff --git a/openerp/service/db.py b/openerp/service/db.py index f10f39aab9b..bb97e72f992 100644 --- a/openerp/service/db.py +++ b/openerp/service/db.py @@ -126,6 +126,11 @@ def exp_duplicate_database(db_original_name, db_name): with closing(db.cursor()) as cr: cr.autocommit(True) # avoid transaction block cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name)) + + from_fs = openerp.tools.config.filestore(db_original_name) + to_fs = openerp.tools.config.filestore(db_name) + if os.path.exists(from_fs) and not os.path.exists(to_fs): + shutil.copy(from_fs, to_fs) return True def exp_get_progress(id): @@ -178,6 +183,10 @@ def exp_drop(db_name): raise Exception("Couldn't drop database %s: %s" % (db_name, e)) else: _logger.info('DROP DB: %s', db_name) + + fs = openerp.tools.config.filestore(db_name) + if os.path.exists(fs): + shutil.rmtree(fs) return True def _set_pg_password_in_environment(func): @@ -328,6 +337,11 @@ def exp_rename(old_name, new_name): except Exception, e: _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e) raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e)) + + old_fs = openerp.tools.config.filestore(old_name) + new_fs = openerp.tools.config.filestore(new_name) + if os.path.exists(old_fs) and not os.path.exists(new_fs): + shutil.move(old_fs, new_fs) return True @openerp.tools.mute_logger('openerp.sql_db') diff --git a/openerp/service/http_server.py b/openerp/service/http_server.py deleted file mode 100644 index e25e75dba62..00000000000 --- a/openerp/service/http_server.py +++ /dev/null @@ -1,178 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright P. Christeas 2008-2010 -# Copyright 2010 OpenERP SA. (http://www.openerp.com) -# -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsibility of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# guarantees and support are strongly advised to contract a Free Software -# Service Company -# -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -############################################################################### - - -""" This module offers the family of HTTP-based servers. These are not a single - class/functionality, but a set of network stack layers, implementing - extendable HTTP protocols. - - The OpenERP server defines a single instance of a HTTP server, listening at - the standard 8069, 8071 ports (well, it is 2 servers, and ports are - configurable, of course). This "single" server then uses a `MultiHTTPHandler` - to dispatch requests to the appropriate channel protocol, like the XML-RPC, - static HTTP, DAV or other. -""" - -import base64 -import posixpath -import urllib -import os -import logging - -from websrv_lib import * -import openerp.tools as tools - -try: - import fcntl -except ImportError: - fcntl = None - -try: - from ssl import SSLError -except ImportError: - class SSLError(Exception): pass - -_logger = logging.getLogger(__name__) - -# TODO delete this for 6.2, it is still needed for 6.1. -class HttpLogHandler: - """ helper class for uniform log handling - Please define self._logger at each class that is derived from this - """ - _logger = None - - def log_message(self, format, *args): - self._logger.debug(format % args) # todo: perhaps other level - - def log_error(self, format, *args): - self._logger.error(format % args) - - def log_exception(self, format, *args): - self._logger.exception(format, *args) - - def log_request(self, code='-', size='-'): - self._logger.debug('"%s" %s %s', - self.requestline, str(code), str(size)) - -class StaticHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler): - _logger = logging.getLogger(__name__) - - _HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD'] } - - def __init__(self,request, client_address, server): - HTTPHandler.__init__(self,request,client_address,server) - document_root = tools.config.get('static_http_document_root', False) - assert document_root, "Please specify static_http_document_root in configuration, or disable static-httpd!" - self.__basepath = document_root - - def translate_path(self, path): - """Translate a /-separated PATH to the local filename syntax. - - Components that mean special things to the local file system - (e.g. drive or directory names) are ignored. (XXX They should - probably be diagnosed.) - - """ - # abandon query parameters - path = path.split('?',1)[0] - path = path.split('#',1)[0] - path = posixpath.normpath(urllib.unquote(path)) - words = path.split('/') - words = filter(None, words) - path = self.__basepath - for word in words: - if word in (os.curdir, os.pardir): continue - path = os.path.join(path, word) - return path - -def init_static_http(): - if not tools.config.get('static_http_enable', False): - return - - document_root = tools.config.get('static_http_document_root', False) - assert document_root, "Document root must be specified explicitly to enable static HTTP service (option --static-http-document-root)" - - base_path = tools.config.get('static_http_url_prefix', '/') - - reg_http_service(base_path, StaticHTTPHandler) - - _logger.info("Registered HTTP dir %s for %s", document_root, base_path) - -import security - -class OpenERPAuthProvider(AuthProvider): - """ Require basic authentication.""" - def __init__(self,realm='OpenERP User'): - self.realm = realm - self.auth_creds = {} - self.auth_tries = 0 - self.last_auth = None - - def authenticate(self, db, user, passwd, client_address): - try: - uid = security.login(db,user,passwd) - if uid is False: - return False - return user, passwd, db, uid - except Exception,e: - _logger.debug("Fail auth: %s" % e ) - return False - - def checkRequest(self,handler,path, db=False): - auth_str = handler.headers.get('Authorization',False) - try: - if not db: - db = handler.get_db_from_path(path) - except Exception: - if path.startswith('/'): - path = path[1:] - psp= path.split('/') - if len(psp)>1: - db = psp[0] - else: - #FIXME! - _logger.info("Wrong path: %s, failing auth" %path) - raise AuthRejectedExc("Authorization failed. Wrong sub-path.") - if self.auth_creds.get(db): - return True - if auth_str and auth_str.startswith('Basic '): - auth_str=auth_str[len('Basic '):] - (user,passwd) = base64.decodestring(auth_str).split(':') - _logger.info("Found user=\"%s\", passwd=\"***\" for db=\"%s\"", user, db) - acd = self.authenticate(db,user,passwd,handler.client_address) - if acd != False: - self.auth_creds[db] = acd - self.last_auth = db - return True - if self.auth_tries > 5: - _logger.info("Failing authorization after 5 requests w/o password") - raise AuthRejectedExc("Authorization failed.") - self.auth_tries += 1 - raise AuthRequiredExc(atype='Basic', realm=self.realm) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py index b31736ba998..5e72ba570b5 100644 --- a/openerp/service/wsgi_server.py +++ b/openerp/service/wsgi_server.py @@ -164,153 +164,6 @@ def wsgi_xmlrpc(environ, start_response): params, method = xmlrpclib.loads(data) return xmlrpc_return(start_response, service, method, params, string_faultcode) -def wsgi_webdav(environ, start_response): - pi = environ['PATH_INFO'] - if environ['REQUEST_METHOD'] == 'OPTIONS' and pi in ['*','/']: - return return_options(environ, start_response) - elif pi.startswith('/webdav'): - http_dir = websrv_lib.find_http_service(pi) - if http_dir: - path = pi[len(http_dir.path):] - if path.startswith('/'): - environ['PATH_INFO'] = path - else: - environ['PATH_INFO'] = '/' + path - return http_to_wsgi(http_dir)(environ, start_response) - -def return_options(environ, start_response): - # Microsoft specific header, see - # http://www.ibm.com/developerworks/rational/library/2089.html - if 'Microsoft' in environ.get('User-Agent', ''): - options = [('MS-Author-Via', 'DAV')] - else: - options = [] - options += [('DAV', '1 2'), ('Allow', 'GET HEAD PROPFIND OPTIONS REPORT')] - start_response("200 OK", [('Content-Length', str(0))] + options) - return [] - -def http_to_wsgi(http_dir): - """ - Turn a BaseHTTPRequestHandler into a WSGI entry point. - - Actually the argument is not a bare BaseHTTPRequestHandler but is wrapped - (as a class, so it needs to be instanciated) in a HTTPDir. - - This code is adapted from wbsrv_lib.MultiHTTPHandler._handle_one_foreign(). - It is a temporary solution: the HTTP sub-handlers (in particular the - document_webdav addon) have to be WSGIfied. - """ - def wsgi_handler(environ, start_response): - - headers = {} - for key, value in environ.items(): - if key.startswith('HTTP_'): - key = key[5:].replace('_', '-').title() - headers[key] = value - if key == 'CONTENT_LENGTH': - key = key.replace('_', '-').title() - headers[key] = value - if environ.get('Content-Type'): - headers['Content-Type'] = environ['Content-Type'] - - path = urllib.quote(environ.get('PATH_INFO', '')) - if environ.get('QUERY_STRING'): - path += '?' + environ['QUERY_STRING'] - - request_version = 'HTTP/1.1' # TODO - request_line = "%s %s %s\n" % (environ['REQUEST_METHOD'], path, request_version) - - class Dummy(object): - pass - - # Let's pretend we have a server to hand to the handler. - server = Dummy() - server.server_name = environ['SERVER_NAME'] - server.server_port = int(environ['SERVER_PORT']) - - # Initialize the underlying handler and associated auth. provider. - con = openerp.service.websrv_lib.noconnection(environ['wsgi.input']) - handler = http_dir.instanciate_handler(con, environ['REMOTE_ADDR'], server) - - # Populate the handler as if it is called by a regular HTTP server - # and the request is already parsed. - handler.wfile = StringIO.StringIO() - handler.rfile = environ['wsgi.input'] - handler.headers = headers - handler.command = environ['REQUEST_METHOD'] - handler.path = path - handler.request_version = request_version - handler.close_connection = 1 - handler.raw_requestline = request_line - handler.requestline = request_line - - # Handle authentication if there is an auth. provider associated to - # the handler. - if hasattr(handler, 'auth_provider'): - try: - handler.auth_provider.checkRequest(handler, path) - except websrv_lib.AuthRequiredExc, ae: - # Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the - # authorisation features of HTTP/1.1 - if request_version != 'HTTP/1.1' and ('Darwin/9.' not in handler.headers.get('User-Agent', '')): - start_response("403 Forbidden", []) - return [] - start_response("401 Authorization required", [ - ('WWW-Authenticate', '%s realm="%s"' % (ae.atype,ae.realm)), - # ('Connection', 'keep-alive'), - ('Content-Type', 'text/html'), - ('Content-Length', 4), # len(self.auth_required_msg) - ]) - return ['Blah'] # self.auth_required_msg - except websrv_lib.AuthRejectedExc,e: - start_response("403 %s" % (e.args[0],), []) - return [] - - method_name = 'do_' + handler.command - - # Support the OPTIONS method even when not provided directly by the - # handler. TODO I would prefer to remove it and fix the handler if - # needed. - if not hasattr(handler, method_name): - if handler.command == 'OPTIONS': - return return_options(environ, start_response) - start_response("501 Unsupported method (%r)" % handler.command, []) - return [] - - # Finally, call the handler's method. - try: - method = getattr(handler, method_name) - method() - # The DAV handler buffers its output and provides a _flush() - # method. - getattr(handler, '_flush', lambda: None)() - response = parse_http_response(handler.wfile.getvalue()) - response_headers = response.getheaders() - body = response.read() - start_response(str(response.status) + ' ' + response.reason, response_headers) - return [body] - except (websrv_lib.AuthRejectedExc, websrv_lib.AuthRequiredExc): - raise - except Exception, e: - start_response("500 Internal error", []) - return [] - - return wsgi_handler - -def parse_http_response(s): - """ Turn a HTTP response string into a httplib.HTTPResponse object.""" - class DummySocket(StringIO.StringIO): - """ - This is used to provide a StringIO to httplib.HTTPResponse - which, instead of taking a file object, expects a socket and - uses its makefile() method. - """ - def makefile(self, *args, **kw): - return self - response = httplib.HTTPResponse(DummySocket(s)) - response.begin() - return response - # WSGI handlers registered through the register_wsgi_handler() function below. module_handlers = [] # RPC endpoints registered through the register_rpc_endpoint() function below. @@ -342,7 +195,7 @@ def application_unproxied(environ, start_response): del threading.current_thread().dbname # Try all handlers until one returns some result (i.e. not None). - wsgi_handlers = [wsgi_xmlrpc, wsgi_webdav] + wsgi_handlers = [wsgi_xmlrpc] wsgi_handlers += module_handlers for handler in wsgi_handlers: result = handler(environ, start_response) diff --git a/openerp/tests/phantomtest.js b/openerp/tests/phantomtest.js index 63a3b2e33ff..5a6a9696cc0 100644 --- a/openerp/tests/phantomtest.js +++ b/openerp/tests/phantomtest.js @@ -6,7 +6,8 @@ function waitFor (ready, callback, timeout, timeoutMessageCallback) { (function waitLoop() { if(new Date - start > timeout) { - error(timeoutMessageCallback ? timeoutMessageCallback() : "Timeout after "+timeout+" ms"); + console.log('error', timeoutMessageCallback ? timeoutMessageCallback() : "Timeout after "+timeout+" ms"); + phantom.exit(1); } else if (ready()) { callback(); } else { @@ -15,10 +16,6 @@ function waitFor (ready, callback, timeout, timeoutMessageCallback) { }()); } -function error(message) { - console.log('error', message); - phantom.exit(1); -} function PhantomTest() { var self = this; this.options = JSON.parse(phantom.args[phantom.args.length-1]); @@ -49,10 +46,12 @@ function PhantomTest() { })); msg.push('(leaf frame on top)') } - error(JSON.stringify(msg.join('\n'))); + console.log('error', JSON.stringify(msg.join('\n'))); + phantom.exit(1); }; this.page.onAlert = function(message) { - error(message); + console.log('error', message); + phantom.exit(1); }; this.page.onConsoleMessage = function(message) { console.log(message); @@ -78,7 +77,8 @@ function PhantomTest() { if(!found) { console.log('Injecting', src, 'needed for', need); if(!self.page.injectJs(src)) { - error("Cannot inject " + src); + console.log('error', "Cannot inject " + src); + phantom.exit(1); } } } @@ -88,8 +88,9 @@ function PhantomTest() { self.page.evaluate(function () { var message = ("Timeout\nhref: " + window.location.href + "\nreferrer: " + document.referrer - + "\n\n" + document.body.innerHTML).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*"); - error(message); + + "\n\n" + (document.body && document.body.innerHTML)).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*"); + console.log('error', message); + phantom.exit(1); }); }, self.timeout); @@ -108,7 +109,8 @@ function PhantomTest() { var url = self.origin + url_path; self.page.open(url, function(status) { if (status !== 'success') { - error("failed to load " + url) + console.log('error', "failed to load " + url); + phantom.exit(1); } else { console.log('loaded', url, status); // process ready @@ -119,7 +121,7 @@ function PhantomTest() { try { console.log("page.evaluate eval expr:", ready); r = !!eval(ready); - } catch(ex) { + } catch(ex) { } console.log("page.evaluate eval result:", r); return r; diff --git a/openerp/tools/config.py b/openerp/tools/config.py index 8313f4d786e..6b21bda8b4f 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -152,19 +152,11 @@ class configmanager(object): parser.add_option_group(group) # WEB - # TODO move to web addons after MetaOption merge group = optparse.OptionGroup(parser, "Web interface Configuration") group.add_option("--db-filter", dest="dbfilter", default='.*', help="Filter listed database", metavar="REGEXP") parser.add_option_group(group) - # Static HTTP - group = optparse.OptionGroup(parser, "Static HTTP service") - group.add_option("--static-http-enable", dest="static_http_enable", action="store_true", my_default=False, help="enable static HTTP service for serving plain HTML files") - group.add_option("--static-http-document-root", dest="static_http_document_root", help="specify the directory containing your static HTML files (e.g '/var/www/')") - group.add_option("--static-http-url-prefix", dest="static_http_url_prefix", help="specify the URL root prefix where you want web browsers to access your static HTML files (e.g '/')") - parser.add_option_group(group) - # Testing Group group = optparse.OptionGroup(parser, "Testing Configuration") group.add_option("--test-file", dest="test_file", my_default=False, @@ -313,9 +305,10 @@ class configmanager(object): self.options[option.dest] = option.my_default self.casts[option.dest] = option - self.parse_config(None, False) + # generate default config + self._parse_config() - def parse_config(self, args=None, complete=True): + def parse_config(self, args=None): """ Parse the configuration file (if any) and the command-line arguments. @@ -329,10 +322,12 @@ class configmanager(object): Typical usage of this method: openerp.tools.config.parse_config(sys.argv[1:]) - - :param complete: this is a hack used in __init__(), leave it to True. - """ + self._parse_config(args) + openerp.netsvc.init_logger() + openerp.modules.module.initialize_sys_path() + + def _parse_config(self, args=None): if args is None: args = [] opt, args = self.parser.parse_args(args) @@ -391,7 +386,6 @@ class configmanager(object): 'db_maxconn', 'import_partial', 'addons_path', 'xmlrpc', 'syslog', 'without_demo', 'timezone', 'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs', - 'static_http_enable', 'static_http_document_root', 'static_http_url_prefix', 'secure_cert_file', 'secure_pkey_file', 'dbfilter', 'log_handler', 'log_level', 'log_db' ] @@ -503,8 +497,6 @@ class configmanager(object): openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(',')) else: openerp.conf.server_wide_modules = ['web','web_kanban'] - if complete: - openerp.modules.module.initialize_sys_path() def _generate_pgpassfile(self): """ @@ -662,6 +654,9 @@ class configmanager(object): os.chmod(d, 0700) return d + def filestore(self, dbname): + return os.path.join(self['data_dir'], 'filestore', dbname) + config = configmanager() diff --git a/openerp/tools/func.py b/openerp/tools/func.py index ca8bba6bd49..89cdf9fb622 100644 --- a/openerp/tools/func.py +++ b/openerp/tools/func.py @@ -3,7 +3,7 @@ # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). -# Copyright (C) 2010 OpenERP s.a. (). +# Copyright (C) 2010, 2014 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 @@ -20,11 +20,39 @@ # ############################################################################## -__all__ = ['synchronized'] +__all__ = ['synchronized', 'lazy_property'] from functools import wraps from inspect import getsourcefile + +class lazy_property(object): + """ Decorator for a lazy property of an object, i.e., an object attribute + that is determined by the result of a method call evaluated once. To + reevaluate the property, simply delete the attribute on the object, and + get it again. + """ + def __init__(self, fget): + self.fget = fget + self.name = fget.__name__ + + def __get__(self, obj, cls): + if obj is None: + return self + value = self.fget(obj) + setattr(obj, self.name, value) + return value + + @staticmethod + def reset_all(obj): + """ Reset all lazy properties on the instance `obj`. """ + cls = type(obj) + obj_dict = obj.__dict__ + for name in obj_dict.keys(): + if isinstance(getattr(cls, name, None), lazy_property): + obj_dict.pop(name) + + def synchronized(lock_attr='_lock'): def decorator(func): @wraps(func) diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 251905137c0..bb61fe2e7a2 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -3,7 +3,7 @@ # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). -# Copyright (C) 2010-2013 OpenERP s.a. (). +# Copyright (C) 2010-2014 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 @@ -83,7 +83,8 @@ def exec_pg_command(name, *args): raise Exception('Couldn\'t find %s' % name) args2 = (prog,) + args - return subprocess.call(args2) + with open(os.devnull) as dn: + return subprocess.call(args2, stdout=dn, stderr=subprocess.STDOUT) def exec_pg_command_pipe(name, *args): prog = find_pg_tool(name)