[MERGE] forward port of branch 7.0 up to revid 4162 launchpad_translations_on_behalf_...
[odoo/odoo.git] / addons / web / controllers / main.py
index d55e7ad..40c9494 100644 (file)
@@ -35,7 +35,8 @@ from openerp.tools.translate import _
 from openerp.tools import config
 
 from .. import http
-openerpweb = http
+
+from openerp.addons.web.http import request
 
 #----------------------------------------------------------
 # OpenERP Web helpers
@@ -85,50 +86,11 @@ def rjsmin(script):
     ).strip()
     return result
 
-def db_list(req, force=False):
-    proxy = req.session.proxy("db")
-    dbs = proxy.list(force)
-    h = req.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)]
-    return dbs
-
-def db_monodb_redirect(req):
-    return db_redirect(req, not config['list_db'])
-
-def db_redirect(req, match_first_only_if_unique):
-    db = False
-    redirect = False
-
-    # 1 try the db in the url
-    db_url = req.params.get('db')
-    if db_url:
-        return (db_url, False)
-
-    dbs = db_list(req, True)
-
-    # 2 use the database from the cookie if it's listable and still listed
-    cookie_db = req.httprequest.cookies.get('last_used_database')
-    if cookie_db in dbs:
-        db = cookie_db
-
-    # 3 use the first db if user can list databases
-    if dbs and not db and (not match_first_only_if_unique or len(dbs) == 1):
-        db = dbs[0]
-
-    # 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.update({'db': db})
-        redirect = req.httprequest.path + '?' + urllib.urlencode(query)
-    return (db, redirect)
-
-def db_monodb(req):
-    # if only one db exists, return it else return False
-    return db_redirect(req, True)[0]
-
-def redirect_with_hash(req, url, code=303):
+db_list = http.db_list
+
+db_monodb = http.db_monodb
+
+def redirect_with_hash(url, code=303):
     # Most IE and Safari versions decided not to preserve location.hash upon
     # redirect. And even if IE10 pretends to support it, it still fails
     # inexplicably in case of multiple redirects (and we do have some).
@@ -179,20 +141,20 @@ def module_topological_sort(modules):
         visit(n)
     return L
 
-def module_installed(req):
+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
 
@@ -200,7 +162,7 @@ def module_installed(req):
     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)
@@ -221,14 +183,14 @@ def module_installed_bypass_session(dbname):
     sorted_modules = module_topological_sort(modules)
     return sorted_modules
 
-def module_boot(req, db=None):
+def module_boot(db=None):
     server_wide_modules = openerp.conf.server_wide_modules or ['web']
     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(req)
+    monodb = db or db_monodb()
     if monodb:
         dbside = module_installed_bypass_session(monodb)
         dbside = [i for i in dbside if i not in serverside]
@@ -306,37 +268,43 @@ def fs2web(path):
     """convert FS path into web path"""
     return '/'.join(path.split(os.path.sep))
 
-def manifest_glob(req, extension, addons=None, db=None):
+def manifest_glob(extension, addons=None, db=None, include_remotes=False):
     if addons is None:
-        addons = module_boot(req, db=db)
+        addons = module_boot(db=db)
     else:
         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 /
         addons_path = os.path.join(manifest['addons_path'], '')[:-1]
         globlist = manifest.get(extension, [])
         for pattern in globlist:
-            for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
-                r.append((path, fs2web(path[len(addons_path):])))
+            if pattern.startswith(('http://', 'https://', '//')):
+                if include_remotes:
+                    r.append((None, pattern))
+            else:
+                for path in glob.glob(os.path.normpath(os.path.join(addons_path, addon, pattern))):
+                    r.append((path, fs2web(path[len(addons_path):])))
     return r
 
-def manifest_list(req, extension, mods=None, db=None):
+def manifest_list(extension, mods=None, db=None, debug=False):
     """ list ressources to load specifying either:
     mods: a comma separated string listing modules
     db: a database name (return all installed modules in that database)
     """
-    if not req.debug:
+    files = manifest_glob(extension, addons=mods, db=db, include_remotes=True)
+    if not debug:
         path = '/web/webclient/' + extension
         if mods is not None:
             path += '?' + urllib.urlencode({'mods': mods})
         elif db:
             path += '?' + urllib.urlencode({'db': db})
-        return [path]
-    files = manifest_glob(req, extension, addons=mods, db=db)
+
+        remotes = [wp for fp, wp in files if fp is None]
+        return [path] + remotes
     return [wp for _fp, wp in files]
 
 def get_last_modified(files):
@@ -353,15 +321,13 @@ def get_last_modified(files):
                    for f in files)
     return datetime.datetime(1970, 1, 1)
 
