[FIX] website_forum: post edition working again
[odoo/odoo.git] / openerp / http.py
index abfbbea..bcd7511 100644 (file)
@@ -23,6 +23,7 @@ import urlparse
 import warnings
 
 import babel.core
+import psycopg2
 import simplejson
 import werkzeug.contrib.sessions
 import werkzeug.datastructures
@@ -34,12 +35,43 @@ import werkzeug.wsgi
 
 import openerp
 from openerp.service import security, model as service_model
+import openerp.tools
 
 _logger = logging.getLogger(__name__)
 
 #----------------------------------------------------------
 # RequestHandler
 #----------------------------------------------------------
+# Thread local global request object
+_request_stack = werkzeug.local.LocalStack()
+
+request = _request_stack()
+"""
+    A global proxy that always redirect to the current request object.
+"""
+
+def local_redirect(path, query=None, keep_hash=False, forward_debug=True, code=303):
+    url = path
+    if not query:
+        query = {}
+    if forward_debug and request and request.debug:
+        query['debug'] = None
+    if query:
+        url += '?' + werkzeug.url_encode(query)
+    if keep_hash:
+        return redirect_with_hash(url, code)
+    else:
+        return werkzeug.utils.redirect(url, code)
+
+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).
+    # See extensive test page at http://greenbytes.de/tech/tc/httpredirects/
+    if request.httprequest.user_agent.browser in ('firefox',):
+        return werkzeug.utils.redirect(url, code)
+    return "<html><head><script>window.location = '%s' + location.hash;</script></head></html>" % url
+
 class WebRequest(object):
     """ Parent class for all OpenERP Web request types, mostly deals with
     initialization and setup of the request object (the dispatching itself has
@@ -97,12 +129,10 @@ class WebRequest(object):
         self.session_id = httprequest.session.sid
         self.disable_db = False
         self.uid = None
-        self.func = None
-        self.func_arguments = {}
+        self.endpoint = None
         self.auth_method = None
         self._cr_cm = None
         self._cr = None
-        self.func_request_type = None
         # set db/uid trackers - they're cleaned up at the WSGI
         # dispatching phase in openerp.service.wsgi_server.application
         if self.db:
@@ -135,55 +165,64 @@ class WebRequest(object):
         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__()
+        if not self._cr:
+            # Test cursors
+            self._cr = openerp.tests.common.acquire_test_cursor(self.session_id)
+            if not self._cr:
+                self._cr = self.registry.db.cursor()
         return self._cr
 
-    def set_handler(self, func, arguments, auth):
+    def __enter__(self):
+        _request_stack.push(self)
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        _request_stack.pop()
+
+        if self._cr:
+            # Dont commit test cursors
+            if not openerp.tests.common.release_test_cursor(self.session_id):
+                if exc_type is None:
+                    self._cr.commit()
+                self._cr.close()
+        # just to be sure no one tries to re-use the request
+        self.disable_db = True
+        self.uid = None
+
+    def set_handler(self, endpoint, arguments, auth):
         # is this needed ?
         arguments = dict((k, v) for k, v in arguments.iteritems()
                          if not k.startswith("_ignored_"))
 
-        self.func = func
-        self.func_request_type = func.exposed
-        self.func_arguments = arguments
+        endpoint.arguments = arguments
+        self.endpoint = endpoint
         self.auth_method = auth
 
     def _call_function(self, *args, **kwargs):
+        request = self
+        if self.endpoint.routing['type'] != self._request_type:
+            raise Exception("%s, %s: Function declared as capable of handling request of type '%s' but called with a request of type '%s'" \
+                % (self.endpoint.original, self.httprequest.path, self.endpoint.routing['type'], self._request_type))
+
+        kwargs.update(self.endpoint.arguments)
+
+        # Backward for 7.0
+        if self.endpoint.first_arg_is_req:
+            args = (request,) + args
+        # Correct exception handling and concurency retry
+        @service_model.check
+        def checked_call(___dbname, *a, **kw):
+            return self.endpoint(*a, **kw)
+
+        # FIXME: code and rollback management could be cleaned
         try:
-            # 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():
-                if 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))
-
-                kwargs.update(self.func_arguments)
-
-                # Backward for 7.0
-                if getattr(self.func, '_first_arg_is_req', False):
-                    args = (request,) + args
-                # Correct exception handling and concurency retry
-                @service_model.check
-                def checked_call(dbname, *a, **kw):
-                    return self.func(*a, **kw)
-                if self.db:
-                    return checked_call(self.db, *args, **kwargs)
-                return self.func(*args, **kwargs)
-        finally:
-            # just to be sure no one tries to re-use the request
-            self.disable_db = True
-            self.uid = None
+            if self.db:
+                return checked_call(self.db, *args, **kwargs)
+            return self.endpoint(*args, **kwargs)
+        except Exception:
+            if self._cr:
+                self._cr.rollback()
+            raise
 
     @property
     def debug(self):
@@ -194,7 +233,7 @@ class WebRequest(object):
         warnings.warn('please use request.registry and request.cr directly', DeprecationWarning)
         yield (self.registry, self.cr)
 
-def route(route, type="http", auth="user"):
+def route(route=None, **kw):
     """
     Decorator marking the decorated method as being a handler for requests. The method must be part of a subclass
     of ``Controller``.
