From 76e04a8ea07b02902cab784d2d4ee39106772a5d Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 11 Jun 2013 18:38:51 +0200 Subject: [PATCH 01/54] added noauth and nodb bzr revid: nicolas.vanhoren@openerp.com-20130611163851-okd35aitcj4jnh5i --- addons/web/http.py | 62 ++++++++++++++++++++++++++++++++++--------- addons/web/session.py | 27 ++++++++++++++----- 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 2dc41c8cad9..20431eb4233 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -83,10 +83,13 @@ class WebRequest(object): ``bool``, indicates whether the debug mode is active on the client """ - def __init__(self, request): - self.httprequest = request + def __init__(self, httprequest, method): + self.httprequest = httprequest self.httpresponse = None - self.httpsession = request.session + self.httpsession = httprequest.session + self.db = None + self.uid = None + self.method = method def init(self, params): self.params = dict(params) @@ -97,6 +100,9 @@ class WebRequest(object): self.session = session.OpenERPSession() self.httpsession[self.session_id] = self.session + self.authenticate() + + # TODO: remove this shit # set db/uid trackers - they're cleaned up at the WSGI # dispatching phase in openerp.service.wsgi_server.application if self.session._db: @@ -121,12 +127,38 @@ class WebRequest(object): # we use _ as seprator where RFC2616 uses '-' self.lang = lang.replace('-', '_') + def authenticate(self): + if self.method.auth == "nodb": + self.db = None + self.uid = None + elif self.method.auth == "noauth": + self.db = (self.session._db or openerp.addons.web.controllers.main.db_monodb(self)).lower() + self.uid = None + else: + self.session.check_security() + self.db = self.session._db + self.uid = self.session_uid + + def registry(self): + return openerp.modules.registry.RegistryManager.get(self.db) + + @contextlib.contextmanager + def cr(self): + with registry.cursor() as cr: + yield cr + @contextlib.contextmanager def registry_cr(self): - dbname = self.session._db or openerp.addons.web.controllers.main.db_monodb(self) - registry = openerp.modules.registry.RegistryManager.get(dbname.lower()) - with registry.cursor() as cr: - yield (registry, cr) + with self.cr() as cr: + yield (self.registry(), cr) + +def noauth(f): + f.auth = "noauth" + return f + +def nodb(f): + f.auth = "nodb" + return f def reject_nonliteral(dct): if '__ref' in dct: @@ -167,13 +199,14 @@ class JsonRequest(WebRequest): "id": null} """ - def dispatch(self, method): + def dispatch(self): """ Calls the method asked for by the JSON-RPC2 or JSONP request :param method: the method which received the request :returns: an utf8 encoded JSON-RPC2 or JSONP reply """ + method = self.method args = self.httprequest.args jsonp = args.get('jsonp') requestf = None @@ -291,7 +324,8 @@ def jsonrequest(f): class HttpRequest(WebRequest): """ Regular GET/POST request """ - def dispatch(self, method): + def dispatch(self): + method = self.method params = dict(self.httprequest.args) params.update(self.httprequest.form) params.update(self.httprequest.files) @@ -658,20 +692,22 @@ class Root(object): method = getattr(c, method_name, None) if method: exposed = getattr(method, 'exposed', False) + auth = getattr(method, 'auth', None) method = c.get_wrapped_method(method_name) + method.auth = auth if exposed == 'json': _logger.debug("Dispatch json to %s %s %s", ps, c, method_name) def fct(_request): - _req = JsonRequest(_request) + _req = JsonRequest(_request, method) with set_request(_req): - return request.dispatch(method) + return request.dispatch() return fct elif exposed == 'http': _logger.debug("Dispatch http to %s %s %s", ps, c, method_name) def fct(_request): - _req = HttpRequest(_request) + _req = HttpRequest(_request, method) with set_request(_req): - return request.dispatch(method) + return request.dispatch() return fct if method_name != "index": method_name = "index" diff --git a/addons/web/session.py b/addons/web/session.py index 13c3bc7cb94..9568da261f0 100644 --- a/addons/web/session.py +++ b/addons/web/session.py @@ -20,6 +20,7 @@ class AuthenticationError(Exception): class SessionExpiredException(Exception): pass +# deprecated class Service(object): def __init__(self, session, service_name): self.session = session @@ -31,6 +32,7 @@ class Service(object): return result return proxy_method +# deprecated class Model(object): def __init__(self, session, model): self.session = session @@ -84,12 +86,6 @@ class OpenERPSession(object): self.context = {} self.jsonp_requests = {} # FIXME use a LRU - def send(self, service_name, method, *args): - return openerp.netsvc.dispatch_rpc(service_name, method, args) - - def proxy(self, service): - return Service(self, service) - def bind(self, db, uid, login, password): self._db = db self._uid = uid @@ -103,6 +99,21 @@ class OpenERPSession(object): if uid: self.get_context() return uid + def check_security(self): + import openerp.service.security as security + if not self._db or not self._uid: + raise SessionExpiredException("Session expired") + security.check(self._db, self._uid, self._password) + + #deprecated + def send(self, service_name, method, *args): + return openerp.netsvc.dispatch_rpc(service_name, method, args) + + #deprecated + def proxy(self, service): + return Service(self, service) + + #deprecated def assert_valid(self, force=False): """ Ensures this session is valid (logged into the openerp server) @@ -114,6 +125,7 @@ class OpenERPSession(object): if not self._uid: raise AuthenticationError("Authentication failure") + #deprecated def ensure_valid(self): if self._uid: try: @@ -121,16 +133,19 @@ class OpenERPSession(object): except Exception: self._uid = None + #deprecated def execute(self, model, func, *l, **d): model = self.model(model) r = getattr(model, func)(*l, **d) return r + #deprecated def exec_workflow(self, model, id, signal): self.assert_valid() r = self.proxy('object').exec_workflow(self._db, self._uid, self._password, model, signal, id) return r + #deprecated def model(self, model): """ Get an RPC proxy for the object ``model``, bound to this session. From c6b4e77df4bb29e4057df48f9fed2536f3b4344d Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 11 Jun 2013 18:59:01 +0200 Subject: [PATCH 02/54] Put @nodb on all methods where it is required in main.py bzr revid: nicolas.vanhoren@openerp.com-20130611165901-l1pyzpvpniq41a29 --- addons/web/controllers/main.py | 25 ++++++++++++++++++++++++- addons/web/http.py | 19 +++++++++++-------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 8683ef177bc..db0a124f2f4 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -36,7 +36,7 @@ from openerp.tools.translate import _ from .. import http openerpweb = http -from openerp.addons.web.http import request as req +from openerp.addons.web.http import request as req, noauth, nodb #---------------------------------------------------------- # OpenERP Web helpers @@ -574,6 +574,7 @@ class Home(openerpweb.Controller): _cp_path = '/' @openerpweb.httprequest + @nodb def index(self, s_action=None, db=None, **kw): db, redir = db_monodb_redirect() if redir: @@ -603,18 +604,22 @@ class WebClient(openerpweb.Controller): _cp_path = "/web/webclient" @openerpweb.jsonrequest + @nodb def csslist(self, mods=None): return manifest_list('css', mods=mods) @openerpweb.jsonrequest + @nodb def jslist(self, mods=None): return manifest_list('js', mods=mods) @openerpweb.jsonrequest + @nodb def qweblist(self, mods=None): return manifest_list('qweb', mods=mods) @openerpweb.httprequest + @nodb def css(self, mods=None, db=None): files = list(manifest_glob('css', addons=mods, db=db)) last_modified = get_last_modified(f[0] for f in files) @@ -666,6 +671,7 @@ class WebClient(openerpweb.Controller): last_modified, checksum) @openerpweb.httprequest + @nodb def js(self, mods=None, db=None): files = [f[0] for f in manifest_glob('js', addons=mods, db=db)] last_modified = get_last_modified(files) @@ -679,6 +685,7 @@ class WebClient(openerpweb.Controller): last_modified, checksum) @openerpweb.httprequest + @nodb def qweb(self, mods=None, db=None): files = [f[0] for f in manifest_glob('qweb', addons=mods, db=db)] last_modified = get_last_modified(files) @@ -692,6 +699,7 @@ class WebClient(openerpweb.Controller): last_modified, checksum) @openerpweb.jsonrequest + @nodb def bootstrap_translations(self, mods): """ Load local translations from *.po files, as a temporary solution until we have established a valid session. This is meant only @@ -715,6 +723,7 @@ class WebClient(openerpweb.Controller): "lang_parameters": None} @openerpweb.jsonrequest + @nodb def translations(self, mods, lang): res_lang = req.session.model('res.lang') ids = res_lang.search([("code", "=", lang)]) @@ -740,6 +749,7 @@ class WebClient(openerpweb.Controller): "lang_parameters": lang_params} @openerpweb.jsonrequest + @nodb def version_info(self): return openerp.service.common.exp_version() @@ -747,6 +757,7 @@ class Proxy(openerpweb.Controller): _cp_path = '/web/proxy' @openerpweb.jsonrequest + @nodb def load(self, path): """ Proxies an HTTP request through a JSON request. @@ -765,10 +776,12 @@ class Database(openerpweb.Controller): _cp_path = "/web/database" @openerpweb.jsonrequest + @nodb def get_list(self): return db_list() @openerpweb.jsonrequest + @nodb def create(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) return req.session.proxy("db").create_database( @@ -779,6 +792,7 @@ class Database(openerpweb.Controller): params['create_admin_pwd']) @openerpweb.jsonrequest + @nodb def duplicate(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) return req.session.proxy("db").duplicate_database( @@ -787,6 +801,7 @@ class Database(openerpweb.Controller): params['db_name']) @openerpweb.jsonrequest + @nodb def duplicate(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) duplicate_attrs = ( @@ -798,6 +813,7 @@ class Database(openerpweb.Controller): return req.session.proxy("db").duplicate_database(*duplicate_attrs) @openerpweb.jsonrequest + @nodb def drop(self, fields): password, db = operator.itemgetter( 'drop_pwd', 'drop_db')( @@ -811,6 +827,7 @@ class Database(openerpweb.Controller): return {'error': _('Could not drop database !'), 'title': _('Drop Database')} @openerpweb.httprequest + @nodb def backup(self, backup_db, backup_pwd, token): try: db_dump = base64.b64decode( @@ -829,6 +846,7 @@ class Database(openerpweb.Controller): return simplejson.dumps([[],[{'error': openerp.tools.ustr(e), 'title': _('Backup Database')}]]) @openerpweb.httprequest + @nodb def restore(self, db_file, restore_pwd, new_db): try: data = base64.b64encode(db_file.read()) @@ -838,6 +856,7 @@ class Database(openerpweb.Controller): raise Exception("AccessDenied") @openerpweb.jsonrequest + @nodb def change_password(self, fields): old_password, new_password = operator.itemgetter( 'old_pwd', 'new_pwd')( @@ -863,10 +882,12 @@ class Session(openerpweb.Controller): } @openerpweb.jsonrequest + @nodb def get_session_info(self): return self.session_info() @openerpweb.jsonrequest + @nodb def authenticate(self, db, login, password, base_location=None): wsgienv = req.httprequest.environ env = dict( @@ -907,6 +928,7 @@ class Session(openerpweb.Controller): return {"error": e, "title": _("Languages")} @openerpweb.jsonrequest + @nodb def modules(self): # return all installed modules. Web client is smart enough to not load a module twice return module_installed() @@ -986,6 +1008,7 @@ class Menu(openerpweb.Controller): return Menus.search(menu_domain, 0, False, False, req.context) @openerpweb.jsonrequest + @nodb def load(self): """ Loads all menu items (all applications and their sub-menus). diff --git a/addons/web/http.py b/addons/web/http.py index 20431eb4233..0d55a12311b 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -83,13 +83,14 @@ class WebRequest(object): ``bool``, indicates whether the debug mode is active on the client """ - def __init__(self, httprequest, method): + def __init__(self, httprequest, method, auth_method=None): self.httprequest = httprequest self.httpresponse = None self.httpsession = httprequest.session self.db = None self.uid = None self.method = method + self.auth_method = auth_method def init(self, params): self.params = dict(params) @@ -128,16 +129,19 @@ class WebRequest(object): self.lang = lang.replace('-', '_') def authenticate(self): - if self.method.auth == "nodb": + if self.auth_method == "nodb": self.db = None self.uid = None - elif self.method.auth == "noauth": + elif self.auth_method == "noauth": self.db = (self.session._db or openerp.addons.web.controllers.main.db_monodb(self)).lower() self.uid = None else: - self.session.check_security() + try: + self.session.check_security() + except session.SessionExpiredException, e: + raise session.SessionExpiredException("Session expired for request %s" % self.httprequest) self.db = self.session._db - self.uid = self.session_uid + self.uid = self.session._uid def registry(self): return openerp.modules.registry.RegistryManager.get(self.db) @@ -694,18 +698,17 @@ class Root(object): exposed = getattr(method, 'exposed', False) auth = getattr(method, 'auth', None) method = c.get_wrapped_method(method_name) - method.auth = auth if exposed == 'json': _logger.debug("Dispatch json to %s %s %s", ps, c, method_name) def fct(_request): - _req = JsonRequest(_request, method) + _req = JsonRequest(_request, method, auth) with set_request(_req): return request.dispatch() return fct elif exposed == 'http': _logger.debug("Dispatch http to %s %s %s", ps, c, method_name) def fct(_request): - _req = HttpRequest(_request, method) + _req = HttpRequest(_request, method, auth) with set_request(_req): return request.dispatch() return fct From f58374357e4ffff72a59cdbb655c9d65f752e7af Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 12 Jun 2013 16:56:47 +0200 Subject: [PATCH 03/54] minor cosmetic improvements bzr revid: nicolas.vanhoren@openerp.com-20130612145647-5wwy71u6lrm7g744 --- addons/web/http.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 0d55a12311b..101229d0024 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -83,13 +83,13 @@ class WebRequest(object): ``bool``, indicates whether the debug mode is active on the client """ - def __init__(self, httprequest, method, auth_method=None): + def __init__(self, httprequest, func, auth_method="auth"): self.httprequest = httprequest self.httpresponse = None self.httpsession = httprequest.session self.db = None self.uid = None - self.method = method + self.func = func self.auth_method = auth_method def init(self, params): @@ -135,7 +135,7 @@ class WebRequest(object): elif self.auth_method == "noauth": self.db = (self.session._db or openerp.addons.web.controllers.main.db_monodb(self)).lower() self.uid = None - else: + else: # auth try: self.session.check_security() except session.SessionExpiredException, e: @@ -146,6 +146,7 @@ class WebRequest(object): def registry(self): return openerp.modules.registry.RegistryManager.get(self.db) + @property @contextlib.contextmanager def cr(self): with registry.cursor() as cr: @@ -206,11 +207,11 @@ class JsonRequest(WebRequest): def dispatch(self): """ Calls the method asked for by the JSON-RPC2 or JSONP request - :param method: the method which received the request + :param func: the func which received the request :returns: an utf8 encoded JSON-RPC2 or JSONP reply """ - method = self.method + func = self.func args = self.httprequest.args jsonp = args.get('jsonp') requestf = None @@ -245,9 +246,9 @@ class JsonRequest(WebRequest): self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral) self.init(self.jsonrequest.get("params", {})) #if _logger.isEnabledFor(logging.DEBUG): - # _logger.debug("--> %s.%s\n%s", method.im_class.__name__, method.__name__, pprint.pformat(self.jsonrequest)) + # _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest)) response['id'] = self.jsonrequest.get('id') - response["result"] = method(**self.params) + response["result"] = func(**self.params) except session.AuthenticationError, e: se = serialize_exception(e) error = { @@ -329,7 +330,7 @@ class HttpRequest(WebRequest): """ Regular GET/POST request """ def dispatch(self): - method = self.method + func = self.func params = dict(self.httprequest.args) params.update(self.httprequest.form) params.update(self.httprequest.files) @@ -340,9 +341,9 @@ class HttpRequest(WebRequest): akw[key] = value else: akw[key] = type(value) - #_logger.debug("%s --> %s.%s %r", self.httprequest.method, method.im_class.__name__, method.__name__, akw) + #_logger.debug("%s --> %s.%s %r", self.httprequest.func, func.im_class.__name__, func.__name__, akw) try: - r = method(**self.params) + r = func(**self.params) except werkzeug.exceptions.HTTPException, e: r = e except Exception, e: From 9ee7c3f41a9941a81249c8ed5835883406dc8356 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 11:58:42 +0200 Subject: [PATCH 04/54] added cr property bzr revid: nicolas.vanhoren@openerp.com-20130613095842-29da30ppr6c06xac --- addons/web/http.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 101229d0024..c92e2f2244b 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -91,6 +91,7 @@ class WebRequest(object): self.uid = None self.func = func self.auth_method = auth_method + self._cr = None def init(self, params): self.params = dict(params) @@ -101,8 +102,6 @@ class WebRequest(object): self.session = session.OpenERPSession() self.httpsession[self.session_id] = self.session - self.authenticate() - # TODO: remove this shit # set db/uid trackers - they're cleaned up at the WSGI # dispatching phase in openerp.service.wsgi_server.application @@ -143,19 +142,30 @@ class WebRequest(object): self.db = self.session._db self.uid = self.session._uid + @property def registry(self): - return openerp.modules.registry.RegistryManager.get(self.db) + return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None @property - @contextlib.contextmanager def cr(self): - with registry.cursor() as cr: - yield cr + return self._cr @contextlib.contextmanager def registry_cr(self): - with self.cr() as cr: - yield (self.registry(), cr) + return (self.registry, cr) + + def call_function(self, *args, **kwargs): + self.authenticate() + if self.registry: + with self.registry.cursor() as cr: + self._cr = cr + try: + return self.func(*args, **kwargs) + finally: + self._cr = None + else: + return self.func(*args, **kwargs) + def noauth(f): f.auth = "noauth" @@ -207,11 +217,8 @@ class JsonRequest(WebRequest): def dispatch(self): """ Calls the method asked for by the JSON-RPC2 or JSONP request - :param func: the func which received the request - :returns: an utf8 encoded JSON-RPC2 or JSONP reply """ - func = self.func args = self.httprequest.args jsonp = args.get('jsonp') requestf = None @@ -248,7 +255,7 @@ class JsonRequest(WebRequest): #if _logger.isEnabledFor(logging.DEBUG): # _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest)) response['id'] = self.jsonrequest.get('id') - response["result"] = func(**self.params) + response["result"] = self.call_function(**self.params) except session.AuthenticationError, e: se = serialize_exception(e) error = { @@ -330,7 +337,6 @@ class HttpRequest(WebRequest): """ Regular GET/POST request """ def dispatch(self): - func = self.func params = dict(self.httprequest.args) params.update(self.httprequest.form) params.update(self.httprequest.files) @@ -343,7 +349,7 @@ class HttpRequest(WebRequest): akw[key] = type(value) #_logger.debug("%s --> %s.%s %r", self.httprequest.func, func.im_class.__name__, func.__name__, akw) try: - r = func(**self.params) + r = self.call_function(**self.params) except werkzeug.exceptions.HTTPException, e: r = e except Exception, e: From 4cc12c7f44325b4d1f271b82d033cf8d7cb45248 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 12:07:43 +0200 Subject: [PATCH 05/54] Made cr lazy bzr revid: nicolas.vanhoren@openerp.com-20130613100743-m5tcv0anibiw820f --- addons/web/http.py | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index c92e2f2244b..f54488c13e7 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -148,23 +148,39 @@ class WebRequest(object): @property def cr(self): + # some magic to lazy create the cr + if not self._cr: + self._cr = self.registry.cursor() + self._cr.__enter__() return self._cr + #TODO: remove @contextlib.contextmanager def registry_cr(self): return (self.registry, cr) - def call_function(self, *args, **kwargs): + def _call_function(self, *args, **kwargs): self.authenticate() - if self.registry: - with self.registry.cursor() as cr: - self._cr = cr - try: + try: + if self.registry: + # ugly syntax only to get the __exit__ arguments to pass to self._cr + request = self + class with_obj(object): + def __enter__(self): + pass + def __exit__(self, *args): + if request._cr: + request._cr.__exit__(*args) + request._cr = None + + with with_obj(): return self.func(*args, **kwargs) - finally: - self._cr = None - else: - return self.func(*args, **kwargs) + else: + return self.func(*args, **kwargs) + finally: + # just to be sure no one tries to re-use the request + self.db = None + self.uid = None def noauth(f): @@ -255,7 +271,7 @@ class JsonRequest(WebRequest): #if _logger.isEnabledFor(logging.DEBUG): # _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest)) response['id'] = self.jsonrequest.get('id') - response["result"] = self.call_function(**self.params) + response["result"] = self._call_function(**self.params) except session.AuthenticationError, e: se = serialize_exception(e) error = { @@ -349,7 +365,7 @@ class HttpRequest(WebRequest): akw[key] = type(value) #_logger.debug("%s --> %s.%s %r", self.httprequest.func, func.im_class.__name__, func.__name__, akw) try: - r = self.call_function(**self.params) + r = self._call_function(**self.params) except werkzeug.exceptions.HTTPException, e: r = e except Exception, e: @@ -703,7 +719,7 @@ class Root(object): method = getattr(c, method_name, None) if method: exposed = getattr(method, 'exposed', False) - auth = getattr(method, 'auth', None) + auth = getattr(method, 'auth', "auth") method = c.get_wrapped_method(method_name) if exposed == 'json': _logger.debug("Dispatch json to %s %s %s", ps, c, method_name) From 50a5a612dae47a2075dbce11c9573ab82e28e830 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 13:00:05 +0200 Subject: [PATCH 06/54] Now uses request.cr for Model bzr revid: nicolas.vanhoren@openerp.com-20130613110005-idverfbs9xe6l32w --- addons/web/controllers/main.py | 4 ---- addons/web/http.py | 14 +++++++++----- addons/web/session.py | 11 ++++++++++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index db0a124f2f4..2e470314ec3 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -723,7 +723,6 @@ class WebClient(openerpweb.Controller): "lang_parameters": None} @openerpweb.jsonrequest - @nodb def translations(self, mods, lang): res_lang = req.session.model('res.lang') ids = res_lang.search([("code", "=", lang)]) @@ -882,7 +881,6 @@ class Session(openerpweb.Controller): } @openerpweb.jsonrequest - @nodb def get_session_info(self): return self.session_info() @@ -928,7 +926,6 @@ class Session(openerpweb.Controller): return {"error": e, "title": _("Languages")} @openerpweb.jsonrequest - @nodb def modules(self): # return all installed modules. Web client is smart enough to not load a module twice return module_installed() @@ -1008,7 +1005,6 @@ class Menu(openerpweb.Controller): return Menus.search(menu_domain, 0, False, False, req.context) @openerpweb.jsonrequest - @nodb def load(self): """ Loads all menu items (all applications and their sub-menus). diff --git a/addons/web/http.py b/addons/web/http.py index f54488c13e7..c09321693aa 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -91,6 +91,7 @@ class WebRequest(object): self.uid = None self.func = func self.auth_method = auth_method + self._cr_cm = None self._cr = None def init(self, params): @@ -149,9 +150,9 @@ class WebRequest(object): @property def cr(self): # some magic to lazy create the cr - if not self._cr: - self._cr = self.registry.cursor() - self._cr.__enter__() + if not self._cr_cm: + self._cr_cm = self.registry.cursor() + self._cr = self._cr_cm.__enter__() return self._cr #TODO: remove @@ -169,8 +170,9 @@ class WebRequest(object): def __enter__(self): pass def __exit__(self, *args): - if request._cr: - request._cr.__exit__(*args) + if request._cr_cm: + request._cr_cm.__exit__(*args) + request._cr_cm = None request._cr = None with with_obj(): @@ -273,6 +275,7 @@ class JsonRequest(WebRequest): response['id'] = self.jsonrequest.get('id') response["result"] = self._call_function(**self.params) except session.AuthenticationError, e: + _logger.exception("Exception during JSON request handling.") se = serialize_exception(e) error = { 'code': 100, @@ -280,6 +283,7 @@ class JsonRequest(WebRequest): 'data': se } except Exception, e: + _logger.exception("Exception during JSON request handling.") se = serialize_exception(e) error = { 'code': 200, diff --git a/addons/web/session.py b/addons/web/session.py index 9568da261f0..05749d35ee3 100644 --- a/addons/web/session.py +++ b/addons/web/session.py @@ -8,6 +8,7 @@ import traceback import sys import openerp +import http _logger = logging.getLogger(__name__) @@ -42,7 +43,15 @@ class Model(object): def __getattr__(self, method): self.session.assert_valid() def proxy(*args, **kw): - result = self.proxy.execute_kw(self.session._db, self.session._uid, self.session._password, self.model, method, args, kw) + # Can't provide any retro-compatibility for this case, so we check it and raise an Exception + # to tell the programmer to adapt his code + if self.session._db != http.request.db or self.session._uid != http.request.uid: + raise Exception("Trying to use Model with badly configured database or user.") + + mod = http.request.registry.get(self.model) + meth = getattr(mod, method) + cr = http.request.cr + result = meth(cr, http.request.uid, *args, **kw) # reorder read if method == "read": if isinstance(result, list) and len(result) > 0 and "id" in result[0]: From 1c0282d09d4e5d192fe44fcacd846b31f1459306 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 13:20:27 +0200 Subject: [PATCH 07/54] fixed lot of problem with nodb bzr revid: nicolas.vanhoren@openerp.com-20130613112027-1fps4v7h6zx5omo0 --- addons/web/controllers/main.py | 4 ++++ addons/web/http.py | 27 ++++++++++++--------------- addons/web/session.py | 2 ++ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 2e470314ec3..697db75797d 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -881,7 +881,10 @@ class Session(openerpweb.Controller): } @openerpweb.jsonrequest + @nodb def get_session_info(self): + req.uid = req.session._uid + req.db = req.session._db return self.session_info() @openerpweb.jsonrequest @@ -1382,6 +1385,7 @@ class Binary(openerpweb.Controller): return out % (simplejson.dumps(callback), simplejson.dumps(args)) @openerpweb.httprequest + @nodb def company_logo(self, dbname=None): # TODO add etag, refactor to use /image code for etag uid = None diff --git a/addons/web/http.py b/addons/web/http.py index c09321693aa..d9ee06a201e 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -158,26 +158,23 @@ class WebRequest(object): #TODO: remove @contextlib.contextmanager def registry_cr(self): - return (self.registry, cr) + return (self.registry, self.cr) def _call_function(self, *args, **kwargs): self.authenticate() try: - if self.registry: - # ugly syntax only to get the __exit__ arguments to pass to self._cr - request = self - class with_obj(object): - def __enter__(self): - pass - def __exit__(self, *args): - if request._cr_cm: - request._cr_cm.__exit__(*args) - request._cr_cm = None - request._cr = None + # ugly syntax only to get the __exit__ arguments to pass to self._cr + request = self + class with_obj(object): + def __enter__(self): + pass + def __exit__(self, *args): + if request._cr_cm: + request._cr_cm.__exit__(*args) + request._cr_cm = None + request._cr = None - with with_obj(): - return self.func(*args, **kwargs) - else: + with with_obj(): return self.func(*args, **kwargs) finally: # just to be sure no one tries to re-use the request diff --git a/addons/web/session.py b/addons/web/session.py index 05749d35ee3..fa5b83b6892 100644 --- a/addons/web/session.py +++ b/addons/web/session.py @@ -104,6 +104,8 @@ class OpenERPSession(object): def authenticate(self, db, login, password, env=None): uid = self.proxy('common').authenticate(db, login, password, env) self.bind(db, uid, login, password) + http.request.db = db + http.request.uid = uid if uid: self.get_context() return uid From 8fe3da4d557252f77c65ca4a7340916f380f91d0 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 13:25:17 +0200 Subject: [PATCH 08/54] cosmetic bzr revid: nicolas.vanhoren@openerp.com-20130613112517-90hwu5l6h3c2dvsd --- addons/web/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/session.py b/addons/web/session.py index fa5b83b6892..56f2da9c582 100644 --- a/addons/web/session.py +++ b/addons/web/session.py @@ -111,9 +111,9 @@ class OpenERPSession(object): return uid def check_security(self): - import openerp.service.security as security if not self._db or not self._uid: raise SessionExpiredException("Session expired") + import openerp.service.security as security security.check(self._db, self._uid, self._password) #deprecated From 085d34bec3e24c0479ed78548d5da17b6623d6b3 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 13:32:03 +0200 Subject: [PATCH 09/54] some polish in session.py bzr revid: nicolas.vanhoren@openerp.com-20130613113203-w0vralvme3coz0sf --- addons/web/session.py | 73 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/addons/web/session.py b/addons/web/session.py index 56f2da9c582..0dbda02352d 100644 --- a/addons/web/session.py +++ b/addons/web/session.py @@ -95,15 +95,12 @@ class OpenERPSession(object): self.context = {} self.jsonp_requests = {} # FIXME use a LRU - def bind(self, db, uid, login, password): + def authenticate(self, db, login, password, env=None): + uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env]) self._db = db self._uid = uid self._login = login self._password = password - - def authenticate(self, db, login, password, env=None): - uid = self.proxy('common').authenticate(db, login, password, env) - self.bind(db, uid, login, password) http.request.db = db http.request.uid = uid @@ -116,6 +113,39 @@ class OpenERPSession(object): import openerp.service.security as security security.check(self._db, self._uid, self._password) + def get_context(self): + """ Re-initializes the current user's session context (based on + his preferences) by calling res.users.get_context() with the old + context + + :returns: the new context + """ + assert self._uid, "The user needs to be logged-in to initialize his context" + self.context = http.request.registry.get('res.users').context_get(http.request.cr, http.request.uid) or {} + self.context['uid'] = self._uid + self._fix_lang(self.context) + return self.context + + def _fix_lang(self, context): + """ OpenERP provides languages which may not make sense and/or may not + be understood by the web client's libraries. + + Fix those here. + + :param dict context: context to fix + """ + lang = context['lang'] + + # inane OpenERP locale + if lang == 'ar_AR': + lang = 'ar' + + # lang to lang_REGION (datejs only handles lang_REGION, no bare langs) + if lang in babel.core.LOCALE_ALIASES: + lang = babel.core.LOCALE_ALIASES[lang] + + context['lang'] = lang or 'en_US' + #deprecated def send(self, service_name, method, *args): return openerp.netsvc.dispatch_rpc(service_name, method, args) @@ -169,37 +199,4 @@ class OpenERPSession(object): return Model(self, model) - def get_context(self): - """ Re-initializes the current user's session context (based on - his preferences) by calling res.users.get_context() with the old - context - - :returns: the new context - """ - assert self._uid, "The user needs to be logged-in to initialize his context" - self.context = self.model('res.users').context_get() or {} - self.context['uid'] = self._uid - self._fix_lang(self.context) - return self.context - - def _fix_lang(self, context): - """ OpenERP provides languages which may not make sense and/or may not - be understood by the web client's libraries. - - Fix those here. - - :param dict context: context to fix - """ - lang = context['lang'] - - # inane OpenERP locale - if lang == 'ar_AR': - lang = 'ar' - - # lang to lang_REGION (datejs only handles lang_REGION, no bare langs) - if lang in babel.core.LOCALE_ALIASES: - lang = babel.core.LOCALE_ALIASES[lang] - - context['lang'] = lang or 'en_US' - # vim:et:ts=4:sw=4: From 2c959246b762ffe18cd1cfa028f1335fc9889f84 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 13:34:58 +0200 Subject: [PATCH 10/54] even more security bzr revid: nicolas.vanhoren@openerp.com-20130613113458-2ww1cbb9q5nz6ayk --- addons/web/session.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web/session.py b/addons/web/session.py index 0dbda02352d..a0c924975ca 100644 --- a/addons/web/session.py +++ b/addons/web/session.py @@ -45,7 +45,8 @@ class Model(object): def proxy(*args, **kw): # Can't provide any retro-compatibility for this case, so we check it and raise an Exception # to tell the programmer to adapt his code - if self.session._db != http.request.db or self.session._uid != http.request.uid: + if not http.request.db or not http.request.uid or self.session._db != http.request.db \ + or self.session._uid != http.request.uid: raise Exception("Trying to use Model with badly configured database or user.") mod = http.request.registry.get(self.model) From 787cb04b2dba0276e2bedcd239b9e62aa9fd08b1 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 13:40:30 +0200 Subject: [PATCH 11/54] wip bzr revid: nicolas.vanhoren@openerp.com-20130613114030-hmrc7881jp8ctjic --- addons/web/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/session.py b/addons/web/session.py index a0c924975ca..b51c540aec5 100644 --- a/addons/web/session.py +++ b/addons/web/session.py @@ -29,7 +29,7 @@ class Service(object): def __getattr__(self, method): def proxy_method(*args): - result = self.session.send(self.service_name, method, *args) + result = openerp.netsvc.dispatch_rpc(self.service_name, method, args) return result return proxy_method From 20c0e6fbb7e6494bdaeb13d4b5b20a1d05e31a27 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 13:44:21 +0200 Subject: [PATCH 12/54] fixed get_lang_list bzr revid: nicolas.vanhoren@openerp.com-20130613114421-hv3rgj0rxghpwlgp --- addons/web/controllers/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 697db75797d..361db5567e3 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -922,6 +922,7 @@ class Session(openerpweb.Controller): req.session._uid, "ir.ui.menu", req.context) @openerpweb.jsonrequest + @nodb def get_lang_list(self): try: return req.session.proxy("db").list_lang() or [] From ef6779f484a3b8251c88050c8cf0a008360a025a Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 16:03:43 +0200 Subject: [PATCH 13/54] Added some doc bzr revid: nicolas.vanhoren@openerp.com-20130613140343-hjvlc697eimgcb55 --- addons/web/doc/index.rst | 37 +++++++++---- addons/web/doc/web_controllers.rst | 85 ++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 addons/web/doc/web_controllers.rst diff --git a/addons/web/doc/index.rst b/addons/web/doc/index.rst index 7127bb975ed..650c12f55ec 100644 --- a/addons/web/doc/index.rst +++ b/addons/web/doc/index.rst @@ -6,32 +6,49 @@ Welcome to OpenERP Web's documentation! ======================================= -Contents: +Basics +------ .. toctree:: :maxdepth: 1 module - widget + changelog-7.0 +Server-Side Web Framework +------------------------- - async - rpc - qweb - client_action +.. toctree:: + :maxdepth: 1 + + web_controllers + +Javascript +---------- + +.. toctree:: + :maxdepth: 1 guidelines - + widget + rpc + async + qweb + client_action testing +Views +----- + +.. toctree:: + :maxdepth: 1 + search_view list_view form_view - changelog-7.0 - Indices and tables -================== +------------------ * :ref:`genindex` * :ref:`modindex` diff --git a/addons/web/doc/web_controllers.rst b/addons/web/doc/web_controllers.rst new file mode 100644 index 00000000000..4694b72070f --- /dev/null +++ b/addons/web/doc/web_controllers.rst @@ -0,0 +1,85 @@ + +Web Controllers +=============== + +Web controllers are classes in OpenERP able to catch the http requests sent by any browser. They allow to generate +html pages to be served like any web server, implement new methods to be used by the Javascript client, etc... + +Controllers File +---------------- + +By convention the controllers should be placed in the controllers directory of the module. Example: + +.. code-block:: text + + web_example + ├── controllers + │ ├── __init__.py + │ └── my_controllers.py + ├── __init__.py + └── __openerp__.py + +In ``__init__.py`` you must add: + +:: + + import controllers + +And here is the content of ``controllers/__init__.py``: + +:: + + import my_controllers + +Now you can put the following content in ``controllers/my_controllers.py``: + +:: + + import openerp.addons.web.http as http + from openerp.addons.web.http import request + + +Controller Declaration +---------------------- + +In your controllers file, you can now declare a controller this way: + +:: + + class MyController(http.Controller): + _cp_path = '/my_url' + + @http.httprequest + def some_html(self): + return "