-def make_conditional(req, response, last_modified=None, etag=None):
+def make_conditional(response, last_modified=None, etag=None):
     """ Makes the provided response conditional based upon the request,
     and mandates revalidation from clients
 
     Uses Werkzeug's own :meth:`ETagResponseMixin.make_conditional`, after
     setting ``last_modified`` and ``etag`` correctly on the response object
 
-    :param req: OpenERP request
-    :type req: web.common.http.WebRequest
     :param response: Werkzeug response
     :type response: werkzeug.wrappers.Response
     :param datetime.datetime last_modified: last modification date of the response content
@@ -375,33 +341,25 @@ def make_conditional(req, response, last_modified=None, etag=None):
         response.last_modified = last_modified
     if etag:
         response.set_etag(etag)
-    return response.make_conditional(req.httprequest)
-
-def login_and_redirect(req, db, login, key, redirect_url='/'):
-    wsgienv = req.httprequest.environ
-    env = dict(
-        base_location=req.httprequest.url_root.rstrip('/'),
-        HTTP_HOST=wsgienv['HTTP_HOST'],
-        REMOTE_ADDR=wsgienv['REMOTE_ADDR'],
-    )
-    req.session.authenticate(db, login, key, env)
-    return set_cookie_and_redirect(req, redirect_url)
-
-def set_cookie_and_redirect(req, redirect_url):
+    return response.make_conditional(request.httprequest)
+
+def login_and_redirect(db, login, key, redirect_url='/'):
+    request.session.authenticate(db, login, key)
+    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))
-    redirect.set_cookie('instance0|session_id', cookie_val)
     return redirect
 
-def load_actions_from_ir_values(req, key, key2, models, meta):
-    Values = req.session.model('ir.values')
-    actions = Values.get(key, key2, models, meta, req.context)
+def load_actions_from_ir_values(key, key2, models, meta):
+    Values = request.session.model('ir.values')
+    actions = Values.get(key, key2, models, meta, request.context)
 
-    return [(id, name, clean_action(req, action))
+    return [(id, name, clean_action(action))
             for id, name, action in actions]
 
-def clean_action(req, action):
+def clean_action(action):
     action.setdefault('flags', {})
     action_type = action.setdefault('type', 'ir.actions.act_window_close')
     if action_type == 'ir.actions.act_window':
@@ -519,11 +477,11 @@ def xml2json_from_elementtree(el, preserve_whitespaces=False):
     res["children"] = kids
     return res
 
-def content_disposition(filename, req):
+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':
@@ -562,50 +520,67 @@ html_template = """<!DOCTYPE html>
 </html>
 """
 
-class Home(openerpweb.Controller):
-    _cp_path = '/'
+class Home(http.Controller):
+
+    @http.route('/', type='http', auth="none")
+    def index(self, s_action=None, db=None, debug=False, **kw):
+        debug = debug != False
+
+        lst = http.db_list(True)
+        if db not in lst:
+            db = None
+        guessed_db = http.db_monodb(request.httprequest)
+        if guessed_db is None and len(lst) > 0:
+            guessed_db = lst[0]
 
-    @openerpweb.httprequest
-    def index(self, req, s_action=None, db=None, **kw):
-        db, redir = db_monodb_redirect(req)
-        if redir:
-            return redirect_with_hash(req, redir)
+        def redirect(db):
+            query = dict(urlparse.parse_qsl(request.httprequest.query_string, keep_blank_values=True))
+            query.update({'db': db})
+            redirect = request.httprequest.path + '?' + urllib.urlencode(query)
+            return redirect_with_hash(redirect)
 
-        js = "\n        ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list(req, 'js', db=db))
-        css = "\n        ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list(req, 'css', db=db))
+        if db is None and guessed_db is not None:
+            return redirect(guessed_db)
+
+        if db is not None and db != guessed_db:
+            request.session.logout()
+            request.session.db = db
+            guessed_db = db
+
+        js = "\n        ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js', db=guessed_db, debug=debug))
+        css = "\n        ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css', db=guessed_db, debug=debug))
 
         r = html_template % {
             'js': js,
             'css': css,
-            'modules': simplejson.dumps(module_boot(req, db=db)),
+            'modules': simplejson.dumps(module_boot(db=guessed_db)),
             'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));'
         }
-        return r
+        return request.make_response(r, {'Cache-Control': 'no-cache', 'Content-Type': 'text/html; charset=utf-8'})
 
-    @openerpweb.httprequest
-    def login(self, req, db, login, key):
-        return login_and_redirect(req, db, login, key)
+    @http.route('/login', type='http', auth="none")
+    def login(self, db, login, key):
+        return login_and_redirect(db, login, key)
 
-class WebClient(openerpweb.Controller):
-    _cp_path = "/web/webclient"
+class WebClient(http.Controller):
 
-    @openerpweb.jsonrequest
-    def csslist(self, req, mods=None):
-        return manifest_list(req, 'css', mods=mods)
+    @http.route('/web/webclient/csslist', type='json', auth="none")
+    def csslist(self, mods=None):
+        return manifest_list('css', mods=mods)
 
-    @openerpweb.jsonrequest
-    def jslist(self, req, mods=None):
-        return manifest_list(req, 'js', mods=mods)
+    @http.route('/web/webclient/jslist', type='json', auth="none")
+    def jslist(self, mods=None):
+        return manifest_list('js', mods=mods)
 
-    @openerpweb.jsonrequest
-    def qweblist(self, req, mods=None):
-        return manifest_list(req, 'qweb', mods=mods)
+    @http.route('/web/webclient/qweblist', type='json', auth="none")
+    def qweblist(self, mods=None):
+        return manifest_list('qweb', mods=mods)
 
-    @openerpweb.httprequest
-    def css(self, req, mods=None, db=None):
-        files = list(manifest_glob(req, 'css', addons=mods, db=db))
+    @http.route('/web/webclient/css', type='http', auth="none")
+    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)
@@ -629,7 +604,7 @@ class WebClient(openerpweb.Controller):
 
             data = re.sub(
                 rx_url,
-                r"""url(\1%s/""" % (web_dir,),
+                r"url(\1%s/" % (web_dir,),
                 data,
             )
             return data.encode('utf-8')
@@ -649,37 +624,37 @@ class WebClient(openerpweb.Controller):
         content = '\n'.join(matches)
 
         return make_conditional(
-            req, req.make_response(content, [('Content-Type', 'text/css')]),
+            request.make_response(content, [('Content-Type', 'text/css')]),
             last_modified, checksum)
 
-    @openerpweb.httprequest
-    def js(self, req, mods=None, db=None):
-        files = [f[0] for f in manifest_glob(req, 'js', addons=mods, db=db)]
+    @http.route('/web/webclient/js', type='http', auth="none")
+    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, req.make_response(content, [('Content-Type', 'application/javascript')]),
+            request.make_response(content, [('Content-Type', 'application/javascript')]),
             last_modified, checksum)
 
-    @openerpweb.httprequest
-    def qweb(self, req, mods=None, db=None):
-        files = [f[0] for f in manifest_glob(req, 'qweb', addons=mods, db=db)]
+    @http.route('/web/webclient/qweb', type='http', auth="none")
+    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, req.make_response(content, [('Content-Type', 'text/xml')]),
+            request.make_response(content, [('Content-Type', 'text/xml')]),
             last_modified, checksum)
 
-    @openerpweb.jsonrequest
-    def bootstrap_translations(self, req, mods):
+    @http.route('/web/webclient/bootstrap_translations', type='json', auth="none")
+    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
             for translating the login page and db management chrome, using
@@ -687,12 +662,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
@@ -701,20 +676,26 @@ class WebClient(openerpweb.Controller):
         return {"modules": translations_per_module,
                 "lang_parameters": None}
 
-    @openerpweb.jsonrequest
-    def translations(self, req, mods, lang):
-        res_lang = req.session.model('res.lang')
-        ids = res_lang.search([("code", "=", lang)])
+    @http.route('/web/webclient/translations', type='json', auth="admin")
+    def translations(self, mods=None, lang=None):
+        if mods is None:
+            m = request.registry.get('ir.module.module')
+            mods = [x['name'] for x in m.search_read(request.cr, request.uid,
+                [('state','=','installed')], ['name'])]
+        if lang is None:
+            lang = request.context["lang"]
+        res_lang = request.registry.get('res.lang')
+        ids = res_lang.search(request.cr, request.uid, [("code", "=", lang)])
         lang_params = None
         if ids:
-            lang_params = res_lang.read(ids[0], ["direction", "date_format", "time_format",
+            lang_params = res_lang.read(request.cr, request.uid, ids[0], ["direction", "date_format", "time_format",
                                                 "grouping", "decimal_point", "thousands_sep"])
 
         # 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.registry.get('ir.translation')
         translations_per_module = {}
-        messages = ir_translation.search_read([('module','in',mods),('lang','=',lang),
+        messages = ir_translation.search_read(request.cr, request.uid, [('module','in',mods),('lang','=',lang),
                                                ('comments','like','openerp-web'),('value','!=',False),
                                                ('value','!=','')],
                                               ['module','src','value','lang'], order='module')
@@ -726,147 +707,142 @@ class WebClient(openerpweb.Controller):
         return {"modules": translations_per_module,
                 "lang_parameters": lang_params}
 
-    @openerpweb.jsonrequest
-    def version_info(self, req):
-        return openerp.service.web_services.RPC_VERSION_1
+    @http.route('/web/webclient/version_info', type='json', auth="none")
+    def version_info(self):
+        return openerp.service.common.exp_version()
 
-class Proxy(openerpweb.Controller):
-    _cp_path = '/web/proxy'
+class Proxy(http.Controller):
 
-    @openerpweb.jsonrequest
-    def load(self, req, path):
+    @http.route('/web/proxy/load', type='json', auth="none")
+    def load(self, path):
         """ Proxies an HTTP request through a JSON request.
 
         It is strongly recommended to not request binary files through this,
         as the result will be a binary data blob as well.
 
-        :param req: OpenERP request
         :param path: actual request path
         :return: file content
         """
         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):
-    _cp_path = "/web/database"
+class Database(http.Controller):
 
-    @openerpweb.jsonrequest
-    def get_list(self, req):
+    @http.route('/web/database/get_list', type='json', auth="none")
+    def get_list(self):
         # TODO change js to avoid calling this method if in monodb mode
         try:
-            return db_list(req)
-        except xmlrpclib.Fault:
-            monodb = db_monodb(req)
+            return http.db_list()
+        except openerp.exceptions.AccessDenied:
+            monodb = db_monodb()
             if monodb:
                 return [monodb]
             raise
 
-    @openerpweb.jsonrequest
-    def create(self, req, fields):
+    @http.route('/web/database/create', type='json', auth="none")
+    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
-    def duplicate(self, req, fields):
+    @http.route('/web/database/duplicate', type='json', auth="none")
+    def duplicate(self, fields):
         params = dict(map(operator.itemgetter('name', 'value'), fields))
         duplicate_attrs = (
             params['super_admin_pwd'],
             params['db_original_name'],
             params['db_name'],
         )
-        return req.session.proxy("db").duplicate_database(*duplicate_attrs)
 
-    @openerpweb.jsonrequest
-    def drop(self, req, fields):
+        return request.session.proxy("db").duplicate_database(*duplicate_attrs)
+
+    @http.route('/web/database/drop', type='json', auth="none")
+    def drop(self, fields):
         password, db = operator.itemgetter(
             'drop_pwd', 'drop_db')(
                 dict(map(operator.itemgetter('name', 'value'), fields)))
         
         try:
-            if req.session.proxy("db").drop(password, db):return True
-        except xmlrpclib.Fault, e:
-            if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
-                return {'error': e.faultCode, 'title': 'Drop Database'}
-        return {'error': _('Could not drop database !'), 'title': _('Drop Database')}
-
-    @openerpweb.httprequest
-    def backup(self, req, backup_db, backup_pwd, token):
+            if request.session.proxy("db").drop(password, db):
+                return True
+            else:
+                return False
+        except openerp.exceptions.AccessDenied:
+            return {'error': 'AccessDenied', 'title': 'Drop Database'}
+        except Exception:
+            return {'error': _('Could not drop database !'), 'title': _('Drop Database')}
+
+    @http.route('/web/database/backup', type='http', auth="none")
+    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, req))],
+               ('Content-Disposition', content_disposition(filename))],
                {'fileToken': token}
             )
-        except xmlrpclib.Fault, e:
-            return simplejson.dumps([[],[{'error': e.faultCode, 'title': _('Backup Database')}]])
+        except Exception, e:
+            return simplejson.dumps([[],[{'error': openerp.tools.ustr(e), 'title': _('Backup Database')}]])
 
-    @openerpweb.httprequest
-    def restore(self, req, db_file, restore_pwd, new_db):
+    @http.route('/web/database/restore', type='http', auth="none")
+    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 xmlrpclib.Fault, e:
-            if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
-                raise Exception("AccessDenied")
+        except openerp.exceptions.AccessDenied, e:
+            raise Exception("AccessDenied")
 
-    @openerpweb.jsonrequest
-    def change_password(self, req, fields):
+    @http.route('/web/database/change_password', type='json', auth="none")
+    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)
-        except xmlrpclib.Fault, e:
-            if e.faultCode and e.faultCode.split(':')[0] == 'AccessDenied':
-                return {'error': e.faultCode, 'title': _('Change Password')}
-        return {'error': _('Error, password not changed !'), 'title': _('Change 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):
-    _cp_path = "/web/session"
+class Session(http.Controller):
 
-    def session_info(self, req):
-        req.session.ensure_valid()
+    def session_info(self):
+        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
-    def get_session_info(self, req):
-        return self.session_info(req)
-
-    @openerpweb.jsonrequest
-    def authenticate(self, req, db, login, password, base_location=None):
-        wsgienv = req.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)
+    @http.route('/web/session/get_session_info', type='json', auth="none")
+    def get_session_info(self):
+        request.uid = request.session.uid
+        request.disable_db = False
+        return self.session_info()
+
+    @http.route('/web/session/authenticate', type='json', auth="none")
+    def authenticate(self, db, login, password, base_location=None):
+        request.session.authenticate(db, login, password)
 
-        return self.session_info(req)
+        return self.session_info()
 
-    @openerpweb.jsonrequest
-    def change_password (self,req,fields):
+    @http.route('/web/session/change_password', type='json', auth="user")
+    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)))
         if not (old_password.strip() and new_password.strip() and confirm_password.strip()):
@@ -874,32 +850,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
-    def sc_list(self, req):
-        return req.session.model('ir.ui.view_sc').get_sc(
-            req.session._uid, "ir.ui.menu", req.context)
+    @http.route('/web/session/sc_list', type='json', auth="user")
+    def sc_list(self):
+        return request.session.model('ir.ui.view_sc').get_sc(
+            request.session.uid, "ir.ui.menu", request.context)
 
-    @openerpweb.jsonrequest
-    def get_lang_list(self, req):
+    @http.route('/web/session/get_lang_list', type='json', auth="none")
+    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
-    def modules(self, req):
+    @http.route('/web/session/modules', type='json', auth="user")
+    def modules(self):
         # return all installed modules. Web client is smart enough to not load a module twice
-        return module_installed(req)
+        return module_installed()
 
-    @openerpweb.jsonrequest
-    def save_session_action(self, req, the_action):
+    @http.route('/web/session/save_session_action', type='json', auth="user")
+    def save_session_action(self, the_action):
         """
         This method store an action object in the session object and returns an integer
         identifying that action. The method get_session_action() can be used to get
@@ -910,10 +886,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"])]
@@ -922,8 +898,8 @@ class Session(openerpweb.Controller):
         saved_actions["next"] = key + 1
         return key
 
-    @openerpweb.jsonrequest
-    def get_session_action(self, req, key):
+    @http.route('/web/session/get_session_action', type='json', auth="user")
+    def get_session_action(self, key):
         """
         Gets back a previously saved action. This method can return None if the action
         was saved since too much time (this case should be handled in a smart way).
@@ -933,61 +909,56 @@ 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
-    def check(self, req):
-        req.session.assert_valid()
+    @http.route('/web/session/check', type='json', auth="user")
+    def check(self):
+        request.session.assert_valid()
         return None
 
-    @openerpweb.jsonrequest
-    def destroy(self, req):
-        req.session._suicide = True
+    @http.route('/web/session/destroy', type='json', auth="user")
+    def destroy(self):
+        request.session.logout()
 
-class Menu(openerpweb.Controller):
-    _cp_path = "/web/menu"
+class Menu(http.Controller):
 
-    @openerpweb.jsonrequest
-    def get_user_roots(self, req):
+    @http.route('/web/menu/get_user_roots', type='json', auth="user")
+    def get_user_roots(self):
         """ Return all root menu ids visible for the session user.
 
-        :param req: A request object, with an OpenERP session attribute
-        :type req: < session -> OpenERPSession >
         :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']
+        user_menu_id = s.model('res.users').read([s.uid], ['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
-    def load(self, req):
+    @http.route('/web/menu/load', type='json', auth="user")
+    def load(self):
         """ Loads all menu items (all applications and their sub-menus).
 
-        :param req: A request object, with an OpenERP session attribute
-        :type req: < session -> OpenERPSession >
         :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(req)
-        menu_roots = Menus.read(menu_root_ids, fields, req.context) if menu_root_ids else []
+        menu_root_ids = self.get_user_roots()
+        menu_roots = Menus.read(menu_root_ids, fields, request.context) if menu_root_ids else []
         menu_root = {
             'id': False,
             'name': 'root',
@@ -1000,8 +971,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.
@@ -1027,35 +998,32 @@ class Menu(openerpweb.Controller):
 
         return menu_root
 
-    @openerpweb.jsonrequest
-    def load_needaction(self, req, menu_ids):
+    @http.route('/web/menu/load_needaction', type='json', auth="user")
+    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
-    def action(self, req, menu_id):
+    @http.route('/web/menu/action', type='json', auth="user")
+    def action(self, menu_id):
         # still used by web_shortcut
-        actions = load_actions_from_ir_values(req,'action', 'tree_but_open',
+        actions = load_actions_from_ir_values('action', 'tree_but_open',
                                              [('ir.ui.menu', menu_id)], False)
         return {"action": actions}
 
-class DataSet(openerpweb.Controller):
-    _cp_path = "/web/dataset"
+class DataSet(http.Controller):
 
-    @openerpweb.jsonrequest
-    def search_read(self, req, model, fields=False, offset=0, limit=False, domain=None, sort=None):
-        return self.do_search_read(req, model, fields, offset, limit, domain, sort)
-    def do_search_read(self, req, model, fields=False, offset=0, limit=False, domain=None
+    @http.route('/web/dataset/search_read', type='json', auth="user")
+    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
                        , sort=None):
         """ Performs a search() followed by a read() (if needed) using the
         provided search criteria
 
-        :param req: a JSON-RPC request object
-        :type req: openerpweb.JsonRequest
         :param str model: the name of the model to search on
         :param fields: a list of the fields to return in the result records
         :type fields: [str]
@@ -1068,12 +1036,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']:
@@ -1083,7 +1051,7 @@ 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)
 
         index = dict((r['id'], r) for r in records)
         records = [index[x] for x in ids if x in index]
@@ -1093,53 +1061,56 @@ class DataSet(openerpweb.Controller):
             'records': records
         }
 
-    @openerpweb.jsonrequest
-    def load(self, req, model, id, fields):
-        m = req.session.model(model)
+    @http.route('/web/dataset/load', type='json', auth="user")
+    def load(self, model, id, fields):
+        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}
 
-    def call_common(self, req, model, method, args, domain_id=None, context_id=None):
-        return self._call_kw(req, model, method, args, {})
+    def call_common(self, model, method, args, domain_id=None, context_id=None):
+        return self._call_kw(model, method, args, {})
 
-    def _call_kw(self, req, model, method, args, kwargs):
+    def _call_kw(self, model, method, args, kwargs):
         # 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)
+        if method.startswith('_'):
+            raise Exception("Access denied")
 
-    @openerpweb.jsonrequest
-    def call(self, req, model, method, args, domain_id=None, context_id=None):
-        return self._call_kw(req, model, method, args, {})
+        return getattr(request.registry.get(model), method)(request.cr, request.uid, *args, **kwargs)
 
-    @openerpweb.jsonrequest
-    def call_kw(self, req, model, method, args, kwargs):
-        return self._call_kw(req, model, method, args, kwargs)
+    @http.route('/web/dataset/call', type='json', auth="user")
+    def call(self, model, method, args, domain_id=None, context_id=None):
+        return self._call_kw(model, method, args, {})
 
-    @openerpweb.jsonrequest
-    def call_button(self, req, model, method, args, domain_id=None, context_id=None):
-        action = self._call_kw(req, model, method, args, {})
+    @http.route(['/web/dataset/call_kw', '/web/dataset/call_kw/<path:path>'], type='json', auth="user")
+    def call_kw(self, model, method, args, kwargs, path=None):
+        return self._call_kw(model, method, args, kwargs)
+
+    @http.route('/web/dataset/call_button', type='json', auth="user")
+    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(req, action)
+            return clean_action(action)
         return False
 
-    @openerpweb.jsonrequest
-    def exec_workflow(self, req, model, id, signal):
-        return req.session.exec_workflow(model, id, signal)
+    @http.route('/web/dataset/exec_workflow', type='json', auth="user")
+    def exec_workflow(self, model, id, signal):
+        return request.session.exec_workflow(model, id, signal)
 
-    @openerpweb.jsonrequest
-    def resequence(self, req, model, ids, field='sequence', offset=0):
+    @http.route('/web/dataset/resequence', type='json', auth="user")
+    def resequence(self, model, ids, field='sequence', offset=0):
         """ Re-sequences a number of records in the model, by their ids
 
         The re-sequencing starts at the first model of ``ids``, the sequence
@@ -1153,7 +1124,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
@@ -1161,69 +1132,66 @@ class DataSet(openerpweb.Controller):
             m.write(id, { field: i + offset })
         return True
 
-class View(openerpweb.Controller):
-    _cp_path = "/web/view"
+class View(http.Controller):
 
-    @openerpweb.jsonrequest
-    def add_custom(self, req, view_id, arch):
-        CustomView = req.session.model('ir.ui.view.custom')
+    @http.route('/web/view/add_custom', type='json', auth="user")
+    def add_custom(self, view_id, arch):
+        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
-    def undo_custom(self, req, 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)
+    @http.route('/web/view/undo_custom', type='json', auth="user")
+    def undo_custom(self, view_id, reset=False):
+        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
-    def action(self, req, model, id):
+    @http.route('/web/treeview/action', type='json', auth="user")
+    def action(self, model, id):
         return load_actions_from_ir_values(
-            req,'action', 'tree_but_open',[(model, id)],
+            'action', 'tree_but_open',[(model, id)],
             False)
 
-class Binary(openerpweb.Controller):
-    _cp_path = "/web/binary"
+class Binary(http.Controller):
 
-    @openerpweb.httprequest
-    def image(self, req, model, id, field, **kw):
+    @http.route('/web/binary/image', type='http', auth="user")
+    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:
             id = id[0] # m2o
-        if etag:
-            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)
-                if hashlib.md5(date).hexdigest() == etag:
+        try:
+            if etag:
+                if not id and hashed_session == etag:
                     return werkzeug.wrappers.Response(status=304)
+                else:
+                    date = Model.read([id], [last_update], request.context)[0].get(last_update)
+                    if hashlib.md5(date).hexdigest() == etag:
+                        return werkzeug.wrappers.Response(status=304)
 
-        retag = hashed_session
-        try:
             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)
 
@@ -1239,8 +1207,8 @@ class Binary(openerpweb.Controller):
 
             image_data = base64.b64decode(image_base64)
 
-        except (TypeError, xmlrpclib.Fault):
-            image_data = self.placeholder(req)
+        except Exception:
+            image_data = self.placeholder()
         headers.append(('ETag', retag))
         headers.append(('Content-Length', len(image_data)))
         try:
@@ -1248,49 +1216,47 @@ 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, req, image='placeholder.png'):
-        addons_path = openerpweb.addons_manifest['web']['addons_path']
+    def placeholder(self, image='placeholder.png'):
+        addons_path = http.addons_manifest['web']['addons_path']
         return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', image), 'rb').read()
 
-    @openerpweb.httprequest
-    def saveas(self, req, model, field, id=None, filename_field=None, **kw):
+    @http.route('/web/binary/saveas', type='http', auth="user")
+    def saveas(self, model, field, id=None, filename_field=None, **kw):
         """ Download link for files stored as binary fields.
 
         If the ``id`` parameter is omitted, fetches the default value for the
         binary field (via ``default_get``), otherwise fetches the field for
         that precise record.
 
-        :param req: OpenERP request
-        :type req: :class:`web.common.http.HttpRequest`
         :param str model: name of the model to fetch the binary from
         :param str field: binary field
         :param str id: id of the record from which to fetch the binary
         :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, req))])
+                 ('Content-Disposition', content_disposition(filename))])
 
-    @openerpweb.httprequest
-    def saveas_ajax(self, req, data, token):
+    @http.route('/web/binary/saveas_ajax', type='http', auth="user")
+    def saveas_ajax(self, data, token):
         jdata = simplejson.loads(data)
         model = jdata['model']
         field = jdata['field']
@@ -1299,7 +1265,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)
@@ -1317,13 +1283,13 @@ 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, req))],
+                        ('Content-Disposition', content_disposition(filename))],
                 cookies={'fileToken': token})
 
-    @openerpweb.httprequest
-    def upload(self, req, callback, ufile):
+    @http.route('/web/binary/upload', type='http', auth="user")
+    def upload(self, callback, ufile):
         # TODO: might be useful to have a configuration flag for max-length file uploads
         out = """<script language="javascript" type="text/javascript">
                     var win = window.top.window;
@@ -1337,9 +1303,9 @@ class Binary(openerpweb.Controller):
             args = [False, e.message]
         return out % (simplejson.dumps(callback), simplejson.dumps(args))
 
-    @openerpweb.httprequest
-    def upload_attachment(self, req, callback, model, id, ufile):
-        Model = req.session.model('ir.attachment')
+    @http.route('/web/binary/upload_attachment', type='http', auth="user")
+    def upload_attachment(self, callback, model, id, ufile):
+        Model = request.session.model('ir.attachment')
         out = """<script language="javascript" type="text/javascript">
                     var win = window.top.window;
                     win.jQuery(win).trigger(%s, %s);
@@ -1351,30 +1317,30 @@ class Binary(openerpweb.Controller):
                 'datas_fname': ufile.filename,
                 'res_model': model,
                 'res_id': int(id)
-            }, req.context)
+            }, request.context)
             args = {
                 'filename': ufile.filename,
                 'id':  attachment_id
             }
-        except xmlrpclib.Fault, e:
-            args = {'error':e.faultCode }
+        except Exception:
+            args = {'error': "Something horrible happened"}
         return out % (simplejson.dumps(callback), simplejson.dumps(args))
 
-    @openerpweb.httprequest
-    def company_logo(self, req, dbname=None):
+    @http.route('/web/binary/company_logo', type='http', auth="none")
+    def company_logo(self, dbname=None):
         # TODO add etag, refactor to use /image code for etag
         uid = None
-        if req.session._db:
-            dbname = req.session._db
-            uid = req.session._uid
+        if request.session.db:
+            dbname = request.session.db
+            uid = request.session.uid
         elif dbname is None:
-            dbname = db_monodb(req)
+            dbname = db_monodb()
 
         if not uid:
             uid = openerp.SUPERUSER_ID
 
         if not dbname:
-            image_data = self.placeholder(req, 'logo.png')
+            image_data = self.placeholder('logo.png')
         else:
             try:
                 # create an empty registry
@@ -1390,85 +1356,81 @@ class Binary(openerpweb.Controller):
                     if row and row[0]:
                         image_data = str(row[0]).decode('base64')
                     else:
-                        image_data = self.placeholder(req, 'nologo.png')
+                        image_data = self.placeholder('nologo.png')
             except Exception:
-                image_data = self.placeholder(req, 'logo.png')
+                image_data = self.placeholder('logo.png')
 
         headers = [
             ('Content-Type', 'image/png'),
             ('Content-Length', len(image_data)),
         ]
-        return req.make_response(image_data, headers)
+        return request.make_response(image_data, headers)
 
-class Action(openerpweb.Controller):
-    _cp_path = "/web/action"
+class Action(http.Controller):
 
-    @openerpweb.jsonrequest
-    def load(self, req, action_id, do_not_eval=False):
-        Actions = req.session.model('ir.actions.actions')
+    @http.route('/web/action/load', type='json', auth="user")
+    def load(self, action_id, do_not_eval=False):
+        Actions = request.session.model('ir.actions.actions')
         value = False
         try:
             action_id = int(action_id)
         except ValueError:
             try:
                 module, xmlid = action_id.split('.', 1)
-                model, action_id = req.session.model('ir.model.data').get_object_reference(module, xmlid)
+                model, action_id = request.session.model('ir.model.data').get_object_reference(module, xmlid)
                 assert model.startswith('ir.actions.')
             except Exception:
                 action_id = 0   # force failed read
 
-        base_action = Actions.read([action_id], ['type'], req.context)
+        base_action = Actions.read([action_id], ['type'], request.context)
         if base_action:
             ctx = {}
             action_type = base_action[0]['type']
             if action_type == 'ir.actions.report.xml':
                 ctx.update({'bin_size': True})
-            ctx.update(req.context)
-            action = req.session.model(action_type).read([action_id], False, ctx)
+            ctx.update(request.context)
+            action = request.session.model(action_type).read([action_id], False, ctx)
             if action:
-                value = clean_action(req, action[0])
+                value = clean_action(action[0])
         return value
 
-    @openerpweb.jsonrequest
-    def run(self, req, action_id):
-        return_action = req.session.model('ir.actions.server').run(
-            [action_id], req.context)
+    @http.route('/web/action/run', type='json', auth="user")
+    def run(self, action_id):
+        return_action = request.session.model('ir.actions.server').run(
+            [action_id], request.context)
         if return_action:
-            return clean_action(req, return_action)
+            return clean_action(return_action)
         else:
             return False
 
-class Export(openerpweb.Controller):
-    _cp_path = "/web/export"
+class Export(http.Controller):
 
-    @openerpweb.jsonrequest
-    def formats(self, req):
+    @http.route('/web/export/formats', type='json', auth="user")
+    def formats(self):
         """ Returns all valid export formats
 
         :returns: for each export format, a pair of identifier and printable name
         :rtype: [(str, str)]
         """
-        return sorted([
-            controller.fmt
-            for path, controller in openerpweb.controllers_path.iteritems()
-            if path.startswith(self._cp_path)
-            if hasattr(controller, 'fmt')
-        ], key=operator.itemgetter("label"))
-
-    def fields_get(self, req, model):
-        Model = req.session.model(model)
-        fields = Model.fields_get(False, req.context)
+        return [
+            {'tag': 'csv', 'label': 'CSV'},
+            {'tag': 'xls', 'label': 'Excel', 'error': None if xlwt else "XLWT required"},
+        ]
+
+    def fields_get(self, model):
+        Model = request.session.model(model)
+        fields = Model.fields_get(False, request.context)
         return fields
 
-    @openerpweb.jsonrequest
-    def get_fields(self, req, model, prefix='', parent_name= '',
+    @http.route('/web/export/get_fields', type='json', auth="user")
+    def get_fields(self, model, prefix='', parent_name= '',
                    import_compat=True, parent_field_type=None,
                    exclude=None):
 
         if import_compat and parent_field_type == "many2one":
             fields = {}
         else:
-            fields = self.fields_get(req, model)
+            fields = self.fields_get(model)
 
         if import_compat:
             fields.pop('id', None)
@@ -1511,24 +1473,24 @@ class Export(openerpweb.Controller):
 
         return records
 
-    @openerpweb.jsonrequest
-    def namelist(self,req,  model, export_id):
+    @http.route('/web/export/namelist', type='json', auth="user")
+    def namelist(self, model, export_id):
         # TODO: namelist really has no reason to be in Python (although itertools.groupby helps)
-        export = req.session.model("ir.exports").read([export_id])[0]
-        export_fields_list = req.session.model("ir.exports.line").read(
+        export = request.session.model("ir.exports").read([export_id])[0]
+        export_fields_list = request.session.model("ir.exports.line").read(
             export['export_fields'])
 
         fields_data = self.fields_info(
-            req, model, map(operator.itemgetter('name'), export_fields_list))
+            model, map(operator.itemgetter('name'), export_fields_list))
 
         return [
             {'name': field['name'], 'label': fields_data[field['name']]}
             for field in export_fields_list
         ]
 
-    def fields_info(self, req, model, export_fields):
+    def fields_info(self, model, export_fields):
         info = {}
-        fields = self.fields_get(req, model)
+        fields = self.fields_get(model)
         if ".id" in export_fields:
             fields['.id'] = fields.pop('id', {'string': 'ID'})
 
@@ -1567,7 +1529,7 @@ class Export(openerpweb.Controller):
             if length == 2:
                 # subfields is a seq of $base/*rest, and not loaded yet
                 info.update(self.graft_subfields(
-                    req, fields[base]['relation'], base, fields[base]['string'],
+                    fields[base]['relation'], base, fields[base]['string'],
                     subfields
                 ))
             elif base in fields:
@@ -1575,11 +1537,11 @@ class Export(openerpweb.Controller):
 
         return info
 
-    def graft_subfields(self, req, model, prefix, prefix_string, fields):
+    def graft_subfields(self, model, prefix, prefix_string, fields):
         export_fields = [field.split('/', 1)[1] for field in fields]
         return (
             (prefix + '/' + k, prefix_string + '/' + v)
-            for k, v in self.fields_info(req, model, export_fields).iteritems())
+            for k, v in self.fields_info(model, export_fields).iteritems())
 
 class ExportFormat(object):
     @property