@@ -211,17 +250,35 @@ def route(route, type="http", auth="user"):
         * ``none``: The method is always active, even if there is no database. Mainly used by the framework and
         authentication modules. There request code will not have any facilities to access the database nor have any
         configuration indicating the current database nor the current user.
+    :param methods: A sequence of http methods this route applies to. If not specified, all methods are allowed.
+    :param cors: The Access-Control-Allow-Origin cors directive value.
     """
-    assert type in ["http", "json"]
+    routing = kw.copy()
+    assert not 'type' in routing or routing['type'] in ("http", "json")
     def decorator(f):
-        if isinstance(route, list):
-            f.routes = route
-        else:
-            f.routes = [route]
-        f.exposed = type
-        if getattr(f, "auth", None) is None:
-            f.auth = auth
-        return f
+        if route:
+            if isinstance(route, list):
+                routes = route
+            else:
+                routes = [route]
+            routing['routes'] = routes
+        @functools.wraps(f)
+        def response_wrap(*args, **kw):
+            response = f(*args, **kw)
+            if isinstance(response, Response) or f.routing_type == 'json':
+                return response
+            elif isinstance(response, werkzeug.wrappers.BaseResponse):
+                response = Response.force_type(response)
+                response.set_default()
+                return response
+            elif isinstance(response, basestring):
+                return Response(response)
+            else:
+                _logger.warn("<function %s.%s> returns an invalid response type for an http request" % (f.__module__, f.__name__))
+            return response
+        response_wrap.routing = routing
+        response_wrap.original_func = f
+        return response_wrap
     return decorator
 
 class JsonRequest(WebRequest):
@@ -271,7 +328,7 @@ class JsonRequest(WebRequest):
         if jsonp and self.httprequest.method == 'POST':
             # jsonp 2 steps step1 POST: save call
             def handler():
-                self.session.jsonp_requests[request_id] = self.httprequest.form['r']
+                self.session['jsonp_request_%s' % (request_id,)] = self.httprequest.form['r']
                 self.session.modified = True
                 headers=[('Content-Type', 'text/plain; charset=utf-8')]
                 r = werkzeug.wrappers.Response(request_id, headers=headers)
@@ -283,7 +340,7 @@ class JsonRequest(WebRequest):
             request = args.get('r')
         elif jsonp and request_id:
             # jsonp 2 steps step2 GET: run and return result
-            request = self.session.jsonp_requests.pop(request_id, "")
+            request = self.session.pop('jsonp_request_%s' % (request_id,), '{}')
         else:
             # regular jsonrpc2
             request = self.httprequest.stream.read()
@@ -291,7 +348,7 @@ class JsonRequest(WebRequest):
         # Read POST content or POST Form Data named "request"
         self.jsonrequest = simplejson.loads(request)
         self.params = dict(self.jsonrequest.get("params", {}))
-        self.context = self.params.pop('context', self.session.context)
+        self.context = self.params.pop('context', dict(self.session.context))
 
     def dispatch(self):
         """ Calls the method asked for by the JSON-RPC2 or JSONP request