This is a test

" + + @http.jsonrequest + def some_json(self): + return {"sample_dictionary": "This is a sample JSON dictionary"} + +A controller must inherit from ``http.Controller``. When defining a controller, you must define the url it will match. +This is the ``_cp_path`` class attribute. + +Each time you define a method with ``@http.httprequest`` or ``@http.jsonrequest`` it defines a new part of url to +match. As example, the ``some_html()`` method will be called a client query the ``/my_url/some_html`` url. + +If you want to match precisely the ``/my_url`` url, you must a define a method called ``index``. This is an exception +compared to other methods: + +:: + + @http.httprequest + def index(self): + return "
This is the /my_url
" + +Pure HTTP Requests +------------------ + +You can define methods to get any requests using the ``@http.httprequest`` decorator. When doing so, you get the +HTTP parameters as named parameters of the method: + +:: + + @http.httprequest + def some_html(self, name): + return "

You name is %s

" % name From d6c89cdb7c7f9770f2e0c92ffd0b9670e49cb588 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 16:04:18 +0200 Subject: [PATCH 14/54] Implemented jsonrpc method bzr revid: nicolas.vanhoren@openerp.com-20130613140418-2i1qsb6jykhx5jaz --- addons/web/controllers/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 361db5567e3..d2f425e0c49 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -596,9 +596,10 @@ class Home(openerpweb.Controller): return login_and_redirect(db, login, key) @openerpweb.jsonrequest + @nodb def jsonrpc(self, service, method, args): """ Method used by client APIs to contact OpenERP. """ - return getattr(req.session.proxy(service), method)(*args) + return openerp.netsvc.dispatch_rpc(service, method, args) class WebClient(openerpweb.Controller): _cp_path = "/web/webclient" From 7c916c593653fae8f32203b17dc6efdd018c204e Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 16:27:17 +0200 Subject: [PATCH 15/54] added doc bzr revid: nicolas.vanhoren@openerp.com-20130613142717-nj3ox24vip56buc0 --- addons/web/doc/web_controllers.rst | 56 ++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/addons/web/doc/web_controllers.rst b/addons/web/doc/web_controllers.rst index 4694b72070f..32b49f768ee 100644 --- a/addons/web/doc/web_controllers.rst +++ b/addons/web/doc/web_controllers.rst @@ -81,5 +81,57 @@ HTTP parameters as named parameters of the method: :: @http.httprequest - def some_html(self, name): - return "

You name is %s

" % name + def say_hello(self, name): + return "

Hello %s

" % name + +This url could be contacted by typing this url in a browser: ``http://localhost:8069/my_url/say_hello?name=Nicolas``. + +JSON Requests +------------- + +The ``@http.jsonrequest`` decorator is used to define JSON requests. The OpenERP Javascript client can contact these +methods using the JSON-RPC protocol. JSON methods must return JSON. Like the HTTP methods they receive arguments +as named parameters (except these arguments are JSON-RPC parameters). + +:: + + @http.jsonrequest + def division(self, i, j): + return i / j # returns a number + +Contacting Models +----------------- + +To use the database you must access the OpenERP models. The global ``request`` object provides the necessary objects: + +:: + + @http.httprequest + def my_name(self): + my_user_record = request.registry.get("res.users").browse(request.cr, request.uid, request.ui) + return "

Your name is %s

" % my_user_record.name + +``request.registry`` is the registry that gives you access to the models. It is the equivalent of ``self.pool`` when +working inside OpenERP models. + +``request.cr`` is the cursor object. This is the ``cr`` parameter you have to pass as first argument of every model +method in OpenERP. + +``request.uid`` is the id of the current logged in user. This is the ``uid`` parameter you have to pass as second +argument of every model method in OpenERP. + +Authorization Levels +-------------------- + +By default, all methods can only be used by users logged into OpenERP (OpenERP uses cookies to track logged users). +There are some cases when you need to enable not-logged in users to access some methods. To do so, add the +``http.noauth`` decorator to your method: + +:: + + @http.httprequest + @http.noauth + def hello(self): + return "
Hello unknown user!
" + +Please note the ``request.cr`` cursor will not be available in this case. From 1e70a1f2e11e6926644dfb4b87a6b9f1213aee88 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 16:27:38 +0200 Subject: [PATCH 16/54] fix bug with monodb bzr revid: nicolas.vanhoren@openerp.com-20130613142738-s4zp2b89xlxoaot4 --- addons/web/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/http.py b/addons/web/http.py index d9ee06a201e..42ba0c2e35f 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -133,7 +133,7 @@ class WebRequest(object): self.db = None self.uid = None elif self.auth_method == "noauth": - self.db = (self.session._db or openerp.addons.web.controllers.main.db_monodb(self)).lower() + self.db = (self.session._db or openerp.addons.web.controllers.main.db_monodb()).lower() self.uid = None else: # auth try: From 213a1a53a346595a3b603a100fdd4ff4fa2ffeb1 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 16:29:28 +0200 Subject: [PATCH 17/54] doc bzr revid: nicolas.vanhoren@openerp.com-20130613142928-8z4ivvhb9jjm5gxc --- addons/web/doc/web_controllers.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web/doc/web_controllers.rst b/addons/web/doc/web_controllers.rst index 32b49f768ee..6c0137e8282 100644 --- a/addons/web/doc/web_controllers.rst +++ b/addons/web/doc/web_controllers.rst @@ -134,4 +134,5 @@ There are some cases when you need to enable not-logged in users to access some def hello(self): return "
Hello unknown user!
" -Please note the ``request.cr`` cursor will not be available in this case. +Please note the ``request.uid`` user id will be ``None`` inside this method call. This is due to the fact no user was +authenticated. From 5a22702043d5b29d67e27e8c93051716d727357c Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 18:23:19 +0200 Subject: [PATCH 18/54] added black magic to detect the user when using http requests bzr revid: nicolas.vanhoren@openerp.com-20130613162319-1w0renis7g2x5cw0 --- addons/web/doc/web_controllers.rst | 2 +- addons/web/http.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/addons/web/doc/web_controllers.rst b/addons/web/doc/web_controllers.rst index 6c0137e8282..8938f0adbb8 100644 --- a/addons/web/doc/web_controllers.rst +++ b/addons/web/doc/web_controllers.rst @@ -108,7 +108,7 @@ To use the database you must access the OpenERP models. The global ``request`` o @http.httprequest def my_name(self): - my_user_record = request.registry.get("res.users").browse(request.cr, request.uid, request.ui) + my_user_record = request.registry.get("res.users").browse(request.cr, request.uid, request.uid) return "

Your name is %s

" % my_user_record.name ``request.registry`` is the registry that gives you access to the models. It is the equivalent of ``self.pool`` when diff --git a/addons/web/http.py b/addons/web/http.py index 42ba0c2e35f..d919c247bcb 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -29,6 +29,7 @@ import werkzeug.exceptions import werkzeug.utils import werkzeug.wrappers import werkzeug.wsgi +import urllib2 import openerp @@ -97,7 +98,13 @@ class WebRequest(object): def init(self, params): self.params = dict(params) # OpenERP session setup - self.session_id = self.params.pop("session_id", None) or uuid.uuid4().hex + self.session_id = self.params.pop("session_id", None) + if not self.session_id: + i0 = self.httprequest.cookies.get("instance0|session_id", None) + if i0: + self.session_id = simplejson.loads(urllib2.unquote(i0)) + else: + self.session_id = uuid.uuid4().hex self.session = self.httpsession.get(self.session_id) if not self.session: self.session = session.OpenERPSession() From 4257dfed8a99d917e106cb8d4b6339135a8ffa15 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Thu, 13 Jun 2013 19:01:47 +0200 Subject: [PATCH 19/54] Added doc in the code bzr revid: nicolas.vanhoren@openerp.com-20130613170147-d2mfdwlvkg088jtq --- addons/web/http.py | 42 +++++++++++++++++++++++++++++------ addons/web/session.py | 51 +++++++++++++++++++++++++++++++++---------- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index d919c247bcb..0f5450ffd30 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -83,6 +83,16 @@ class WebRequest(object): .. attribute:: debug ``bool``, indicates whether the debug mode is active on the client + + .. attribute:: db + + ``str``, the name of the database linked to the current request. Can be ``None`` + if the current request uses the @nodb decorator. + + .. attribute:: uid + + ``int``, the id of the user related to the current request. Can be ``None`` + if the current request uses the @nodb or the @noauth decorator. """ def __init__(self, httprequest, func, auth_method="auth"): self.httprequest = httprequest @@ -135,12 +145,14 @@ class WebRequest(object): # we use _ as seprator where RFC2616 uses '-' self.lang = lang.replace('-', '_') - def authenticate(self): + def _authenticate(self): if self.auth_method == "nodb": self.db = None self.uid = None elif self.auth_method == "noauth": self.db = (self.session._db or openerp.addons.web.controllers.main.db_monodb()).lower() + if not self.db: + raise session.SessionExpiredException("No valid database for request %s" % self.httprequest) self.uid = None else: # auth try: @@ -152,23 +164,26 @@ class WebRequest(object): @property def registry(self): + """ + The registry to the database linked to this request. Can be ``None`` if the current request uses the + @nodb decorator. + """ return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None @property def cr(self): + """ + The cursor initialized for the current method call. If the current request uses the @nodb decorator + trying to access this property will raise an exception. + """ # some magic to lazy create the cr if not self._cr_cm: self._cr_cm = self.registry.cursor() self._cr = self._cr_cm.__enter__() return self._cr - #TODO: remove - @contextlib.contextmanager - def registry_cr(self): - return (self.registry, self.cr) - def _call_function(self, *args, **kwargs): - self.authenticate() + self._authenticate() try: # ugly syntax only to get the __exit__ arguments to pass to self._cr request = self @@ -190,10 +205,20 @@ class WebRequest(object): def noauth(f): + """ + Decorator to put on a controller method to inform it does not require a user to be logged. When this decorator + is used, ``request.uid`` will be ``None``. The request will still try to detect the database and an exception + will be launched if there is no way to guess it. + """ f.auth = "noauth" return f def nodb(f): + """ + Decorator to put on a controller method to inform it does not require authentication nor any link to a database. + When this decorator is used, ``request.uid`` and ``request.db`` will be ``None``. Trying to use ``request.cr`` + will launch an exception. + """ f.auth = "nodb" return f @@ -447,6 +472,9 @@ def set_request(request): _request_stack.pop() return with_obj() +""" + A global proxy that always redirect to the current request object. +""" request = _request_stack() #---------------------------------------------------------- diff --git a/addons/web/session.py b/addons/web/session.py index b51c540aec5..aaa712fdedd 100644 --- a/addons/web/session.py +++ b/addons/web/session.py @@ -21,8 +21,10 @@ class AuthenticationError(Exception): class SessionExpiredException(Exception): pass -# deprecated class Service(object): + """ + Deprecated. Use ``openerp.netsvc.dispatch_rpc()`` instead. + """ def __init__(self, session, service_name): self.session = session self.service_name = service_name @@ -33,8 +35,10 @@ class Service(object): return result return proxy_method -# deprecated class Model(object): + """ + Deprecated. Use the resistry and cursor in ``openerp.addons.web.http.request`` instead. + """ def __init__(self, session, model): self.session = session self.model = model @@ -97,6 +101,10 @@ class OpenERPSession(object): self.jsonp_requests = {} # FIXME use a LRU def authenticate(self, db, login, password, env=None): + """ + Authenticate the current user with the given db, login and password. If successful, store + the authentication parameters in the current session and request. + """ uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env]) self._db = db self._uid = uid @@ -109,15 +117,21 @@ class OpenERPSession(object): return uid def check_security(self): + """ + Chech the current authentication parameters to know if those are still valid. This method + should be called at each request. If the authentication fails, a ``SessionExpiredException`` + is raised. + """ if not self._db or not self._uid: raise SessionExpiredException("Session expired") import openerp.service.security as security security.check(self._db, self._uid, self._password) def get_context(self): - """ Re-initializes the current user's session context (based on + """ + Re-initializes the current user's session context (based on his preferences) by calling res.users.get_context() with the old - context + context. :returns: the new context """ @@ -147,17 +161,22 @@ class OpenERPSession(object): context['lang'] = lang or 'en_US' - #deprecated def send(self, service_name, method, *args): + """ + Deprecated. Use ``openerp.netsvc.dispatch_rpc()`` instead. + """ return openerp.netsvc.dispatch_rpc(service_name, method, args) - #deprecated def proxy(self, service): + """ + Deprecated. Use ``openerp.netsvc.dispatch_rpc()`` instead. + """ return Service(self, service) - #deprecated def assert_valid(self, force=False): """ + Deprecated. Use ``check_security()`` instead. + Ensures this session is valid (logged into the openerp server) """ if self._uid and not force: @@ -167,29 +186,37 @@ class OpenERPSession(object): if not self._uid: raise AuthenticationError("Authentication failure") - #deprecated def ensure_valid(self): + """ + Deprecated. Use ``check_security()`` instead. + """ if self._uid: try: self.assert_valid(True) except Exception: self._uid = None - #deprecated def execute(self, model, func, *l, **d): + """ + Deprecated. Use the resistry and cursor in ``openerp.addons.web.http.request`` instead. + """ model = self.model(model) r = getattr(model, func)(*l, **d) return r - #deprecated def exec_workflow(self, model, id, signal): + """ + Deprecated. Use the resistry and cursor in ``openerp.addons.web.http.request`` instead. + """ self.assert_valid() r = self.proxy('object').exec_workflow(self._db, self._uid, self._password, model, signal, id) return r - #deprecated def model(self, model): - """ Get an RPC proxy for the object ``model``, bound to this session. + """ + Deprecated. Use the resistry and cursor in ``openerp.addons.web.http.request`` instead. + + Get an RPC proxy for the object ``model``, bound to this session. :param model: an OpenERP model name :type model: str From 0ab2825e343ed2f9afcdd6e2826b5c73a70e86ab Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 14 Jun 2013 14:24:00 +0200 Subject: [PATCH 20/54] corrected test bzr revid: nicolas.vanhoren@openerp.com-20130614122400-9xns98yv1k42gsoz --- addons/web/controllers/testing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web/controllers/testing.py b/addons/web/controllers/testing.py index b6327bf4622..8b89ab8ac9d 100644 --- a/addons/web/controllers/testing.py +++ b/addons/web/controllers/testing.py @@ -89,6 +89,7 @@ class TestRunnerController(http.Controller): _cp_path = '/web/tests' @http.httprequest + @http.nodb def index(self, req, mod=None, **kwargs): ms = module.get_modules() manifests = dict( From faf2ef3d1ac8212d6ce9542cb9617d019513a012 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 14 Jun 2013 17:01:25 +0200 Subject: [PATCH 21/54] Simplefied a little current routing bzr revid: nicolas.vanhoren@openerp.com-20130614150125-w8fnyclnw0l8b3r7 --- addons/web/http.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 0f5450ffd30..a9039f38f9e 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -29,6 +29,7 @@ import werkzeug.exceptions import werkzeug.utils import werkzeug.wrappers import werkzeug.wsgi +import werkzeug.routing as routing import urllib2 import openerp @@ -482,7 +483,6 @@ request = _request_stack() #---------------------------------------------------------- addons_module = {} addons_manifest = {} -controllers_class = [] controllers_class_path = {} controllers_object = {} controllers_object_path = {} @@ -505,9 +505,8 @@ class ControllerType(type): # store the controller in the controllers list name_class = ("%s.%s" % (cls.__module__, cls.__name__), cls) - controllers_class.append(name_class) path = attrs.get('_cp_path') - if path not in controllers_class_path: + if path and path not in controllers_class_path: controllers_class_path[path] = name_class class Controller(object): @@ -655,6 +654,7 @@ class Root(object): def __init__(self): self.addons = {} self.statics = {} + self.routing_map = None self.load_addons() @@ -727,12 +727,11 @@ class Root(object): self.statics['/%s/static' % module] = path_static for k, v in controllers_class_path.items(): - if k not in controllers_object_path and hasattr(v[1], '_cp_path'): - o = v[1]() - controllers_object[v[0]] = o - controllers_object_path[k] = o - if hasattr(o, '_cp_path'): - controllers_path[o._cp_path] = o + o = v[1]() + controllers_object[v[0]] = o + controllers_object_path[k] = o + if hasattr(o, '_cp_path'): + controllers_path[o._cp_path] = o app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) self.dispatch = DisableCacheMiddleware(app) From c44888527bc027a49777bc2394a2ae69e64b547c Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 14 Jun 2013 17:26:21 +0200 Subject: [PATCH 22/54] Added werkzeug route building, not yet switched bzr revid: nicolas.vanhoren@openerp.com-20130614152621-t8jv4474gt9k6yib --- addons/web/controllers/main.py | 4 ++-- addons/web/http.py | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index d2f425e0c49..87355d075f3 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1477,8 +1477,8 @@ class Export(openerpweb.Controller): """ return sorted([ controller.fmt - for path, controller in openerpweb.controllers_path.iteritems() - if path.startswith(self._cp_path) + for controller in openerpweb.controllers_object.itervalues() + if (controller._cp_path or "").startswith(self._cp_path) if hasattr(controller, 'fmt') ], key=operator.itemgetter("label")) diff --git a/addons/web/http.py b/addons/web/http.py index a9039f38f9e..ddb253c6b22 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -485,7 +485,6 @@ addons_module = {} addons_manifest = {} controllers_class_path = {} controllers_object = {} -controllers_object_path = {} controllers_path = {} class ControllerType(type): @@ -680,7 +679,7 @@ class Root(object): request.parameter_storage_class = werkzeug.datastructures.ImmutableDict request.app = self - handler = self.find_handler(*(request.path.split('/')[1:])) + handler = self.find_handler(request.path) if not handler: response = werkzeug.exceptions.NotFound() @@ -726,25 +725,32 @@ class Root(object): addons_manifest[module] = manifest self.statics['/%s/static' % module] = path_static - for k, v in controllers_class_path.items(): + self.routing_map = routing.Map() + for k, v in controllers_class_path.iteritems(): o = v[1]() controllers_object[v[0]] = o - controllers_object_path[k] = o - if hasattr(o, '_cp_path'): - controllers_path[o._cp_path] = o + members = inspect.getmembers(o) + for mk, mv in members: + if inspect.ismethod(mv) and getattr(mv, 'exposed', False): + url = os.path.join(o._cp_path, mk) + url += "/" + function = (mv, o.get_wrapped_method(mk)) + self.routing_map.add(routing.Rule(url, function)) + controllers_path[o._cp_path] = o app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) self.dispatch = DisableCacheMiddleware(app) - def find_handler(self, *l): + def find_handler(self, path): """ Tries to discover the controller handling the request for the path specified by the provided parameters - :param l: path sections to a controller or controller method + :param path: path to match :returns: a callable matching the path sections, or ``None`` :rtype: ``Controller | None`` """ + l = path.split('/')[1:] if l: ps = '/' + '/'.join(filter(None, l)) method_name = 'index' From bd5eaa5f9239d92d8517be56dd524f6ab324cc0a Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Fri, 14 Jun 2013 17:52:50 +0200 Subject: [PATCH 23/54] Replaced routing by werkzeug routing bzr revid: nicolas.vanhoren@openerp.com-20130614155250-2a29y2uv2255a79x --- addons/web/http.py | 94 +++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index ddb253c6b22..35c68d1ca82 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -681,26 +681,23 @@ class Root(object): handler = self.find_handler(request.path) - if not handler: - response = werkzeug.exceptions.NotFound() - else: - sid = request.cookies.get('sid') - if not sid: - sid = request.args.get('sid') + sid = request.cookies.get('sid') + if not sid: + sid = request.args.get('sid') - session_gc(self.session_store) + session_gc(self.session_store) - with session_context(request, self.session_store, self.session_lock, sid) as session: - result = handler(request) + with session_context(request, self.session_store, self.session_lock, sid) as session: + result = handler(request) - if isinstance(result, basestring): - headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))] - response = werkzeug.wrappers.Response(result, headers=headers) - else: - response = result + if isinstance(result, basestring): + headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))] + response = werkzeug.wrappers.Response(result, headers=headers) + else: + response = result - if hasattr(response, 'set_cookie'): - response.set_cookie('sid', session.sid) + if hasattr(response, 'set_cookie'): + response.set_cookie('sid', session.sid) return response(environ, start_response) @@ -726,16 +723,20 @@ class Root(object): self.statics['/%s/static' % module] = path_static self.routing_map = routing.Map() - for k, v in controllers_class_path.iteritems(): + for v in controllers_class_path.itervalues(): o = v[1]() controllers_object[v[0]] = o members = inspect.getmembers(o) for mk, mv in members: if inspect.ismethod(mv) and getattr(mv, 'exposed', False): - url = os.path.join(o._cp_path, mk) - url += "/" - function = (mv, o.get_wrapped_method(mk)) - self.routing_map.add(routing.Rule(url, function)) + if mk == "index": + url = o._cp_path + else: + url = os.path.join(o._cp_path, mk) + function = (o.get_wrapped_method(mk), mv) + self.routing_map.add(routing.Rule(url, endpoint=function)) + url = os.path.join(url, "") + self.routing_map.add(routing.Rule(url, endpoint=function)) controllers_path[o._cp_path] = o app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) @@ -747,42 +748,25 @@ class Root(object): specified by the provided parameters :param path: path to match - :returns: a callable matching the path sections, or ``None`` + :returns: a callable matching the path sections :rtype: ``Controller | None`` """ - l = path.split('/')[1:] - if l: - ps = '/' + '/'.join(filter(None, l)) - method_name = 'index' - while ps: - c = controllers_path.get(ps) - if c: - method = getattr(c, method_name, None) - if method: - exposed = getattr(method, 'exposed', False) - auth = getattr(method, 'auth', "auth") - method = c.get_wrapped_method(method_name) - if exposed == 'json': - _logger.debug("Dispatch json to %s %s %s", ps, c, method_name) - def fct(_request): - _req = JsonRequest(_request, method, auth) - with set_request(_req): - return request.dispatch() - return fct - elif exposed == 'http': - _logger.debug("Dispatch http to %s %s %s", ps, c, method_name) - def fct(_request): - _req = HttpRequest(_request, method, auth) - with set_request(_req): - return request.dispatch() - return fct - if method_name != "index": - method_name = "index" - continue - ps, _slash, method_name = ps.rpartition('/') - if not ps and method_name: - ps = '/' - return None + urls = self.routing_map.bind("") + func, original = urls.match(path)[0] + auth = getattr(original, "auth", "auth") + + if original.exposed == "json": + def fct(_request): + _req = JsonRequest(_request, func, auth) + with set_request(_req): + return request.dispatch() + return fct + else: # http + def fct(_request): + _req = HttpRequest(_request, func, auth) + with set_request(_req): + return request.dispatch() + return fct def wsgi_postload(): openerp.wsgi.register_wsgi_handler(Root()) From 0aaa5edd9dd50c03d7e6250481ac0c20874d7ba7 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 18 Jun 2013 13:39:05 +0200 Subject: [PATCH 24/54] removed useless var bzr revid: nicolas.vanhoren@openerp.com-20130618113905-yyezil4z5dl12k2v --- addons/web/http.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 35c68d1ca82..8d7c7fbee9d 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -485,7 +485,6 @@ addons_module = {} addons_manifest = {} controllers_class_path = {} controllers_object = {} -controllers_path = {} class ControllerType(type): def __init__(cls, name, bases, attrs): @@ -737,7 +736,6 @@ class Root(object): self.routing_map.add(routing.Rule(url, endpoint=function)) url = os.path.join(url, "") self.routing_map.add(routing.Rule(url, endpoint=function)) - controllers_path[o._cp_path] = o app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) self.dispatch = DisableCacheMiddleware(app) From f83c1d0f4c724fb72a95d7c62dbf158c86dea6fd Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 18 Jun 2013 16:50:28 +0200 Subject: [PATCH 25/54] changed the way controllers are loaded bzr revid: nicolas.vanhoren@openerp.com-20130618145028-qrhp0uwp03g95u8h --- addons/web/http.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 8d7c7fbee9d..1cc0e488be8 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -483,7 +483,7 @@ request = _request_stack() #---------------------------------------------------------- addons_module = {} addons_manifest = {} -controllers_class_path = {} +controllers_per_module = {} controllers_object = {} class ControllerType(type): @@ -503,9 +503,12 @@ class ControllerType(type): # store the controller in the controllers list name_class = ("%s.%s" % (cls.__module__, cls.__name__), cls) + class_path = name_class[0].split(".") path = attrs.get('_cp_path') - if path and path not in controllers_class_path: - controllers_class_path[path] = name_class + if not path or not class_path[:2] == ["openerp", "addons"]: + return + module = class_path[2] + controllers_per_module.setdefault(module, []).append(name_class) class Controller(object): __metaclass__ = ControllerType @@ -722,20 +725,25 @@ class Root(object): self.statics['/%s/static' % module] = path_static self.routing_map = routing.Map() - for v in controllers_class_path.itervalues(): - o = v[1]() - controllers_object[v[0]] = o - members = inspect.getmembers(o) - for mk, mv in members: - if inspect.ismethod(mv) and getattr(mv, 'exposed', False): - if mk == "index": - url = o._cp_path - else: - url = os.path.join(o._cp_path, mk) - function = (o.get_wrapped_method(mk), mv) - self.routing_map.add(routing.Rule(url, endpoint=function)) - url = os.path.join(url, "") - self.routing_map.add(routing.Rule(url, endpoint=function)) + modules = controllers_per_module.keys() + modules.sort() + modules.remove("web") + modules = ["web"] + modules + for module in modules: + for v in controllers_per_module[module]: + o = v[1]() + controllers_object[v[0]] = o + members = inspect.getmembers(o) + for mk, mv in members: + if inspect.ismethod(mv) and getattr(mv, 'exposed', False): + if mk == "index": + url = o._cp_path + else: + url = os.path.join(o._cp_path, mk) + function = (o.get_wrapped_method(mk), mv) + self.routing_map.add(routing.Rule(url, endpoint=function)) + url = os.path.join(url, "") + self.routing_map.add(routing.Rule(url, endpoint=function)) app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) self.dispatch = DisableCacheMiddleware(app) From c2531549ac97fe6ce6a57d3cbad26b7063a55b25 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 18 Jun 2013 17:42:07 +0200 Subject: [PATCH 26/54] small improvements bzr revid: nicolas.vanhoren@openerp.com-20130618154207-p1n49dwoj2357n5j --- addons/web/http.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 1cc0e488be8..ad5b2eee5e6 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -121,7 +121,10 @@ class WebRequest(object): self.session = session.OpenERPSession() self.httpsession[self.session_id] = self.session - # TODO: remove this shit + with set_request(self): + self.db = (self.session._db or openerp.addons.web.controllers.main.db_monodb()).lower() + + # TODO: remove this # set db/uid trackers - they're cleaned up at the WSGI # dispatching phase in openerp.service.wsgi_server.application if self.session._db: @@ -269,7 +272,6 @@ class JsonRequest(WebRequest): """ args = self.httprequest.args jsonp = args.get('jsonp') - requestf = None request = None request_id = args.get('id') @@ -289,16 +291,13 @@ class JsonRequest(WebRequest): request = self.session.jsonp_requests.pop(request_id, "") else: # regular jsonrpc2 - requestf = self.httprequest.stream + request = self.httprequest.stream.read() response = {"jsonrpc": "2.0" } error = None try: # Read POST content or POST Form Data named "request" - if requestf: - self.jsonrequest = simplejson.load(requestf, object_hook=reject_nonliteral) - else: - self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral) + self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral) self.init(self.jsonrequest.get("params", {})) #if _logger.isEnabledFor(logging.DEBUG): # _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest)) From 424c2e61e638989ebea39a1ea1a0a77831de2a00 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 18 Jun 2013 18:10:50 +0200 Subject: [PATCH 27/54] wip bzr revid: nicolas.vanhoren@openerp.com-20130618161050-flzsozt2palfqp1t --- addons/web/http.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index ad5b2eee5e6..980acfe9509 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -676,20 +676,20 @@ class Root(object): Call the object directly. """ - request = werkzeug.wrappers.Request(environ) - request.parameter_storage_class = werkzeug.datastructures.ImmutableDict - request.app = self + httprequest = werkzeug.wrappers.Request(environ) + httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableDict + httprequest.app = self - handler = self.find_handler(request.path) + handler = self.find_handler(httprequest.path) - sid = request.cookies.get('sid') + sid = httprequest.cookies.get('sid') if not sid: - sid = request.args.get('sid') + sid = httprequest.args.get('sid') session_gc(self.session_store) - with session_context(request, self.session_store, self.session_lock, sid) as session: - result = handler(request) + with session_context(httprequest, self.session_store, self.session_lock, sid) as session: + result = handler(httprequest) if isinstance(result, basestring): headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))] @@ -761,14 +761,14 @@ class Root(object): auth = getattr(original, "auth", "auth") if original.exposed == "json": - def fct(_request): - _req = JsonRequest(_request, func, auth) + def fct(httprequest): + _req = JsonRequest(httprequest, func, auth) with set_request(_req): return request.dispatch() return fct else: # http - def fct(_request): - _req = HttpRequest(_request, func, auth) + def fct(httprequest): + _req = HttpRequest(httprequest, func, auth) with set_request(_req): return request.dispatch() return fct From fdc08bd64e4b141376f4747b01c05662fcb05484 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 18 Jun 2013 18:20:33 +0200 Subject: [PATCH 28/54] wip bzr revid: nicolas.vanhoren@openerp.com-20130618162033-pgl073m9g91rgtxg --- addons/web/http.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 980acfe9509..1bb03a60093 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -680,8 +680,6 @@ class Root(object): httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableDict httprequest.app = self - handler = self.find_handler(httprequest.path) - sid = httprequest.cookies.get('sid') if not sid: sid = httprequest.args.get('sid') @@ -689,6 +687,8 @@ class Root(object): session_gc(self.session_store) with session_context(httprequest, self.session_store, self.session_lock, sid) as session: + handler = self.find_handler(httprequest.path) + result = handler(httprequest) if isinstance(result, basestring): From 14329ab05707738fdcb0be55c44f0ea299857c0f Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Tue, 18 Jun 2013 19:20:59 +0200 Subject: [PATCH 29/54] added request typing guessing bzr revid: nicolas.vanhoren@openerp.com-20130618172059-v34p34jkrr8vclb8 --- addons/web/http.py | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 1bb03a60093..931d359c542 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -95,14 +95,14 @@ class WebRequest(object): ``int``, the id of the user related to the current request. Can be ``None`` if the current request uses the @nodb or the @noauth decorator. """ - def __init__(self, httprequest, func, auth_method="auth"): + def __init__(self, httprequest): self.httprequest = httprequest self.httpresponse = None self.httpsession = httprequest.session self.db = None self.uid = None - self.func = func - self.auth_method = auth_method + self.func = None + self.auth_method = None self._cr_cm = None self._cr = None @@ -687,9 +687,12 @@ class Root(object): session_gc(self.session_store) with session_context(httprequest, self.session_store, self.session_lock, sid) as session: - handler = self.find_handler(httprequest.path) + request = self._build_request(httprequest) - result = handler(httprequest) + self.find_handler(httprequest.path, request) + + with set_request(request): + result = request.dispatch() if isinstance(result, basestring): headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))] @@ -700,7 +703,20 @@ class Root(object): if hasattr(response, 'set_cookie'): response.set_cookie('sid', session.sid) - return response(environ, start_response) + return response(environ, start_response) + + def _build_request(self, httprequest): + if httprequest.args.get('jsonp'): + return JsonRequest(httprequest) + + content = httprequest.stream.read() + import cStringIO + httprequest.stream = cStringIO.StringIO(content) + try: + simplejson.loads(content) + return JsonRequest(httprequest) + except: + return HttpRequest(httprequest) def load_addons(self): """ Load all addons from addons patch containg static files and @@ -747,7 +763,7 @@ class Root(object): app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) self.dispatch = DisableCacheMiddleware(app) - def find_handler(self, path): + def find_handler(self, path, request): """ Tries to discover the controller handling the request for the path specified by the provided parameters @@ -760,18 +776,8 @@ class Root(object): func, original = urls.match(path)[0] auth = getattr(original, "auth", "auth") - if original.exposed == "json": - def fct(httprequest): - _req = JsonRequest(httprequest, func, auth) - with set_request(_req): - return request.dispatch() - return fct - else: # http - def fct(httprequest): - _req = HttpRequest(httprequest, func, auth) - with set_request(_req): - return request.dispatch() - return fct + request.func = func + request.auth_method = auth def wsgi_postload(): openerp.wsgi.register_wsgi_handler(Root()) From d6676ec846c3553915d849056be858434ee66afc Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 19 Jun 2013 11:44:37 +0200 Subject: [PATCH 30/54] Changed the way the request init bzr revid: nicolas.vanhoren@openerp.com-20130619094437-ybbgtrgz12n66sno --- addons/web/http.py | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index 931d359c542..b0ab28b2249 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -265,23 +265,28 @@ class JsonRequest(WebRequest): "id": null} """ - def dispatch(self): - """ Calls the method asked for by the JSON-RPC2 or JSONP request + def __init__(self, *args): + super(JsonRequest, self).__init__(*args) + + self.jsonp_handler = None - :returns: an utf8 encoded JSON-RPC2 or JSONP reply - """ args = self.httprequest.args jsonp = args.get('jsonp') + self.jsonp = jsonp request = None request_id = args.get('id') if jsonp and self.httprequest.method == 'POST': # jsonp 2 steps step1 POST: save call self.init(args) - self.session.jsonp_requests[request_id] = self.httprequest.form['r'] - headers=[('Content-Type', 'text/plain; charset=utf-8')] - r = werkzeug.wrappers.Response(request_id, headers=headers) - return r + + def handler(): + self.session.jsonp_requests[request_id] = self.httprequest.form['r'] + headers=[('Content-Type', 'text/plain; charset=utf-8')] + r = werkzeug.wrappers.Response(request_id, headers=headers) + return r + self.jsonp_handler = handler + return elif jsonp and args.get('r'): # jsonp method GET request = args.get('r') @@ -293,12 +298,20 @@ class JsonRequest(WebRequest): # regular jsonrpc2 request = self.httprequest.stream.read() + # Read POST content or POST Form Data named "request" + self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral) + self.init(self.jsonrequest.get("params", {})) + + def dispatch(self): + """ Calls the method asked for by the JSON-RPC2 or JSONP request + + :returns: an utf8 encoded JSON-RPC2 or JSONP reply + """ + if self.jsonp_handler: + return self.jsonp_handler() response = {"jsonrpc": "2.0" } error = None try: - # Read POST content or POST Form Data named "request" - self.jsonrequest = simplejson.loads(request, object_hook=reject_nonliteral) - self.init(self.jsonrequest.get("params", {})) #if _logger.isEnabledFor(logging.DEBUG): # _logger.debug("--> %s.%s\n%s", func.im_class.__name__, func.__name__, pprint.pformat(self.jsonrequest)) response['id'] = self.jsonrequest.get('id') @@ -325,13 +338,13 @@ class JsonRequest(WebRequest): if _logger.isEnabledFor(logging.DEBUG): _logger.debug("<--\n%s", pprint.pformat(response)) - if jsonp: + if self.jsonp: # If we use jsonp, that's mean we are called from another host # Some browser (IE and Safari) do no allow third party cookies # We need then to manage http sessions manually. response['httpsessionid'] = self.httpsession.sid mime = 'application/javascript' - body = "%s(%s);" % (jsonp, simplejson.dumps(response),) + body = "%s(%s);" % (self.jsonp, simplejson.dumps(response),) else: mime = 'application/json' body = simplejson.dumps(response) @@ -385,11 +398,14 @@ def jsonrequest(f): class HttpRequest(WebRequest): """ Regular GET/POST request """ - def dispatch(self): + def __init__(self, *args): + super(HttpRequest, self).__init__(*args) params = dict(self.httprequest.args) params.update(self.httprequest.form) params.update(self.httprequest.files) self.init(params) + + def dispatch(self): akw = {} for key, value in self.httprequest.args.iteritems(): if isinstance(value, basestring) and len(value) < 1024: From 2e0461e483438710dd1e967863c662bc1440ccee Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 19 Jun 2013 11:49:03 +0200 Subject: [PATCH 31/54] wip bzr revid: nicolas.vanhoren@openerp.com-20130619094903-5vblw05lvm4ow19n --- addons/web/http.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index b0ab28b2249..b5a94de4b6c 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -705,7 +705,7 @@ class Root(object): with session_context(httprequest, self.session_store, self.session_lock, sid) as session: request = self._build_request(httprequest) - self.find_handler(httprequest.path, request) + self.find_handler(request) with set_request(request): result = request.dispatch() @@ -779,7 +779,7 @@ class Root(object): app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) self.dispatch = DisableCacheMiddleware(app) - def find_handler(self, path, request): + def find_handler(self, request): """ Tries to discover the controller handling the request for the path specified by the provided parameters @@ -788,6 +788,7 @@ class Root(object): :returns: a callable matching the path sections :rtype: ``Controller | None`` """ + path = request.httprequest.path urls = self.routing_map.bind("") func, original = urls.match(path)[0] auth = getattr(original, "auth", "auth") From ee104c9ed0ef39dd9fa941bbe7240359dd29b34b Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 19 Jun 2013 12:15:50 +0200 Subject: [PATCH 32/54] now uses one routing map per db bzr revid: nicolas.vanhoren@openerp.com-20130619101550-e3nb5aq2u474b1wx --- addons/web/http.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index b5a94de4b6c..b9873fb653e 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -670,7 +670,9 @@ class Root(object): def __init__(self): self.addons = {} self.statics = {} - self.routing_map = None + + self.db_routers = {} + self.db_routers_lock = threading.Lock() self.load_addons() @@ -680,6 +682,7 @@ class Root(object): self.session_lock = threading.Lock() _logger.debug('HTTP sessions stored in: %s', path) + def __call__(self, environ, start_response): """ Handle a WSGI request """ @@ -755,7 +758,13 @@ class Root(object): addons_manifest[module] = manifest self.statics['/%s/static' % module] = path_static - self.routing_map = routing.Map() + self.db_routers[None] = self._build_router(None) + + app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) + self.dispatch = DisableCacheMiddleware(app) + + def _build_router(self, db): + routing_map = routing.Map() modules = controllers_per_module.keys() modules.sort() modules.remove("web") @@ -767,17 +776,27 @@ class Root(object): members = inspect.getmembers(o) for mk, mv in members: if inspect.ismethod(mv) and getattr(mv, 'exposed', False): + auth = getattr(mv, 'auth', None) + if (db is None and auth != 'nodb'): + continue if mk == "index": url = o._cp_path else: url = os.path.join(o._cp_path, mk) function = (o.get_wrapped_method(mk), mv) - self.routing_map.add(routing.Rule(url, endpoint=function)) + routing_map.add(routing.Rule(url, endpoint=function)) url = os.path.join(url, "") - self.routing_map.add(routing.Rule(url, endpoint=function)) + routing_map.add(routing.Rule(url, endpoint=function)) + return routing_map - app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) - self.dispatch = DisableCacheMiddleware(app) + def get_db_router(self, db): + with self.db_routers_lock: + router = self.db_routers.get(db) + if not router: + router = self._build_router(db) + with self.db_routers_lock: + router = self.db_routers[db] = router + return router def find_handler(self, request): """ @@ -789,8 +808,9 @@ class Root(object): :rtype: ``Controller | None`` """ path = request.httprequest.path - urls = self.routing_map.bind("") + urls = self.get_db_router(request.db).bind("") func, original = urls.match(path)[0] + auth = getattr(original, "auth", "auth") request.func = func From 1d2bab08e118eeca63c6aa06470394327ab3d1f4 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 19 Jun 2013 12:55:31 +0200 Subject: [PATCH 33/54] now calls registrymanager update methods bzr revid: nicolas.vanhoren@openerp.com-20130619105531-aqo7lc71d48lzmw8 --- addons/web/http.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index b9873fb653e..fc75c323d3f 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -670,7 +670,7 @@ class Root(object): def __init__(self): self.addons = {} self.statics = {} - + self.db_routers = {} self.db_routers_lock = threading.Lock() @@ -707,12 +707,16 @@ class Root(object): with session_context(httprequest, self.session_store, self.session_lock, sid) as session: request = self._build_request(httprequest) + db = request.db + + updated = openerp.modules.registry.RegistryManager.check_registry_signaling(db) self.find_handler(request) - with set_request(request): result = request.dispatch() + openerp.modules.registry.RegistryManager.signal_caches_change(db) + if isinstance(result, basestring): headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))] response = werkzeug.wrappers.Response(result, headers=headers) From da9abb25ed38050af9c3bdef870cd3724170d6ba Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 19 Jun 2013 14:23:22 +0200 Subject: [PATCH 34/54] fixed minor stuff and removed controllers_object, which will cause some addons to crash bzr revid: nicolas.vanhoren@openerp.com-20130619122322-aza8yd8jz4lbndnq --- addons/web/http.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index fc75c323d3f..d8e12fef1e8 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -105,6 +105,7 @@ class WebRequest(object): self.auth_method = None self._cr_cm = None self._cr = None + self.func_request_type = None def init(self, params): self.params = dict(params) @@ -201,6 +202,9 @@ class WebRequest(object): request._cr = None with with_obj(): + if self.func_request_type != self._request_type: + raise Exception("%s: Function declared as capable of handling request of type '%s' but called with a request of type '%s'" \ + % (self.httprequest.path, self.func_request_type, self._request_type)) return self.func(*args, **kwargs) finally: # just to be sure no one tries to re-use the request @@ -265,6 +269,8 @@ class JsonRequest(WebRequest): "id": null} """ + _request_type = "json" + def __init__(self, *args): super(JsonRequest, self).__init__(*args) @@ -398,6 +404,8 @@ def jsonrequest(f): class HttpRequest(WebRequest): """ Regular GET/POST request """ + _request_type = "http" + def __init__(self, *args): super(HttpRequest, self).__init__(*args) params = dict(self.httprequest.args) @@ -499,7 +507,6 @@ request = _request_stack() addons_module = {} addons_manifest = {} controllers_per_module = {} -controllers_object = {} class ControllerType(type): def __init__(cls, name, bases, attrs): @@ -709,13 +716,18 @@ class Root(object): request = self._build_request(httprequest) db = request.db - updated = openerp.modules.registry.RegistryManager.check_registry_signaling(db) + if db: + updated = openerp.modules.registry.RegistryManager.check_registry_signaling(db) + if updated: + with self.db_routers_lock: + del self.db_routers[db] self.find_handler(request) with set_request(request): result = request.dispatch() - openerp.modules.registry.RegistryManager.signal_caches_change(db) + if db: + openerp.modules.registry.RegistryManager.signal_caches_change(db) if isinstance(result, basestring): headers=[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', len(result))] @@ -776,7 +788,6 @@ class Root(object): for module in modules: for v in controllers_per_module[module]: o = v[1]() - controllers_object[v[0]] = o members = inspect.getmembers(o) for mk, mv in members: if inspect.ismethod(mv) and getattr(mv, 'exposed', False): @@ -815,10 +826,9 @@ class Root(object): urls = self.get_db_router(request.db).bind("") func, original = urls.match(path)[0] - auth = getattr(original, "auth", "auth") - request.func = func - request.auth_method = auth + request.auth_method = getattr(original, "auth", "auth") + request.func_request_type = original.exposed def wsgi_postload(): openerp.wsgi.register_wsgi_handler(Root()) From 1a977c7d0d357588e5aa142830609621ecdee832 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 19 Jun 2013 14:33:25 +0200 Subject: [PATCH 35/54] wip bzr revid: nicolas.vanhoren@openerp.com-20130619123325-mal77wf9pvdnubo2 --- addons/web/http.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index d8e12fef1e8..df9a785752d 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -774,8 +774,6 @@ class Root(object): addons_manifest[module] = manifest self.statics['/%s/static' % module] = path_static - self.db_routers[None] = self._build_router(None) - app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, self.statics) self.dispatch = DisableCacheMiddleware(app) From b3a99c860f6e30f6a159ca1d9f53a14d97a21158 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 19 Jun 2013 15:47:11 +0200 Subject: [PATCH 36/54] now uses only the installed modules to build the routes bzr revid: nicolas.vanhoren@openerp.com-20130619134711-evs92rdtikdeqjt1 --- addons/web/http.py | 49 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/addons/web/http.py b/addons/web/http.py index df9a785752d..4f89dbf10bb 100644 --- a/addons/web/http.py +++ b/addons/web/http.py @@ -203,8 +203,8 @@ class WebRequest(object): with with_obj(): if self.func_request_type != self._request_type: - raise Exception("%s: Function declared as capable of handling request of type '%s' but called with a request of type '%s'" \ - % (self.httprequest.path, self.func_request_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)) return self.func(*args, **kwargs) finally: # just to be sure no one tries to re-use the request @@ -722,8 +722,8 @@ class Root(object): with self.db_routers_lock: del self.db_routers[db] - self.find_handler(request) with set_request(request): + self.find_handler() result = request.dispatch() if db: @@ -778,20 +778,45 @@ class Root(object): self.dispatch = DisableCacheMiddleware(app) def _build_router(self, db): + _logger.info("Generating routing configuration for database %s" % db) routing_map = routing.Map() - modules = controllers_per_module.keys() - modules.sort() - modules.remove("web") - modules = ["web"] + modules + modules_set = set(controllers_per_module.keys()) + modules_set -= set("web") + + modules = ["web"] + sorted(modules_set) + # building all nodb methods + for module in modules: + for v in controllers_per_module[module]: + members = inspect.getmembers(v[1]()) + for mk, mv in members: + if inspect.ismethod(mv) and getattr(mv, 'exposed', False) and getattr(mv, 'auth', None) == "nodb": + o = v[1]() + if mk == "index": + url = o._cp_path + else: + url = os.path.join(o._cp_path, mk) + function = (o.get_wrapped_method(mk), mv) + routing_map.add(routing.Rule(url, endpoint=function)) + url = os.path.join(url, "") + routing_map.add(routing.Rule(url, endpoint=function)) + + if not db: + return routing_map + + registry = openerp.modules.registry.RegistryManager.get(db) + with registry.cursor() as cr: + m = registry.get('ir.module.module') + ids = m.search(cr, openerp.SUPERUSER_ID, [('state','=','installed')]) + installed = set([x['name'] for x in m.read(cr, 1, ids, ['name'])]) + modules_set -= set(installed) + modules = ["web"] + sorted(modules_set) + # building all other methods for module in modules: for v in controllers_per_module[module]: o = v[1]() members = inspect.getmembers(o) for mk, mv in members: - if inspect.ismethod(mv) and getattr(mv, 'exposed', False): - auth = getattr(mv, 'auth', None) - if (db is None and auth != 'nodb'): - continue + if inspect.ismethod(mv) and getattr(mv, 'exposed', False) and getattr(mv, 'auth', None) != "nodb": if mk == "index": url = o._cp_path else: @@ -811,7 +836,7 @@ class Root(object): router = self.db_routers[db] = router return router - def find_handler(self, request): + def find_handler(self): """ Tries to discover the controller handling the request for the path specified by the provided parameters From 69ae9cc0b5fa0d0db0a72111619f6b27dfd8a68e Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 19 Jun 2013 18:16:27 +0200 Subject: [PATCH 37/54] Big refactoring to prepare even more refactoring bzr revid: nicolas.vanhoren@openerp.com-20130619161627-543e0brldclvvw98 --- addons/web/controllers/main.py | 412 +++++++++++++++--------------- addons/web/controllers/testing.py | 4 +- addons/web/http.py | 94 ++++--- 3 files changed, 267 insertions(+), 243 deletions(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 87355d075f3..708600fdb99 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -34,9 +34,9 @@ import openerp.modules.registry from openerp.tools.translate import _ from .. import http -openerpweb = http +http = http -from openerp.addons.web.http import request as req, noauth, nodb +from openerp.addons.web.http import request, nodb, noauth #---------------------------------------------------------- # OpenERP Web helpers @@ -87,9 +87,9 @@ def rjsmin(script): return result def db_list(): - proxy = req.session.proxy("db") + proxy = request.session.proxy("db") dbs = proxy.list() - h = req.httprequest.environ['HTTP_HOST'].split(':')[0] + h = request.httprequest.environ['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)] @@ -100,7 +100,7 @@ def db_monodb_redirect(): redirect = False # 1 try the db in the url - db_url = req.params.get('db') + db_url = request.params.get('db') if db_url: return (db_url, False) @@ -111,7 +111,7 @@ def db_monodb_redirect(): dbs = [] # 2 use the database from the cookie if it's listable and still listed - cookie_db = req.httprequest.cookies.get('last_used_database') + cookie_db = request.httprequest.cookies.get('last_used_database') if cookie_db in dbs: db = cookie_db @@ -121,9 +121,9 @@ def db_monodb_redirect(): # redirect to the chosen db if multiple are available if db and len(dbs) > 1: - query = dict(urlparse.parse_qsl(req.httprequest.query_string, keep_blank_values=True)) + query = dict(urlparse.parse_qsl(request.httprequest.query_string, keep_blank_values=True)) query.update({ 'db': db }) - redirect = req.httprequest.path + '?' + urllib.urlencode(query) + redirect = request.httprequest.path + '?' + urllib.urlencode(query) return (db, redirect) def db_monodb(): @@ -131,9 +131,9 @@ def db_monodb(): return db_monodb_redirect()[0] def redirect_with_hash(url, code=303): - if req.httprequest.user_agent.browser == 'msie': + if request.httprequest.user_agent.browser == 'msie': try: - version = float(req.httprequest.user_agent.version) + version = float(request.httprequest.user_agent.version) if version < 10: return "" % url except Exception: @@ -186,18 +186,18 @@ def module_topological_sort(modules): def module_installed(): # Candidates module the current heuristic is the /static dir - loadable = openerpweb.addons_manifest.keys() + loadable = http.addons_manifest.keys() modules = {} # Retrieve database installed modules # TODO The following code should move to ir.module.module.list_installed_modules() - Modules = req.session.model('ir.module.module') + Modules = request.session.model('ir.module.module') domain = [('state','=','installed'), ('name','in', loadable)] for module in Modules.search_read(domain, ['name', 'dependencies_id']): modules[module['name']] = [] deps = module.get('dependencies_id') if deps: - deps_read = req.session.model('ir.module.module.dependency').read(deps, ['name']) + deps_read = request.session.model('ir.module.module.dependency').read(deps, ['name']) dependencies = [i['name'] for i in deps_read] modules[module['name']] = dependencies @@ -205,7 +205,7 @@ def module_installed(): return sorted_modules def module_installed_bypass_session(dbname): - loadable = openerpweb.addons_manifest.keys() + loadable = http.addons_manifest.keys() modules = {} try: registry = openerp.modules.registry.RegistryManager.get(dbname) @@ -231,7 +231,7 @@ def module_boot(db=None): serverside = [] dbside = [] for i in server_wide_modules: - if i in openerpweb.addons_manifest: + if i in http.addons_manifest: serverside.append(i) monodb = db or db_monodb() if monodb: @@ -317,7 +317,7 @@ def manifest_glob(extension, addons=None, db=None, include_remotes=False): addons = addons.split(',') r = [] for addon in addons: - manifest = openerpweb.addons_manifest.get(addon, None) + manifest = http.addons_manifest.get(addon, None) if not manifest: continue # ensure does not ends with / @@ -338,7 +338,7 @@ def manifest_list(extension, mods=None, db=None): db: a database name (return all installed modules in that database) """ files = manifest_glob(extension, addons=mods, db=db, include_remotes=True) - if not req.debug: + if not request.debug: path = '/web/webclient/' + extension if mods is not None: path += '?' + urllib.urlencode({'mods': mods}) @@ -383,28 +383,28 @@ def make_conditional(response, last_modified=None, etag=None): response.last_modified = last_modified if etag: response.set_etag(etag) - return response.make_conditional(req.httprequest) + return response.make_conditional(request.httprequest) def login_and_redirect(db, login, key, redirect_url='/'): - wsgienv = req.httprequest.environ + wsgienv = request.httprequest.environ env = dict( - base_location=req.httprequest.url_root.rstrip('/'), + base_location=request.httprequest.url_root.rstrip('/'), HTTP_HOST=wsgienv['HTTP_HOST'], REMOTE_ADDR=wsgienv['REMOTE_ADDR'], ) - req.session.authenticate(db, login, key, env) + request.session.authenticate(db, login, key, env) return set_cookie_and_redirect(redirect_url) def set_cookie_and_redirect(redirect_url): redirect = werkzeug.utils.redirect(redirect_url, 303) redirect.autocorrect_location_header = False - cookie_val = urllib2.quote(simplejson.dumps(req.session_id)) + cookie_val = urllib2.quote(simplejson.dumps(request.session_id)) redirect.set_cookie('instance0|session_id', cookie_val) return redirect def load_actions_from_ir_values(key, key2, models, meta): - Values = req.session.model('ir.values') - actions = Values.get(key, key2, models, meta, req.context) + Values = request.session.model('ir.values') + actions = Values.get(key, key2, models, meta, request.context) return [(id, name, clean_action(action)) for id, name, action in actions] @@ -530,8 +530,8 @@ def xml2json_from_elementtree(el, preserve_whitespaces=False): def content_disposition(filename): filename = filename.encode('utf8') escaped = urllib2.quote(filename) - browser = req.httprequest.user_agent.browser - version = int((req.httprequest.user_agent.version or '0').split('.')[0]) + browser = request.httprequest.user_agent.browser + version = int((request.httprequest.user_agent.version or '0').split('.')[0]) if browser == 'msie' and version < 9: return "attachment; filename=%s" % escaped elif browser == 'safari': @@ -570,10 +570,10 @@ html_template = """ """ -class Home(openerpweb.Controller): +class Home(http.Controller): _cp_path = '/' - @openerpweb.httprequest + @http.httprequest @nodb def index(self, s_action=None, db=None, **kw): db, redir = db_monodb_redirect() @@ -591,40 +591,40 @@ class Home(openerpweb.Controller): } return r - @openerpweb.httprequest + @http.httprequest def login(self, db, login, key): return login_and_redirect(db, login, key) - @openerpweb.jsonrequest + @http.jsonrequest @nodb def jsonrpc(self, service, method, args): """ Method used by client APIs to contact OpenERP. """ return openerp.netsvc.dispatch_rpc(service, method, args) -class WebClient(openerpweb.Controller): +class WebClient(http.Controller): _cp_path = "/web/webclient" - @openerpweb.jsonrequest + @http.jsonrequest @nodb def csslist(self, mods=None): return manifest_list('css', mods=mods) - @openerpweb.jsonrequest + @http.jsonrequest @nodb def jslist(self, mods=None): return manifest_list('js', mods=mods) - @openerpweb.jsonrequest + @http.jsonrequest @nodb def qweblist(self, mods=None): return manifest_list('qweb', mods=mods) - @openerpweb.httprequest + @http.httprequest @nodb def css(self, mods=None, db=None): files = list(manifest_glob('css', addons=mods, db=db)) last_modified = get_last_modified(f[0] for f in files) - if req.httprequest.if_modified_since and req.httprequest.if_modified_since >= last_modified: + if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified: return werkzeug.wrappers.Response(status=304) file_map = dict(files) @@ -668,38 +668,38 @@ class WebClient(openerpweb.Controller): content = '\n'.join(matches) return make_conditional( - req.make_response(content, [('Content-Type', 'text/css')]), + request.make_response(content, [('Content-Type', 'text/css')]), last_modified, checksum) - @openerpweb.httprequest + @http.httprequest @nodb def js(self, mods=None, db=None): files = [f[0] for f in manifest_glob('js', addons=mods, db=db)] last_modified = get_last_modified(files) - if req.httprequest.if_modified_since and req.httprequest.if_modified_since >= last_modified: + if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified: return werkzeug.wrappers.Response(status=304) content, checksum = concat_js(files) return make_conditional( - req.make_response(content, [('Content-Type', 'application/javascript')]), + request.make_response(content, [('Content-Type', 'application/javascript')]), last_modified, checksum) - @openerpweb.httprequest + @http.httprequest @nodb def qweb(self, mods=None, db=None): files = [f[0] for f in manifest_glob('qweb', addons=mods, db=db)] last_modified = get_last_modified(files) - if req.httprequest.if_modified_since and req.httprequest.if_modified_since >= last_modified: + if request.httprequest.if_modified_since and request.httprequest.if_modified_since >= last_modified: return werkzeug.wrappers.Response(status=304) content, checksum = concat_xml(files) return make_conditional( - req.make_response(content, [('Content-Type', 'text/xml')]), + request.make_response(content, [('Content-Type', 'text/xml')]), last_modified, checksum) - @openerpweb.jsonrequest + @http.jsonrequest @nodb def bootstrap_translations(self, mods): """ Load local translations from *.po files, as a temporary solution @@ -709,12 +709,12 @@ class WebClient(openerpweb.Controller): # For performance reasons we only load a single translation, so for # sub-languages (that should only be partially translated) we load the # main language PO instead - that should be enough for the login screen. - lang = req.lang.split('_')[0] + lang = request.lang.split('_')[0] translations_per_module = {} for addon_name in mods: - if openerpweb.addons_manifest[addon_name].get('bootstrap'): - addons_path = openerpweb.addons_manifest[addon_name]['addons_path'] + if http.addons_manifest[addon_name].get('bootstrap'): + addons_path = http.addons_manifest[addon_name]['addons_path'] f_name = os.path.join(addons_path, addon_name, "i18n", lang + ".po") if not os.path.exists(f_name): continue @@ -723,9 +723,9 @@ class WebClient(openerpweb.Controller): return {"modules": translations_per_module, "lang_parameters": None} - @openerpweb.jsonrequest + @http.jsonrequest def translations(self, mods, lang): - res_lang = req.session.model('res.lang') + res_lang = request.session.model('res.lang') ids = res_lang.search([("code", "=", lang)]) lang_params = None if ids: @@ -734,7 +734,7 @@ class WebClient(openerpweb.Controller): # Regional languages (ll_CC) must inherit/override their parent lang (ll), but this is # done server-side when the language is loaded, so we only need to load the user's lang. - ir_translation = req.session.model('ir.translation') + ir_translation = request.session.model('ir.translation') translations_per_module = {} messages = ir_translation.search_read([('module','in',mods),('lang','=',lang), ('comments','like','openerp-web'),('value','!=',False), @@ -748,15 +748,15 @@ class WebClient(openerpweb.Controller): return {"modules": translations_per_module, "lang_parameters": lang_params} - @openerpweb.jsonrequest + @http.jsonrequest @nodb def version_info(self): return openerp.service.common.exp_version() -class Proxy(openerpweb.Controller): +class Proxy(http.Controller): _cp_path = '/web/proxy' - @openerpweb.jsonrequest + @http.jsonrequest @nodb def load(self, path): """ Proxies an HTTP request through a JSON request. @@ -770,37 +770,37 @@ class Proxy(openerpweb.Controller): from werkzeug.test import Client from werkzeug.wrappers import BaseResponse - return Client(req.httprequest.app, BaseResponse).get(path).data + return Client(request.httprequest.app, BaseResponse).get(path).data -class Database(openerpweb.Controller): +class Database(http.Controller): _cp_path = "/web/database" - @openerpweb.jsonrequest + @http.jsonrequest @nodb def get_list(self): return db_list() - @openerpweb.jsonrequest + @http.jsonrequest @nodb def create(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) - return req.session.proxy("db").create_database( + return request.session.proxy("db").create_database( params['super_admin_pwd'], params['db_name'], bool(params.get('demo_data')), params['db_lang'], params['create_admin_pwd']) - @openerpweb.jsonrequest + @http.jsonrequest @nodb def duplicate(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) - return req.session.proxy("db").duplicate_database( + return request.session.proxy("db").duplicate_database( params['super_admin_pwd'], params['db_original_name'], params['db_name']) - @openerpweb.jsonrequest + @http.jsonrequest @nodb def duplicate(self, fields): params = dict(map(operator.itemgetter('name', 'value'), fields)) @@ -810,9 +810,9 @@ class Database(openerpweb.Controller): params['db_name'], ) - return req.session.proxy("db").duplicate_database(*duplicate_attrs) + return request.session.proxy("db").duplicate_database(*duplicate_attrs) - @openerpweb.jsonrequest + @http.jsonrequest @nodb def drop(self, fields): password, db = operator.itemgetter( @@ -820,24 +820,24 @@ class Database(openerpweb.Controller): dict(map(operator.itemgetter('name', 'value'), fields))) try: - return req.session.proxy("db").drop(password, db) + return request.session.proxy("db").drop(password, db) except openerp.exceptions.AccessDenied: return {'error': 'AccessDenied', 'title': 'Drop Database'} except Exception: return {'error': _('Could not drop database !'), 'title': _('Drop Database')} - @openerpweb.httprequest + @http.httprequest @nodb def backup(self, backup_db, backup_pwd, token): try: db_dump = base64.b64decode( - req.session.proxy("db").dump(backup_pwd, backup_db)) + request.session.proxy("db").dump(backup_pwd, backup_db)) filename = "%(db)s_%(timestamp)s.dump" % { 'db': backup_db, 'timestamp': datetime.datetime.utcnow().strftime( "%Y-%m-%d_%H-%M-%SZ") } - return req.make_response(db_dump, + return request.make_response(db_dump, [('Content-Type', 'application/octet-stream; charset=binary'), ('Content-Disposition', content_disposition(filename))], {'fileToken': int(token)} @@ -845,63 +845,63 @@ class Database(openerpweb.Controller): except Exception, e: return simplejson.dumps([[],[{'error': openerp.tools.ustr(e), 'title': _('Backup Database')}]]) - @openerpweb.httprequest + @http.httprequest @nodb def restore(self, db_file, restore_pwd, new_db): try: data = base64.b64encode(db_file.read()) - req.session.proxy("db").restore(restore_pwd, new_db, data) + request.session.proxy("db").restore(restore_pwd, new_db, data) return '' except openerp.exceptions.AccessDenied, e: raise Exception("AccessDenied") - @openerpweb.jsonrequest + @http.jsonrequest @nodb def change_password(self, fields): old_password, new_password = operator.itemgetter( 'old_pwd', 'new_pwd')( dict(map(operator.itemgetter('name', 'value'), fields))) try: - return req.session.proxy("db").change_admin_password(old_password, new_password) + return request.session.proxy("db").change_admin_password(old_password, new_password) except openerp.exceptions.AccessDenied: return {'error': 'AccessDenied', 'title': _('Change Password')} except Exception: return {'error': _('Error, password not changed !'), 'title': _('Change Password')} -class Session(openerpweb.Controller): +class Session(http.Controller): _cp_path = "/web/session" def session_info(self): - req.session.ensure_valid() + request.session.ensure_valid() return { - "session_id": req.session_id, - "uid": req.session._uid, - "user_context": req.session.get_context() if req.session._uid else {}, - "db": req.session._db, - "username": req.session._login, + "session_id": request.session_id, + "uid": request.session._uid, + "user_context": request.session.get_context() if request.session._uid else {}, + "db": request.session._db, + "username": request.session._login, } - @openerpweb.jsonrequest + @http.jsonrequest @nodb def get_session_info(self): - req.uid = req.session._uid - req.db = req.session._db + request.uid = request.session._uid + request.db = request.session._db return self.session_info() - @openerpweb.jsonrequest + @http.jsonrequest @nodb def authenticate(self, db, login, password, base_location=None): - wsgienv = req.httprequest.environ + wsgienv = request.httprequest.environ env = dict( base_location=base_location, HTTP_HOST=wsgienv['HTTP_HOST'], REMOTE_ADDR=wsgienv['REMOTE_ADDR'], ) - req.session.authenticate(db, login, password, env) + request.session.authenticate(db, login, password, env) return self.session_info() - @openerpweb.jsonrequest + @http.jsonrequest def change_password (self, fields): old_password, new_password,confirm_password = operator.itemgetter('old_pwd', 'new_password','confirm_pwd')( dict(map(operator.itemgetter('name', 'value'), fields))) @@ -910,32 +910,32 @@ class Session(openerpweb.Controller): if new_password != confirm_password: return {'error': _('The new password and its confirmation must be identical.'),'title': _('Change Password')} try: - if req.session.model('res.users').change_password( + if request.session.model('res.users').change_password( old_password, new_password): return {'new_password':new_password} except Exception: return {'error': _('The old password you provided is incorrect, your password was not changed.'), 'title': _('Change Password')} return {'error': _('Error, password not changed !'), 'title': _('Change Password')} - @openerpweb.jsonrequest + @http.jsonrequest def sc_list(self): - return req.session.model('ir.ui.view_sc').get_sc( - req.session._uid, "ir.ui.menu", req.context) + return request.session.model('ir.ui.view_sc').get_sc( + request.session._uid, "ir.ui.menu", request.context) - @openerpweb.jsonrequest + @http.jsonrequest @nodb def get_lang_list(self): try: - return req.session.proxy("db").list_lang() or [] + return request.session.proxy("db").list_lang() or [] except Exception, e: return {"error": e, "title": _("Languages")} - @openerpweb.jsonrequest + @http.jsonrequest def modules(self): # return all installed modules. Web client is smart enough to not load a module twice return module_installed() - @openerpweb.jsonrequest + @http.jsonrequest def save_session_action(self, the_action): """ This method store an action object in the session object and returns an integer @@ -947,10 +947,10 @@ class Session(openerpweb.Controller): :return: A key identifying the saved action. :rtype: integer """ - saved_actions = req.httpsession.get('saved_actions') + saved_actions = request.httpsession.get('saved_actions') if not saved_actions: saved_actions = {"next":1, "actions":{}} - req.httpsession['saved_actions'] = saved_actions + request.httpsession['saved_actions'] = saved_actions # we don't allow more than 10 stored actions if len(saved_actions["actions"]) >= 10: del saved_actions["actions"][min(saved_actions["actions"])] @@ -959,7 +959,7 @@ class Session(openerpweb.Controller): saved_actions["next"] = key + 1 return key - @openerpweb.jsonrequest + @http.jsonrequest def get_session_action(self, key): """ Gets back a previously saved action. This method can return None if the action @@ -970,57 +970,57 @@ class Session(openerpweb.Controller): :return: The saved action or None. :rtype: anything """ - saved_actions = req.httpsession.get('saved_actions') + saved_actions = request.httpsession.get('saved_actions') if not saved_actions: return None return saved_actions["actions"].get(key) - @openerpweb.jsonrequest + @http.jsonrequest def check(self): - req.session.assert_valid() + request.session.assert_valid() return None - @openerpweb.jsonrequest + @http.jsonrequest def destroy(self): - req.session._suicide = True + request.session._suicide = True -class Menu(openerpweb.Controller): +class Menu(http.Controller): _cp_path = "/web/menu" - @openerpweb.jsonrequest + @http.jsonrequest def get_user_roots(self): """ Return all root menu ids visible for the session user. :return: the root menu ids :rtype: list(int) """ - s = req.session + s = request.session Menus = s.model('ir.ui.menu') # If a menu action is defined use its domain to get the root menu items user_menu_id = s.model('res.users').read([s._uid], ['menu_id'], - req.context)[0]['menu_id'] + request.context)[0]['menu_id'] menu_domain = [('parent_id', '=', False)] if user_menu_id: domain_string = s.model('ir.actions.act_window').read( - [user_menu_id[0]], ['domain'],req.context)[0]['domain'] + [user_menu_id[0]], ['domain'],request.context)[0]['domain'] if domain_string: menu_domain = ast.literal_eval(domain_string) - return Menus.search(menu_domain, 0, False, False, req.context) + return Menus.search(menu_domain, 0, False, False, request.context) - @openerpweb.jsonrequest + @http.jsonrequest def load(self): """ Loads all menu items (all applications and their sub-menus). :return: the menu root :rtype: dict('children': menu_nodes) """ - Menus = req.session.model('ir.ui.menu') + Menus = request.session.model('ir.ui.menu') fields = ['name', 'sequence', 'parent_id', 'action'] menu_root_ids = self.get_user_roots() - menu_roots = Menus.read(menu_root_ids, fields, req.context) if menu_root_ids else [] + menu_roots = Menus.read(menu_root_ids, fields, request.context) if menu_root_ids else [] menu_root = { 'id': False, 'name': 'root', @@ -1033,8 +1033,8 @@ class Menu(openerpweb.Controller): # menus are loaded fully unlike a regular tree view, cause there are a # limited number of items (752 when all 6.1 addons are installed) - menu_ids = Menus.search([('id', 'child_of', menu_root_ids)], 0, False, False, req.context) - menu_items = Menus.read(menu_ids, fields, req.context) + menu_ids = Menus.search([('id', 'child_of', menu_root_ids)], 0, False, False, request.context) + menu_items = Menus.read(menu_ids, fields, request.context) # adds roots at the end of the sequence, so that they will overwrite # equivalent menu items from full menu read when put into id:item # mapping, resulting in children being correctly set on the roots. @@ -1060,26 +1060,26 @@ class Menu(openerpweb.Controller): return menu_root - @openerpweb.jsonrequest + @http.jsonrequest def load_needaction(self, menu_ids): """ Loads needaction counters for specific menu ids. :return: needaction data :rtype: dict(menu_id: {'needaction_enabled': boolean, 'needaction_counter': int}) """ - return req.session.model('ir.ui.menu').get_needaction_data(menu_ids, req.context) + return request.session.model('ir.ui.menu').get_needaction_data(menu_ids, request.context) - @openerpweb.jsonrequest + @http.jsonrequest def action(self, menu_id): # still used by web_shortcut actions = load_actions_from_ir_values('action', 'tree_but_open', [('ir.ui.menu', menu_id)], False) return {"action": actions} -class DataSet(openerpweb.Controller): +class DataSet(http.Controller): _cp_path = "/web/dataset" - @openerpweb.jsonrequest + @http.jsonrequest def search_read(self, model, fields=False, offset=0, limit=False, domain=None, sort=None): return self.do_search_read(model, fields, offset, limit, domain, sort) def do_search_read(self, model, fields=False, offset=0, limit=False, domain=None @@ -1099,12 +1099,12 @@ class DataSet(openerpweb.Controller): matching fields selection set) :rtype: list """ - Model = req.session.model(model) + Model = request.session.model(model) ids = Model.search(domain, offset or 0, limit or False, sort or False, - req.context) + request.context) if limit and len(ids) == limit: - length = Model.search_count(domain, req.context) + length = Model.search_count(domain, request.context) else: length = len(ids) + (offset or 0) if fields and fields == ['id']: @@ -1114,18 +1114,18 @@ class DataSet(openerpweb.Controller): 'records': [{'id': id} for id in ids] } - records = Model.read(ids, fields or False, req.context) + records = Model.read(ids, fields or False, request.context) records.sort(key=lambda obj: ids.index(obj['id'])) return { 'length': length, 'records': records } - @openerpweb.jsonrequest + @http.jsonrequest def load(self, model, id, fields): - m = req.session.model(model) + m = request.session.model(model) value = {} - r = m.read([id], False, req.context) + r = m.read([id], False, request.context) if r: value = r[0] return {'value': value} @@ -1137,36 +1137,36 @@ class DataSet(openerpweb.Controller): # Temporary implements future display_name special field for model#read() if method == 'read' and kwargs.get('context', {}).get('future_display_name'): if 'display_name' in args[1]: - names = dict(req.session.model(model).name_get(args[0], **kwargs)) + names = dict(request.session.model(model).name_get(args[0], **kwargs)) args[1].remove('display_name') - records = req.session.model(model).read(*args, **kwargs) + records = request.session.model(model).read(*args, **kwargs) for record in records: record['display_name'] = \ names.get(record['id']) or "%s#%d" % (model, (record['id'])) return records - return getattr(req.session.model(model), method)(*args, **kwargs) + return getattr(request.session.model(model), method)(*args, **kwargs) - @openerpweb.jsonrequest + @http.jsonrequest def call(self, model, method, args, domain_id=None, context_id=None): return self._call_kw(model, method, args, {}) - @openerpweb.jsonrequest + @http.jsonrequest def call_kw(self, model, method, args, kwargs): return self._call_kw(model, method, args, kwargs) - @openerpweb.jsonrequest + @http.jsonrequest def call_button(self, model, method, args, domain_id=None, context_id=None): action = self._call_kw(model, method, args, {}) if isinstance(action, dict) and action.get('type') != '': return clean_action(action) return False - @openerpweb.jsonrequest + @http.jsonrequest def exec_workflow(self, model, id, signal): - return req.session.exec_workflow(model, id, signal) + return request.session.exec_workflow(model, id, signal) - @openerpweb.jsonrequest + @http.jsonrequest def resequence(self, model, ids, field='sequence', offset=0): """ Re-sequences a number of records in the model, by their ids @@ -1181,7 +1181,7 @@ class DataSet(openerpweb.Controller): starting the resequencing from an arbitrary number, defaults to ``0`` """ - m = req.session.model(model) + m = request.session.model(model) if not m.fields_get([field]): return False # python 2.6 has no start parameter @@ -1189,51 +1189,51 @@ class DataSet(openerpweb.Controller): m.write(id, { field: i + offset }) return True -class View(openerpweb.Controller): +class View(http.Controller): _cp_path = "/web/view" - @openerpweb.jsonrequest + @http.jsonrequest def add_custom(self, view_id, arch): - CustomView = req.session.model('ir.ui.view.custom') + CustomView = request.session.model('ir.ui.view.custom') CustomView.create({ - 'user_id': req.session._uid, + 'user_id': request.session._uid, 'ref_id': view_id, 'arch': arch - }, req.context) + }, request.context) return {'result': True} - @openerpweb.jsonrequest + @http.jsonrequest def undo_custom(self, view_id, reset=False): - CustomView = req.session.model('ir.ui.view.custom') - vcustom = CustomView.search([('user_id', '=', req.session._uid), ('ref_id' ,'=', view_id)], - 0, False, False, req.context) + CustomView = request.session.model('ir.ui.view.custom') + vcustom = CustomView.search([('user_id', '=', request.session._uid), ('ref_id' ,'=', view_id)], + 0, False, False, request.context) if vcustom: if reset: - CustomView.unlink(vcustom, req.context) + CustomView.unlink(vcustom, request.context) else: - CustomView.unlink([vcustom[0]], req.context) + CustomView.unlink([vcustom[0]], request.context) return {'result': True} return {'result': False} class TreeView(View): _cp_path = "/web/treeview" - @openerpweb.jsonrequest + @http.jsonrequest def action(self, model, id): return load_actions_from_ir_values( 'action', 'tree_but_open',[(model, id)], False) -class Binary(openerpweb.Controller): +class Binary(http.Controller): _cp_path = "/web/binary" - @openerpweb.httprequest + @http.httprequest def image(self, model, id, field, **kw): last_update = '__last_update' - Model = req.session.model(model) + Model = request.session.model(model) headers = [('Content-Type', 'image/png')] - etag = req.httprequest.headers.get('If-None-Match') - hashed_session = hashlib.md5(req.session_id).hexdigest() + etag = request.httprequest.headers.get('If-None-Match') + hashed_session = hashlib.md5(request.session_id).hexdigest() retag = hashed_session id = None if not id else simplejson.loads(id) if type(id) is list: @@ -1243,15 +1243,15 @@ class Binary(openerpweb.Controller): if not id and hashed_session == etag: return werkzeug.wrappers.Response(status=304) else: - date = Model.read([id], [last_update], req.context)[0].get(last_update) + date = Model.read([id], [last_update], request.context)[0].get(last_update) if hashlib.md5(date).hexdigest() == etag: return werkzeug.wrappers.Response(status=304) if not id: - res = Model.default_get([field], req.context).get(field) + res = Model.default_get([field], request.context).get(field) image_base64 = res else: - res = Model.read([id], [last_update, field], req.context)[0] + res = Model.read([id], [last_update, field], request.context)[0] retag = hashlib.md5(res.get(last_update)).hexdigest() image_base64 = res.get(field) @@ -1276,13 +1276,13 @@ class Binary(openerpweb.Controller): headers.append(('Cache-Control', 'no-cache' if ncache == 0 else 'max-age=%s' % (ncache))) except: pass - return req.make_response(image_data, headers) + return request.make_response(image_data, headers) def placeholder(self, image='placeholder.png'): - addons_path = openerpweb.addons_manifest['web']['addons_path'] + addons_path = http.addons_manifest['web']['addons_path'] return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read() - @openerpweb.httprequest + @http.httprequest def saveas(self, model, field, id=None, filename_field=None, **kw): """ Download link for files stored as binary fields. @@ -1296,26 +1296,26 @@ class Binary(openerpweb.Controller): :param str filename_field: field holding the file's name, if any :returns: :class:`werkzeug.wrappers.Response` """ - Model = req.session.model(model) + Model = request.session.model(model) fields = [field] if filename_field: fields.append(filename_field) if id: - res = Model.read([int(id)], fields, req.context)[0] + res = Model.read([int(id)], fields, request.context)[0] else: - res = Model.default_get(fields, req.context) + res = Model.default_get(fields, request.context) filecontent = base64.b64decode(res.get(field, '')) if not filecontent: - return req.not_found() + return request.not_found() else: filename = '%s_%s' % (model.replace('.', '_'), id) if filename_field: filename = res.get(filename_field, '') or filename - return req.make_response(filecontent, + return request.make_response(filecontent, [('Content-Type', 'application/octet-stream'), ('Content-Disposition', content_disposition(filename))]) - @openerpweb.httprequest + @http.httprequest def saveas_ajax(self, data, token): jdata = simplejson.loads(data) model = jdata['model'] @@ -1325,7 +1325,7 @@ class Binary(openerpweb.Controller): filename_field = jdata.get('filename_field', None) context = jdata.get('context', {}) - Model = req.session.model(model) + Model = request.session.model(model) fields = [field] if filename_field: fields.append(filename_field) @@ -1343,12 +1343,12 @@ class Binary(openerpweb.Controller): filename = '%s_%s' % (model.replace('.', '_'), id) if filename_field: filename = res.get(filename_field, '') or filename - return req.make_response(filecontent, + return request.make_response(filecontent, headers=[('Content-Type', 'application/octet-stream'), ('Content-Disposition', content_disposition(filename))], cookies={'fileToken': int(token)}) - @openerpweb.httprequest + @http.httprequest def upload(self, callback, ufile): # TODO: might be useful to have a configuration flag for max-length file uploads out = """