@@ -1604,18 +1566,17 @@ class ExportFormat(object):
         """
         raise NotImplementedError()
 
-    @openerpweb.httprequest
-    def index(self, req, data, token):
+    def base(self, data, token):
         model, fields, ids, domain, import_compat = \
             operator.itemgetter('model', 'fields', 'ids', 'domain',
                                 'import_compat')(
                 simplejson.loads(data))
 
-        Model = req.session.model(model)
-        ids = ids or Model.search(domain, 0, False, False, req.context)
+        Model = request.session.model(model)
+        ids = ids or Model.search(domain, 0, False, False, request.context)
 
         field_names = map(operator.itemgetter('name'), fields)
-        import_data = Model.export_data(ids, field_names, req.context).get('datas',[])
+        import_data = Model.export_data(ids, field_names, request.context).get('datas',[])
 
         if import_compat:
             columns_headers = field_names
@@ -1623,15 +1584,17 @@ class ExportFormat(object):
             columns_headers = [val['label'].strip() for val in fields]
 
 
-        return req.make_response(self.from_data(columns_headers, import_data),
+        return request.make_response(self.from_data(columns_headers, import_data),
             headers=[('Content-Disposition',
-                            content_disposition(self.filename(model), req)),
+                            content_disposition(self.filename(model))),
                      ('Content-Type', self.content_type)],
             cookies={'fileToken': token})
 
 class CSVExport(ExportFormat, http.Controller):
-    _cp_path = '/web/export/csv'
-    fmt = {'tag': 'csv', 'label': 'CSV'}
+
+    @http.route('/web/export/csv', type='http', auth="user")
+    def index(self, data, token):
+        return self.base(data, token)
 
     @property
     def content_type(self):
@@ -1665,12 +1628,10 @@ class CSVExport(ExportFormat, http.Controller):
         return data
 
 class ExcelExport(ExportFormat, http.Controller):
-    _cp_path = '/web/export/xls'
-    fmt = {
-        'tag': 'xls',
-        'label': 'Excel',
-        'error': None if xlwt else "XLWT required"
-    }
+
+    @http.route('/web/export/xls', type='http', auth="user")
+    def index(self, data, token):
+        return self.base(data, token)
 
     @property
     def content_type(self):
@@ -1703,8 +1664,7 @@ class ExcelExport(ExportFormat, http.Controller):
         fp.close()
         return data
 
-class Reports(openerpweb.Controller):
-    _cp_path = "/web/report"
+class Reports(http.Controller):
     POLLING_DELAY = 0.25
     TYPES_MAPPING = {
         'doc': 'application/vnd.ms-word',
@@ -1715,16 +1675,16 @@ class Reports(openerpweb.Controller):
         'xls': 'application/vnd.ms-excel',
     }
 
-    @openerpweb.httprequest
-    def index(self, req, action, token):
+    @http.route('/web/report', type='http', auth="user")
+    def index(self, action, token):
         action = simplejson.loads(action)
 
-        report_srv = req.session.proxy("report")
-        context = dict(req.context)
+        report_srv = request.session.proxy("report")
+        context = dict(request.context)
         context.update(action["context"])
 
         report_data = {}
-        report_ids = context["active_ids"]
+        report_ids = context.get("active_ids", None)
         if 'report_type' in action:
             report_data['report_type'] = action['report_type']
         if 'datas' in action:
@@ -1733,14 +1693,14 @@ class Reports(openerpweb.Controller):
             report_data.update(action['datas'])
 
         report_id = report_srv.report(
-            req.session._db, req.session._uid, req.session._password,
+            request.session.db, request.session.uid, request.session.password,
             action["report_name"], report_ids,
             report_data, context)
 
         report_struct = None
         while True:
             report_struct = report_srv.report_get(
-                req.session._db, req.session._uid, req.session._password, report_id)
+                request.session.db, request.session.uid, request.session.password, report_id)
             if report_struct["state"]:
                 break
 
@@ -1753,7 +1713,7 @@ class Reports(openerpweb.Controller):
             report_struct['format'], 'octet-stream')
         file_name = action.get('name', 'report')
         if 'name' not in action:
-            reports = req.session.model('ir.actions.report.xml')
+            reports = request.session.model('ir.actions.report.xml')
             res_id = reports.search([('report_name', '=', action['report_name']),],
                                     0, False, False, context)
             if len(res_id) > 0:
@@ -1762,9 +1722,9 @@ class Reports(openerpweb.Controller):
                 file_name = action['report_name']
         file_name = '%s.%s' % (file_name, report_struct['format'])
 
-        return req.make_response(report,
+        return request.make_response(report,
              headers=[
-                 ('Content-Disposition', content_disposition(file_name, req)),
+                 ('Content-Disposition', content_disposition(file_name)),
                  ('Content-Type', report_mimetype),
                  ('Content-Length', len(report))],
              cookies={'fileToken': token})