@@ -334,7 +391,7 @@ class JsonRequest(WebRequest):
             mime = 'application/json'
             body = simplejson.dumps(response)
 
-        r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
+        r = Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))])
         return r
 
 def serialize_exception(e):
@@ -373,11 +430,10 @@ def jsonrequest(f):
 
         Use the ``route()`` decorator instead.
     """
-    f.combine = True
     base = f.__name__.lstrip('/')
     if f.__name__ == "index":
         base = ""
-    return route([base, base + "/<path:_ignored_path>"], type="json", auth="user")(f)
+    return route([base, base + "/<path:_ignored_path>"], type="json", auth="user", combine=True)(f)
 
 class HttpRequest(WebRequest):
     """ Regular GET/POST request
@@ -386,29 +442,23 @@ class HttpRequest(WebRequest):
 
     def __init__(self, *args):
         super(HttpRequest, self).__init__(*args)
-        params = dict(self.httprequest.args)
-        params.update(self.httprequest.form)
-        params.update(self.httprequest.files)
+        params = self.httprequest.args.to_dict()
+        params.update(self.httprequest.form.to_dict())
+        params.update(self.httprequest.files.to_dict())
         params.pop('session_id', None)
         self.params = params
 
     def dispatch(self):
-        try:
-            r = self._call_function(**self.params)
-        except werkzeug.exceptions.HTTPException, e:
-            r = e
-        except Exception, e:
-            _logger.exception("An exception occured during an http request")
-            se = serialize_exception(e)
-            error = {
-                'code': 200,
-                'message': "OpenERP Server Error",
-                'data': se
+        if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'):
+            headers = {
+                'Access-Control-Max-Age': 60 * 60 * 24,
+                'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
             }
-            r = werkzeug.exceptions.InternalServerError(simplejson.dumps(error))
-        else:
-            if not r:
-                r = werkzeug.wrappers.Response(status=204)  # no content
+            return Response(status=200, headers=headers)
+
+        r = self._call_function(**self.params)
+        if not r:
+            r = Response(status=204)  # no content
         return r
 
     def make_response(self, data, headers=None, cookies=None):
@@ -425,12 +475,24 @@ class HttpRequest(WebRequest):
         :type headers: ``[(name, value)]``
         :param collections.Mapping cookies: cookies to set on the client
         """
-        response = werkzeug.wrappers.Response(data, headers=headers)
+        response = Response(data, headers=headers)
         if cookies:
             for k, v in cookies.iteritems():
                 response.set_cookie(k, v)
         return response
 
+    def render(self, template, qcontext=None, **kw):
+        """ Lazy render of QWeb template.
+
+        The actual rendering of the given template will occur at then end of
+        the dispatching. Meanwhile, the template and/or qcontext can be
+        altered or even replaced by a static response.
+
+        :param basestring template: template to render
+        :param dict qcontext: Rendering context to use
+        """
+        return Response(template=template, qcontext=qcontext, **kw)
+
     def not_found(self, description=None):
         """ Helper for 404 response, return its result from the method
         """
@@ -442,29 +504,10 @@ def httprequest(f):
 
         Use the ``route()`` decorator instead.
     """
-    f.combine = True
     base = f.__name__.lstrip('/')
     if f.__name__ == "index":
         base = ""
-    return route([base, base + "/<path:_ignored_path>"], type="http", auth="user")(f)
-
-#----------------------------------------------------------
-# Thread local global request object
-#----------------------------------------------------------
-_request_stack = werkzeug.local.LocalStack()
-
-request = _request_stack()
-"""
-    A global proxy that always redirect to the current request object.
-"""
-
-@contextlib.contextmanager
-def set_request(req):
-    _request_stack.push(req)
-    try:
-        yield
-    finally:
-        _request_stack.pop()
+    return route([base, base + "/<path:_ignored_path>"], type="http", auth="user", combine=True)(f)
 
 #----------------------------------------------------------
 # Controller and route registration
@@ -501,6 +544,21 @@ class ControllerType(type):
 class Controller(object):
     __metaclass__ = ControllerType
 
+class EndPoint(object):
+    def __init__(self, method, routing):
+        self.method = method
+        self.original = getattr(method, 'original_func', method)
+        self.routing = routing
+        self.arguments = {}
+
+    @property
+    def first_arg_is_req(self):
+        # Backward for 7.0
+        return getattr(self.method, '_first_arg_is_req', False)
+
+    def __call__(self, *args, **kw):
+        return self.method(*args, **kw)
+
 def routing_map(modules, nodb_only, converters=None):
     routing_map = werkzeug.routing.Map(strict_slashes=False, converters=converters)
     for module in modules:
@@ -517,13 +575,35 @@ def routing_map(modules, nodb_only, converters=None):
             o = cls()
             members = inspect.getmembers(o)
             for mk, mv in members:
-                if inspect.ismethod(mv) and getattr(mv, 'exposed', False) and (not nodb_only or nodb_only == (mv.auth == "none")):
-                    for url in mv.routes:
-                        if getattr(mv, "combine", False):
-                            url = o._cp_path.rstrip('/') + '/' + url.lstrip('/')
-                            if url.endswith("/") and len(url) > 1:
-                                url = url[: -1]
-                        routing_map.add(werkzeug.routing.Rule(url, endpoint=mv))
+                if inspect.ismethod(mv) and hasattr(mv, 'routing'):
+                    routing = dict(type='http', auth='user', methods=None, routes=None)
+                    methods_done = list()
+                    routing_type = None
+                    for claz in reversed(mv.im_class.mro()):
+                        fn = getattr(claz, mv.func_name, None)
+                        if fn and hasattr(fn, 'routing') and fn not in methods_done:
+                            fn_type = fn.routing.get('type')
+                            if not routing_type:
+                                routing_type = fn_type
+                            else:
+                                if fn_type and routing_type != fn_type:
+                                    _logger.warn("Subclass re-defines <function %s.%s> with different type than original."
+                                                    " Will use original type: %r", fn.__module__, fn.__name__, routing_type)
+                                fn.routing['type'] = routing_type
+                            fn.original_func.routing_type = routing_type
+                            methods_done.append(fn)
+                            routing.update(fn.routing)
+                    if not nodb_only or nodb_only == (routing['auth'] == "none"):
+                        assert routing['routes'], "Method %r has not route defined" % mv
+                        endpoint = EndPoint(mv, routing)
+                        for url in routing['routes']:
+                            if routing.get("combine", False):
+                                # deprecated
+                                url = o._cp_path.rstrip('/') + '/' + url.lstrip('/')
+                                if url.endswith("/") and len(url) > 1:
+                                    url = url[: -1]
+
+                            routing_map.add(werkzeug.routing.Rule(url, endpoint=endpoint, methods=routing['methods']))
     return routing_map
 
 #----------------------------------------------------------
@@ -553,7 +633,7 @@ class Service(object):
 class Model(object):
     """
         .. deprecated:: 8.0
-        Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
+        Use the resistry and cursor in ``openerp.http.request`` instead.
     """
     def __init__(self, session, model):
         self.session = session
@@ -642,9 +722,10 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
             raise SessionExpiredException("Session expired")
         security.check(self.db, self.uid, self.password)
 
-    def logout(self):
+    def logout(self, keep_db=False):
         for k in self.keys():
-            del self[k]
+            if not (keep_db and k == 'db'):
+                del self[k]
         self._default_values()
 
     def _default_values(self):
@@ -653,7 +734,6 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
         self.setdefault("login", None)
         self.setdefault("password", None)
         self.setdefault("context", {'tz': "UTC", "uid": None})
-        self.setdefault("jsonp_requests", {})
 
     def get_context(self):
         """
@@ -793,6 +873,40 @@ class OpenERPSession(werkzeug.contrib.sessions.Session):
 
         return Model(self, model)
 
+    def save_action(self, action):
+        """
+        This method store an action object in the session and returns an integer
+        identifying that action. The method get_action() can be used to get
+        back the action.
+
+        :param the_action: The action to save in the session.
+        :type the_action: anything
+        :return: A key identifying the saved action.
+        :rtype: integer
+        """
+        saved_actions = self.setdefault('saved_actions', {"next": 1, "actions": {}})
+        # we don't allow more than 10 stored actions
+        if len(saved_actions["actions"]) >= 10:
+            del saved_actions["actions"][min(saved_actions["actions"])]
+        key = saved_actions["next"]
+        saved_actions["actions"][key] = action
+        saved_actions["next"] = key + 1
+        self.modified = True
+        return key
+
+    def get_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).
+
+        :param key: The key given by save_action()
+        :type key: integer
+        :return: The saved action or None.
+        :rtype: anything
+        """
+        saved_actions = self.get('saved_actions', {})
+        return saved_actions.get("actions", {}).get(key)
+
 def session_gc(session_store):
     if random.random() < 0.001:
         # we keep session one week
@@ -813,6 +927,52 @@ mimetypes.add_type('application/font-woff', '.woff')
 mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
 mimetypes.add_type('application/x-font-ttf', '.ttf')
 
+class Response(werkzeug.wrappers.Response):
+    """ Response object passed through controller route chain.
+
+    In addition to the werkzeug.wrappers.Response parameters, this
+    classe's constructor can take the following additional parameters
+    for QWeb Lazy Rendering.
+
+    :param basestring template: template to render
+    :param dict qcontext: Rendering context to use
+    :param int uid: User id to use for the ir.ui.view render call
+    """
+    default_mimetype = 'text/html'
+    def __init__(self, *args, **kw):
+        template = kw.pop('template', None)
+        qcontext = kw.pop('qcontext', None)
+        uid = kw.pop('uid', None)
+        super(Response, self).__init__(*args, **kw)
+        self.set_default(template, qcontext, uid)
+
+    def set_default(self, template=None, qcontext=None, uid=None):
+        self.template = template
+        self.qcontext = qcontext or dict()
+        self.uid = uid
+        # Support for Cross-Origin Resource Sharing
+        if request.endpoint and 'cors' in request.endpoint.routing:
+            self.headers.set('Access-Control-Allow-Origin', request.endpoint.routing['cors'])
+            methods = 'GET, POST'
+            if request.endpoint.routing['type'] == 'json':
+                methods = 'POST'
+            elif request.endpoint.routing.get('methods'):
+                methods = ', '.join(request.endpoint.routing['methods'])
+            self.headers.set('Access-Control-Allow-Methods', methods)
+
+    @property
+    def is_qweb(self):
+        return self.template is not None
+
+    def render(self):
+        view_obj = request.registry["ir.ui.view"]
+        uid = self.uid or request.uid or openerp.SUPERUSER_ID
+        return view_obj.render(request.cr, uid, self.template, self.qcontext, context=request.context)
+
+    def flatten(self):
+        self.response.append(self.render())
+        self.template = None
+
 class DisableCacheMiddleware(object):
     def __init__(self, app):
         self.app = app
@@ -870,7 +1030,7 @@ class Root(object):
         self.load_addons()
 
         _logger.info("Generating nondb routing")
-        self.nodb_routing_map = routing_map(['', "web"], True)
+        self.nodb_routing_map = routing_map([''] + openerp.conf.server_wide_modules, True)
 
     def __call__(self, environ, start_response):
         """ Handle a WSGI request
@@ -918,11 +1078,17 @@ class Root(object):
         return explicit_session
 
     def setup_db(self, httprequest):
-        # if no db is found on the session try to deduce it from the domain
-        db = db_monodb(httprequest)
-        if db != httprequest.session.db:
-            httprequest.session.logout()
-            httprequest.session.db = db
+        db = httprequest.session.db
+        # Check if session.db is legit
+        if db:
+            if db not in db_filter([db], httprequest=httprequest):
+                _logger.warn("Logged into database '%s', but dbfilter "
+                             "rejects it; logging session out.", db)
+                httprequest.session.logout()
+                db = None
+
+        if not db:
+            httprequest.session.db = db_monodb(httprequest)
 
     def setup_lang(self, httprequest):
         if not "lang" in httprequest.session.context:
@@ -940,8 +1106,17 @@ class Root(object):
             return HttpRequest(httprequest)
 
     def get_response(self, httprequest, result, explicit_session):
+        if isinstance(result, Response) and result.is_qweb:
+            try:
+                result.flatten()
+            except(Exception), e:
+                if request.db:
+                    result = request.registry['ir.http']._handle_exception(e)
+                else:
+                    raise
+
         if isinstance(result, basestring):
-            response = werkzeug.wrappers.Response(result, mimetype='text/html')
+            response = Response(result, mimetype='text/html')
         else:
             response = result
 
@@ -964,7 +1139,6 @@ class Root(object):
         """
         try:
             httprequest = werkzeug.wrappers.Request(environ)
-            httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableDict
             httprequest.app = self
 
             explicit_session = self.setup_session(httprequest)
@@ -973,18 +1147,32 @@ class Root(object):
 
             request = self.get_request(httprequest)
 
-            with set_request(request):
-                db = request.session.db 
+            def _dispatch_nodb():
+                func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match()
+                request.set_handler(func, arguments, "none")
+                result = request.dispatch()
+                return result
+
+            with request:
+                db = request.session.db
                 if db:
                     openerp.modules.registry.RegistryManager.check_registry_signaling(db)
-                    result = request.registry['ir.http']._dispatch()
-                    openerp.modules.registry.RegistryManager.signal_caches_change(db)
+                    try:
+                        with openerp.tools.mute_logger('openerp.sql_db'):
+                            ir_http = request.registry['ir.http']
+                    except psycopg2.OperationalError:
+                        # psycopg2 error. At this point, that means the
+                        # database probably does not exists anymore. Log the
+                        # user out and fall back to nodb
+                        request.session.logout()
+                        result = _dispatch_nodb()
+                    else:
+                        result = ir_http._dispatch()
+                        openerp.modules.registry.RegistryManager.signal_caches_change(db)
                 else:
-                    # fallback to non-db handlers
-                    func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match()
-                    request.set_handler(func, arguments, "none")
-                    result = request.dispatch()
-            response =  self.get_response(httprequest, result, explicit_session)
+                    result = _dispatch_nodb()
+
+                response = self.get_response(httprequest, result, explicit_session)
             return response(environ, start_response)
 
         except werkzeug.exceptions.HTTPException, e:
@@ -996,9 +1184,12 @@ class Root(object):
         return request.registry['ir.http'].routing_map()
 
 def db_list(force=False, httprequest=None):
-    httprequest = httprequest or request.httprequest
     dbs = openerp.netsvc.dispatch_rpc("db", "list", [force])
-    h = httprequest.environ['HTTP_HOST'].split(':')[0]
+    return db_filter(dbs, httprequest=httprequest)
+
+def db_filter(dbs, httprequest=None):
+    httprequest = httprequest or request.httprequest
+    h = httprequest.environ.get('HTTP_HOST', '').split(':')[0]
     d = h.split('.')[0]
     r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
     dbs = [i for i in dbs if re.match(r, i)]
@@ -1016,8 +1207,6 @@ def db_monodb(httprequest=None):
         Returns ``None`` if the magic is not magic enough.
     """
     httprequest = httprequest or request.httprequest
-    db = None
-    redirect = None
 
     dbs = db_list(True, httprequest)
 
@@ -1052,6 +1241,6 @@ root = None
 def wsgi_postload():
     global root
     root = Root()
-    openerp.wsgi.register_wsgi_handler(root)
+    openerp.service.wsgi_server.register_wsgi_handler(root)
 
 # vim:et:ts=4:sw=4: