From 48d1e2d8796e9f5d7dc117b56ae75b6e35eee22d Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 18 Feb 2014 17:15:05 +0100 Subject: [PATCH 01/25] [ADD] Request with lazy rendering support bzr revid: fme@openerp.com-20140218161505-bmfcc2jq16vvflzn --- openerp/http.py | 86 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/openerp/http.py b/openerp/http.py index 74d833e4730..114d3ccfc8e 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -174,6 +174,9 @@ class WebRequest(object): self._cr = self.registry.db.cursor() return self._cr + def __getattr__(self, attr): + return getattr(self.httprequest, attr) + def __enter__(self): _request_stack.push(self) return self @@ -265,8 +268,19 @@ def route(route=None, **kw): else: routes = [route] routing['routes'] = routes - f.routing = routing - return f + @functools.wraps(f) + def response_wrap(*args, **kw): + response = f(*args, **kw) + if isinstance(response, Response) or request.func_request_type == 'json': + return response + elif isinstance(response, LazyResponse): + raise "TODO: remove LazyResponses ???" + else: + response = Response.force_type(response) + response.set_default() + return response + response_wrap.routing = routing + return response_wrap return decorator class JsonRequest(WebRequest): @@ -379,7 +393,7 @@ class JsonRequest(WebRequest): mime = 'application/json' body = simplejson.dumps(response) - r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))]) + r = Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))]) return r def serialize_exception(e): @@ -439,7 +453,7 @@ class HttpRequest(WebRequest): def dispatch(self): # TODO: refactor this correctly. This is a quick fix for pos demo. if request.httprequest.method == 'OPTIONS' and request.func and request.func.routing.get('cors'): - response = werkzeug.wrappers.Response(status=200) + response = Response(status=200) response.headers.set('Access-Control-Allow-Origin', request.func.routing['cors']) methods = 'GET, POST' if request.func_request_type == 'json': @@ -453,7 +467,7 @@ class HttpRequest(WebRequest): r = self._call_function(**self.params) if not r: - r = werkzeug.wrappers.Response(status=204) # no content + r = Response(status=204) # no content return r def make_response(self, data, headers=None, cookies=None): @@ -470,12 +484,24 @@ class HttpRequest(WebRequest): :type headers: ``[(name, value)]`` :param collections.Mapping cookies: cookies to set on the client """ - response = werkzeug.wrappers.Response(data, headers=headers) + response = Response(data, headers=headers) if cookies: for k, v in cookies.iteritems(): response.set_cookie(k, v) return response + def render(self, template, qcontext=None, mimetype='text/html', **kw): + """ Lazy render of QWeb template. + + The actual rendering of the given template will occur at then end of + the dispatching. Meanwhile, the template and/or qcontext can be + altered or even replaced by a static response. + + :param basestring template: template to render + :param dict qcontext: Rendering context to use + """ + return Response(template=template, qcontext=qcontext, mimetype=mimetype, **kw) + def not_found(self, description=None): """ Helper for 404 response, return its result from the method """ @@ -892,6 +918,42 @@ mimetypes.add_type('application/font-woff', '.woff') mimetypes.add_type('application/vnd.ms-fontobject', '.eot') mimetypes.add_type('application/x-font-ttf', '.ttf') +class Response(werkzeug.wrappers.Response): + """ Response object passed through controller route chain. + + In addition to the werkzeug.wrappers.Response parameters, this + classe's constructor can take the following additional parameters + for QWeb Lazy Rendering. + + :param basestring template: template to render + :param dict qcontext: Rendering context to use + :param int uid: User id to use for the ir.ui.view render call + """ + def __init__(self, *args, **kw): + template = kw.pop('template', None) + qcontext = kw.pop('qcontext', None) + uid = kw.pop('uid', None) + self.set_default(template, qcontext, uid) + super(Response, self).__init__(*args, **kw) + + def set_default(self, template=None, qcontext=None, uid=None): + self.template = template + self.qcontext = qcontext or dict() + self.uid = uid + + @property + def is_qweb(self): + return self.template is not None + + def render(self): + view_obj = request.registry["ir.ui.view"] + uid = self.uid or request.uid or openerp.SUPERUSER_ID + return view_obj.render(request.cr, uid, self.template, self.qcontext, context=request.context) + + def flatten(self): + self.response.append(self.render()) + self.template = None + class LazyResponse(werkzeug.wrappers.Response): """ Lazy werkzeug response. API not yet frozen""" @@ -1039,6 +1101,7 @@ class Root(object): return HttpRequest(httprequest) def get_response(self, httprequest, result, explicit_session): + # TODO: Remove LazyResponse if isinstance(result, LazyResponse): try: result.process() @@ -1048,8 +1111,17 @@ class Root(object): else: raise + if isinstance(result, Response) and result.is_qweb: + try: + result.flatten() + except(Exception), e: + if request.db: + result = request.registry['ir.http']._handle_exception(e) + else: + raise + if isinstance(result, basestring): - response = werkzeug.wrappers.Response(result, mimetype='text/html') + response = Response(result, mimetype='text/html') else: response = result From 33159381788385316b78a5ae28b9f8936db6f177 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 19 Feb 2014 11:29:03 +0100 Subject: [PATCH 02/25] [FIX] Response default mimetype bzr revid: fme@openerp.com-20140219102903-h8n070qqbbhlnenr --- openerp/http.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openerp/http.py b/openerp/http.py index 114d3ccfc8e..ed66e13edb1 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -275,10 +275,15 @@ def route(route=None, **kw): return response elif isinstance(response, LazyResponse): raise "TODO: remove LazyResponses ???" - else: + elif isinstance(response, werkzeug.wrappers.BaseResponse): response = Response.force_type(response) response.set_default() return response + elif isinstance(response, basestring): + return Response(response) + else: + raise "TODO: shall we autorise this ?" + return response response_wrap.routing = routing return response_wrap return decorator @@ -490,7 +495,7 @@ class HttpRequest(WebRequest): response.set_cookie(k, v) return response - def render(self, template, qcontext=None, mimetype='text/html', **kw): + def render(self, template, qcontext=None, **kw): """ Lazy render of QWeb template. The actual rendering of the given template will occur at then end of @@ -500,7 +505,7 @@ class HttpRequest(WebRequest): :param basestring template: template to render :param dict qcontext: Rendering context to use """ - return Response(template=template, qcontext=qcontext, mimetype=mimetype, **kw) + return Response(template=template, qcontext=qcontext, **kw) def not_found(self, description=None): """ Helper for 404 response, return its result from the method @@ -929,6 +934,7 @@ class Response(werkzeug.wrappers.Response): :param dict qcontext: Rendering context to use :param int uid: User id to use for the ir.ui.view render call """ + default_mimetype = 'text/html' def __init__(self, *args, **kw): template = kw.pop('template', None) qcontext = kw.pop('qcontext', None) From 226b15415433247304b660cc8a932b8e41f253d9 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 19 Feb 2014 14:51:28 +0100 Subject: [PATCH 03/25] [FIX] convert endpoint responses only if it's the current request endpoint bzr revid: fme@openerp.com-20140219135128-6rdnymufpfo1xyvf --- openerp/http.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/openerp/http.py b/openerp/http.py index ed66e13edb1..ebca5efde7e 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -129,6 +129,7 @@ class WebRequest(object): self.session_id = httprequest.session.sid self.disable_db = False self.uid = None + self.endpoint = None self.func = None self.func_arguments = {} self.auth_method = None @@ -199,6 +200,8 @@ class WebRequest(object): arguments = dict((k, v) for k, v in arguments.iteritems() if not k.startswith("_ignored_")) + self.endpoint = func + # TODO: get rid of func_* self.func = func self.func_request_type = func.routing['type'] self.func_arguments = arguments @@ -271,20 +274,22 @@ def route(route=None, **kw): @functools.wraps(f) def response_wrap(*args, **kw): response = f(*args, **kw) - if isinstance(response, Response) or request.func_request_type == 'json': - return response - elif isinstance(response, LazyResponse): - raise "TODO: remove LazyResponses ???" - elif isinstance(response, werkzeug.wrappers.BaseResponse): - response = Response.force_type(response) - response.set_default() - return response - elif isinstance(response, basestring): - return Response(response) - else: - raise "TODO: shall we autorise this ?" - return response + if request.endpoint.original == f: + if isinstance(response, Response) or request.func_request_type == 'json': + return response + elif isinstance(response, LazyResponse): + raise "TODO: remove LazyResponses ???" + elif isinstance(response, werkzeug.wrappers.BaseResponse): + response = Response.force_type(response) + response.set_default() + return response + elif isinstance(response, basestring): + return Response(response) + else: + raise "TODO: shall we autorise this ?" + return response response_wrap.routing = routing + response_wrap.original_func = f return response_wrap return decorator @@ -561,6 +566,7 @@ class Controller(object): class EndPoint(object): def __init__(self, method, routing): self.method = method + self.original = getattr(method, 'original_func', method) self.routing = routing def __call__(self, *args, **kw): return self.method(*args, **kw) From ae87d00ffaae906c2664ccbee98be72aa268ea0e Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 19 Feb 2014 15:23:13 +0100 Subject: [PATCH 04/25] [REM] Completely removed LazyResponse bzr revid: fme@openerp.com-20140219142313-1upmwem8debsqxy4 --- openerp/http.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/openerp/http.py b/openerp/http.py index ebca5efde7e..7c4fbc72067 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -277,8 +277,6 @@ def route(route=None, **kw): if request.endpoint.original == f: if isinstance(response, Response) or request.func_request_type == 'json': return response - elif isinstance(response, LazyResponse): - raise "TODO: remove LazyResponses ???" elif isinstance(response, werkzeug.wrappers.BaseResponse): response = Response.force_type(response) response.set_default() @@ -966,20 +964,6 @@ class Response(werkzeug.wrappers.Response): self.response.append(self.render()) self.template = None -class LazyResponse(werkzeug.wrappers.Response): - """ Lazy werkzeug response. - API not yet frozen""" - - def __init__(self, callback, status_code=None, **kwargs): - super(LazyResponse, self).__init__(mimetype='text/html') - if status_code: - self.status_code = status_code - self.callback = callback - self.params = kwargs - def process(self): - response = self.callback(**self.params) - self.response.append(response) - class DisableCacheMiddleware(object): def __init__(self, app): self.app = app @@ -1113,16 +1097,6 @@ class Root(object): return HttpRequest(httprequest) def get_response(self, httprequest, result, explicit_session): - # TODO: Remove LazyResponse - if isinstance(result, LazyResponse): - try: - result.process() - except(Exception), e: - if request.db: - result = request.registry['ir.http']._handle_exception(e) - else: - raise - if isinstance(result, Response) and result.is_qweb: try: result.flatten() From d5dbf9184daa05f3cccf9fe103069d0b67d576bc Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 19 Feb 2014 16:32:01 +0100 Subject: [PATCH 05/25] [IMP] Warn if invalid response type is returned for a request@type=http bzr revid: fme@openerp.com-20140219153201-hgyk9c83hwqe49yl --- openerp/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/http.py b/openerp/http.py index 7c4fbc72067..c6075baf281 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -284,7 +284,7 @@ def route(route=None, **kw): elif isinstance(response, basestring): return Response(response) else: - raise "TODO: shall we autorise this ?" + _logger.warn(" returns an invalid response type for an http request" % (f.__module__, f.__name__)) return response response_wrap.routing = routing response_wrap.original_func = f From 17f19926980ddf0ebb79b8db438ca0ade5e5a86c Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 20 Feb 2014 12:50:16 +0100 Subject: [PATCH 06/25] [FIX] Correctly handle super() for decorated routes and keep the request type on all functions bzr revid: fme@openerp.com-20140220115016-axebl9ei9dvysko0 --- openerp/http.py | 82 +++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/openerp/http.py b/openerp/http.py index c6075baf281..507afc748fd 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -130,12 +130,9 @@ class WebRequest(object): self.disable_db = False self.uid = None self.endpoint = None - self.func = None - self.func_arguments = {} self.auth_method = None self._cr_cm = None self._cr = None - self.func_request_type = None # set db/uid trackers - they're cleaned up at the WSGI # dispatching phase in openerp.service.wsgi_server.application if self.db: @@ -195,39 +192,36 @@ class WebRequest(object): self.disable_db = True self.uid = None - def set_handler(self, func, arguments, auth): + def set_handler(self, endpoint, arguments, auth): # is this needed ? arguments = dict((k, v) for k, v in arguments.iteritems() if not k.startswith("_ignored_")) - self.endpoint = func - # TODO: get rid of func_* - self.func = func - self.func_request_type = func.routing['type'] - self.func_arguments = arguments + endpoint.arguments = arguments + self.endpoint = endpoint self.auth_method = auth def _call_function(self, *args, **kwargs): request = self - if self.func_request_type != self._request_type: + if self.endpoint.routing['type'] != self._request_type: raise Exception("%s, %s: Function declared as capable of handling request of type '%s' but called with a request of type '%s'" \ - % (self.func, self.httprequest.path, self.func_request_type, self._request_type)) + % (self.endpoint.original, self.httprequest.path, self.endpoint.routing['type'], self._request_type)) - kwargs.update(self.func_arguments) + kwargs.update(self.endpoint.arguments) # Backward for 7.0 - if getattr(self.func.method, '_first_arg_is_req', False): + if self.endpoint.first_arg_is_req: args = (request,) + args # Correct exception handling and concurency retry @service_model.check def checked_call(___dbname, *a, **kw): - return self.func(*a, **kw) + return self.endpoint(*a, **kw) # FIXME: code and rollback management could be cleaned try: if self.db: return checked_call(self.db, *args, **kwargs) - return self.func(*args, **kwargs) + return self.endpoint(*args, **kwargs) except Exception: if self._cr: self._cr.rollback() @@ -274,17 +268,16 @@ def route(route=None, **kw): @functools.wraps(f) def response_wrap(*args, **kw): response = f(*args, **kw) - if request.endpoint.original == f: - if isinstance(response, Response) or request.func_request_type == 'json': - return response - elif isinstance(response, werkzeug.wrappers.BaseResponse): - response = Response.force_type(response) - response.set_default() - return response - elif isinstance(response, basestring): - return Response(response) - else: - _logger.warn(" returns an invalid response type for an http request" % (f.__module__, f.__name__)) + if isinstance(response, Response) or f.routing_type == 'json': + return response + elif isinstance(response, werkzeug.wrappers.BaseResponse): + response = Response.force_type(response) + response.set_default() + return response + elif isinstance(response, basestring): + return Response(response) + else: + _logger.warn(" returns an invalid response type for an http request" % (f.__module__, f.__name__)) return response response_wrap.routing = routing response_wrap.original_func = f @@ -460,14 +453,14 @@ class HttpRequest(WebRequest): def dispatch(self): # TODO: refactor this correctly. This is a quick fix for pos demo. - if request.httprequest.method == 'OPTIONS' and request.func and request.func.routing.get('cors'): + if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'): response = Response(status=200) - response.headers.set('Access-Control-Allow-Origin', request.func.routing['cors']) + response.headers.set('Access-Control-Allow-Origin', request.endpoint.routing['cors']) methods = 'GET, POST' - if request.func_request_type == 'json': + if request.endpoint.routing['type'] == 'json': methods = 'POST' - elif request.func.routing.get('methods'): - methods = ', '.join(request.func.routing['methods']) + elif request.endpoint.routing.get('methods'): + methods = ', '.join(request.endpoint.routing['methods']) response.headers.set('Access-Control-Allow-Methods', methods) response.headers.set('Access-Control-Max-Age',60*60*24) response.headers.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept') @@ -566,6 +559,13 @@ class EndPoint(object): self.method = method self.original = getattr(method, 'original_func', method) self.routing = routing + self.arguments = {} + + @property + def first_arg_is_req(self): + # Backward for 7.0 + return getattr(self.method, '_first_arg_is_req', False) + def __call__(self, *args, **kw): return self.method(*args, **kw) @@ -588,9 +588,19 @@ def routing_map(modules, nodb_only, converters=None): if inspect.ismethod(mv) and hasattr(mv, 'routing'): routing = dict(type='http', auth='user', methods=None, routes=None) methods_done = list() + routing_type = None for claz in reversed(mv.im_class.mro()): fn = getattr(claz, mv.func_name, None) if fn and hasattr(fn, 'routing') and fn not in methods_done: + fn_type = fn.routing.get('type') + if not routing_type: + routing_type = fn_type + else: + if fn_type and routing_type != fn_type: + _logger.warn("Subclass re-defines with different type than original." + " Will use original type: %r", fn.__module__, fn.__name__, routing_type) + fn.routing['type'] = routing_type + fn.original_func.routing_type = routing_type methods_done.append(fn) routing.update(fn.routing) if not nodb_only or nodb_only == (routing['auth'] == "none"): @@ -1123,13 +1133,13 @@ class Root(object): response.set_cookie('session_id', httprequest.session.sid, max_age=90 * 24 * 60 * 60) # Support for Cross-Origin Resource Sharing - if request.func and 'cors' in request.func.routing: - response.headers.set('Access-Control-Allow-Origin', request.func.routing['cors']) + if request.endpoint and 'cors' in request.endpoint.routing: + response.headers.set('Access-Control-Allow-Origin', request.endpoint.routing['cors']) methods = 'GET, POST' - if request.func_request_type == 'json': + if request.endpoint.routing['type'] == 'json': methods = 'POST' - elif request.func.routing.get('methods'): - methods = ', '.join(request.func.routing['methods']) + elif request.endpoint.routing['methods']: + methods = ', '.join(request.endpoint.routing['methods']) response.headers.set('Access-Control-Allow-Methods', methods) return response From d7ea890cbe9915fa964bed96db579bac16b31a30 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 20 Feb 2014 14:29:22 +0100 Subject: [PATCH 07/25] [FIX] Don't crash if no host specified bzr revid: fme@openerp.com-20140220132922-wax6e05u8x06lepu --- openerp/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/http.py b/openerp/http.py index 507afc748fd..90dbb6bbe86 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -1200,7 +1200,7 @@ def db_list(force=False, httprequest=None): def db_filter(dbs, httprequest=None): httprequest = httprequest or request.httprequest - h = httprequest.environ['HTTP_HOST'].split(':')[0] + h = httprequest.environ.get('HTTP_HOST', '').split(':')[0] d = h.split('.')[0] r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d) dbs = [i for i in dbs if re.match(r, i)] From 587ada964ee238814cb82fbba14d606ab9be007f Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 20 Feb 2014 14:37:14 +0100 Subject: [PATCH 08/25] [IMP] Refactor CROS support bzr revid: fme@openerp.com-20140220133714-igpobx20mhzlxi20 --- openerp/http.py | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/openerp/http.py b/openerp/http.py index 90dbb6bbe86..2a5b89f7ee7 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -452,19 +452,12 @@ class HttpRequest(WebRequest): self.params = params def dispatch(self): - # TODO: refactor this correctly. This is a quick fix for pos demo. if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'): - response = Response(status=200) - response.headers.set('Access-Control-Allow-Origin', request.endpoint.routing['cors']) - methods = 'GET, POST' - if request.endpoint.routing['type'] == 'json': - methods = 'POST' - elif request.endpoint.routing.get('methods'): - methods = ', '.join(request.endpoint.routing['methods']) - response.headers.set('Access-Control-Allow-Methods', methods) - response.headers.set('Access-Control-Max-Age',60*60*24) - response.headers.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept') - return response + headers = { + 'Access-Control-Max-Age': 60 * 60 * 24, + 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept' + } + return Response(status=200, headers=headers) r = self._call_function(**self.params) if not r: @@ -953,13 +946,22 @@ class Response(werkzeug.wrappers.Response): template = kw.pop('template', None) qcontext = kw.pop('qcontext', None) uid = kw.pop('uid', None) - self.set_default(template, qcontext, uid) super(Response, self).__init__(*args, **kw) + self.set_default(template, qcontext, uid) def set_default(self, template=None, qcontext=None, uid=None): self.template = template self.qcontext = qcontext or dict() self.uid = uid + # Support for Cross-Origin Resource Sharing + if request.endpoint and 'cors' in request.endpoint.routing: + self.headers.set('Access-Control-Allow-Origin', request.endpoint.routing['cors']) + methods = 'GET, POST' + if request.endpoint.routing['type'] == 'json': + methods = 'POST' + elif request.endpoint.routing.get('methods'): + methods = ', '.join(request.endpoint.routing['methods']) + self.headers.set('Access-Control-Allow-Methods', methods) @property def is_qweb(self): @@ -1132,16 +1134,6 @@ class Root(object): if not explicit_session and hasattr(response, 'set_cookie'): response.set_cookie('session_id', httprequest.session.sid, max_age=90 * 24 * 60 * 60) - # Support for Cross-Origin Resource Sharing - if request.endpoint and 'cors' in request.endpoint.routing: - response.headers.set('Access-Control-Allow-Origin', request.endpoint.routing['cors']) - methods = 'GET, POST' - if request.endpoint.routing['type'] == 'json': - methods = 'POST' - elif request.endpoint.routing['methods']: - methods = ', '.join(request.endpoint.routing['methods']) - response.headers.set('Access-Control-Allow-Methods', methods) - return response def dispatch(self, environ, start_response): From b79f35471ecd27f1372849a9fd63cac7f740d2fd Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 21 Feb 2014 10:55:00 +0100 Subject: [PATCH 10/25] [FIX] testing: rpc tests works directly on the source database instead of copying it bzr revid: chs@openerp.com-20140221095500-5cer5exzdei76y1d --- addons/web/static/src/js/testing.js | 33 +++++------------------------ 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js index c4f5aeebd60..41dd2f03ad9 100644 --- a/addons/web/static/src/js/testing.js +++ b/addons/web/static/src/js/testing.js @@ -305,39 +305,16 @@ openerp.testing = {}; case 'rpc': async = true; (function () { - // Bunch of random base36 characters - var dbname = 'test_' + Math.random().toString(36).slice(2); - // Add db setup/teardown at the start of the stack + // Add a session setup at the start of the stack to ensure user is logged in case_stack = case_stack.unshift(function (instance) { // FIXME hack: don't want the session to go through shitty loading process of everything instance.session.session_init = testing.noop; instance.session.load_modules = testing.noop; instance.session.session_bind(); - return instance.session.rpc('/web/database/duplicate', { - fields: [ - {name: 'super_admin_pwd', value: db.supadmin}, - {name: 'db_original_name', value: db.source}, - {name: 'db_name', value: dbname} - ] - }).then(function (result) { - if (result.error) { - return $.Deferred().reject(result.error).promise(); - } - return instance.session.session_authenticate( - dbname, 'admin', db.password, true); - }); - }, function (instance) { - return instance.session.rpc('/web/database/drop', { - fields: [ - {name: 'drop_pwd', value: db.supadmin}, - {name: 'drop_db', value: dbname} - ] - }).then(function (result) { - if (result.error) { - return $.Deferred().reject(result.error).promise(); - } - return result; - }); + if (instance.session.session_is_valid()) { + return $.when(); + } + return instance.session.session_authenticate(db.source, 'admin', db.password, true); }); })(); } From 636b3ff2dd331b0c488c24f68ab266b7f4350c41 Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Fri, 21 Feb 2014 10:55:43 +0100 Subject: [PATCH 11/25] [FIX] testing: only load js tests of loaded modules bzr revid: chs@openerp.com-20140221095543-07lv6as8ym17mj7z --- addons/web/controllers/testing.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py index 598270cfe54..f58482f7759 100644 --- a/addons/web/controllers/testing.py +++ b/addons/web/controllers/testing.py @@ -8,7 +8,7 @@ import operator import os from mako.template import Template -from openerp.modules import module +from openerp.modules import module, registry from openerp import http from openerp.http import request @@ -90,7 +90,11 @@ class TestRunnerController(http.Controller): @http.route('/web/tests', type='http', auth="none") def index(self, mod=None, **kwargs): - ms = module.get_modules() + source = kwargs.get('source') + if source: + ms = list(registry.RegistryManager.get(source)._init_modules) + else: + ms = module.get_modules() manifests = dict( (name, desc) for name, desc in zip(ms, map(self.load_manifest, ms)) From d27526d088653a5f9e892a1fe48829724274d8ee Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 21 Feb 2014 13:45:57 +0100 Subject: [PATCH 12/25] [IMP] JS unit tests split automated suite so web only runs its own JS tests, and web_tests_demo runs its own. Also reversed changes to index. todo: use hack cursor thing? bzr revid: xmo@openerp.com-20140221124557-s875nj0xrf2t85i9 --- addons/web/controllers/testing.py | 8 ++------ addons/web/tests/test_js.py | 15 +++++++++------ addons/web_tests_demo/tests/__init__.py | 2 ++ addons/web_tests_demo/tests/test_js.py | 6 ++++++ 4 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 addons/web_tests_demo/tests/__init__.py create mode 100644 addons/web_tests_demo/tests/test_js.py diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py index f58482f7759..598270cfe54 100644 --- a/addons/web/controllers/testing.py +++ b/addons/web/controllers/testing.py @@ -8,7 +8,7 @@ import operator import os from mako.template import Template -from openerp.modules import module, registry +from openerp.modules import module from openerp import http from openerp.http import request @@ -90,11 +90,7 @@ class TestRunnerController(http.Controller): @http.route('/web/tests', type='http', auth="none") def index(self, mod=None, **kwargs): - source = kwargs.get('source') - if source: - ms = list(registry.RegistryManager.get(source)._init_modules) - else: - ms = module.get_modules() + ms = module.get_modules() manifests = dict( (name, desc) for name, desc in zip(ms, map(self.load_manifest, ms)) diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py index 0d4a9f35a85..0bab1de96a5 100644 --- a/addons/web/tests/test_js.py +++ b/addons/web/tests/test_js.py @@ -1,21 +1,24 @@ +import urllib import urlparse from openerp import sql_db, tools from qunitsuite.suite import QUnitSuite class WebSuite(QUnitSuite): - def __init__(self): + def __init__(self, module): url = urlparse.urlunsplit([ 'http', 'localhost:{port}'.format(port=tools.config['xmlrpc_port']), '/web/tests', - 'mod=*&source={db}&supadmin={supadmin}&password={password}'.format( - db=tools.config['db_name'], - supadmin=tools.config['admin_passwd'], - password='admin'), + urllib.urlencode({ + 'mod': module, + 'source': tools.config['db_name'], + 'supadmin': tools.config['admin_passwd'], + 'password': 'admin', + }), '' ]) super(WebSuite, self).__init__(url, 50000) def load_tests(loader, standard_tests, _): - standard_tests.addTest(WebSuite()) + standard_tests.addTest(WebSuite('web')) return standard_tests diff --git a/addons/web_tests_demo/tests/__init__.py b/addons/web_tests_demo/tests/__init__.py new file mode 100644 index 00000000000..1db9e399fd2 --- /dev/null +++ b/addons/web_tests_demo/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import test_js diff --git a/addons/web_tests_demo/tests/test_js.py b/addons/web_tests_demo/tests/test_js.py new file mode 100644 index 00000000000..fb97176bad2 --- /dev/null +++ b/addons/web_tests_demo/tests/test_js.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from openerp.addons.web.tests.test_js import WebSuite + +def load_tests(loader, standard_tests, _): + standard_tests.addTest(WebSuite('web_tests_demo')) + return standard_tests From 42763039f0ceb3fcaf875d6afbc29d9ea9827350 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 21 Feb 2014 15:35:12 +0100 Subject: [PATCH 13/25] [IMP] filter out phantomjs warning on OSX bzr revid: xmo@openerp.com-20140221143512-0qmmxq0gibmdgk6d --- addons/web/tests/qunitsuite/suite.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/addons/web/tests/qunitsuite/suite.py b/addons/web/tests/qunitsuite/suite.py index 1b6bc7ae0ac..54d2e18e36e 100644 --- a/addons/web/tests/qunitsuite/suite.py +++ b/addons/web/tests/qunitsuite/suite.py @@ -59,7 +59,7 @@ class QUnitSuite(unittest.TestSuite): 'timeout': self.timeout, 'inject': os.path.join(ROOT, 'qunit-phantomjs-bridge.js') }) - ], stdout=subprocess.PIPE) + ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) try: while True: @@ -75,7 +75,12 @@ class QUnitSuite(unittest.TestSuite): phantom.terminate() def process(self, line, result): - args = json.loads(line) + try: + args = json.loads(line) + except ValueError: # phantomjs stderr + if 'CoreText' not in line: + print line + return False event_name = args[0] if event_name == 'qunit.done': From bb7eb5269917cfde51abb50a4dca175ddd371fb9 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 21 Feb 2014 15:35:18 +0100 Subject: [PATCH 14/25] [REM] RPC from js/qunit tests They're a pain in the ass, they never worked right and they're basically useless. Screw it. bzr revid: xmo@openerp.com-20140221143518-hv3rjkw2b00ughvh --- addons/web/controllers/testing.py | 9 +--- addons/web/static/src/js/testing.js | 50 ------------------- addons/web/static/test/jsonrpc.js | 58 ----------------------- addons/web_tests_demo/static/test/demo.js | 15 ------ 4 files changed, 1 insertion(+), 131 deletions(-) diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py index 598270cfe54..c99efed4294 100644 --- a/addons/web/controllers/testing.py +++ b/addons/web/controllers/testing.py @@ -52,7 +52,6 @@ TESTING = Template(u"""